Added filtered handler.
This commit is contained in:
parent
75df2d17df
commit
555bcf54c6
|
@ -0,0 +1,19 @@
|
||||||
|
name: Build and Test Module
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'source/**'
|
||||||
|
- '.gitea/workflows/ci.yaml'
|
||||||
|
pull_request:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup DLang
|
||||||
|
uses: dlang-community/setup-dlang@v2
|
||||||
|
with:
|
||||||
|
compiler: ldc-latest
|
||||||
|
- name: Build and Test
|
||||||
|
run: dub -q test
|
|
@ -0,0 +1,176 @@
|
||||||
|
/**
|
||||||
|
* Defines a "filtered" request handler, that applies an ordered set of filters
|
||||||
|
* (otherwise known as a "filter chain") before and after handling a request,
|
||||||
|
* as a means of adding a simple middleware layer to HTTP request processing.
|
||||||
|
*/
|
||||||
|
module handy_http_handlers.filtered_handler;
|
||||||
|
|
||||||
|
import handy_http_primitives;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter that can be applied to an HTTP request. If the filter determines
|
||||||
|
* that it's okay to continue processing the request, it should call
|
||||||
|
* `filterChain.doFilter(request, response)` to continue the chain. If the
|
||||||
|
* chain is not continued, request processing ends at this filter, and the
|
||||||
|
* current response is sent back to the client.
|
||||||
|
*/
|
||||||
|
interface HttpRequestFilter {
|
||||||
|
void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response, FilterChain filterChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A filter chain is a singly-linked list that represents a series of filters
|
||||||
|
* to be applied when processing a request.
|
||||||
|
*/
|
||||||
|
class FilterChain {
|
||||||
|
private HttpRequestFilter filter;
|
||||||
|
private FilterChain next;
|
||||||
|
|
||||||
|
this(HttpRequestFilter filter, FilterChain next) {
|
||||||
|
this.filter = filter;
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies this filter chain link's filter to the given request and
|
||||||
|
* response, and then if there's another link in the chain, calls it to
|
||||||
|
* apply its filter thereafter, and so on until the chain is complete or
|
||||||
|
* a filter has short-circuited without calling `filterChain.doFilter`.
|
||||||
|
* Params:
|
||||||
|
* request = The request.
|
||||||
|
* response = The response.
|
||||||
|
*/
|
||||||
|
void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||||
|
if (next !is null) {
|
||||||
|
filter.doFilter(request, response, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a filter chain from a list of request filters.
|
||||||
|
* Params:
|
||||||
|
* filters = The filters to use to build the filter chain.
|
||||||
|
* Returns: The root of the filter chain that when called, executes for
|
||||||
|
* each of the filters provided.
|
||||||
|
*/
|
||||||
|
static FilterChain build(HttpRequestFilter[] filters) {
|
||||||
|
if (filters.length == 0) return null;
|
||||||
|
|
||||||
|
FilterChain root = new FilterChain(filters[0], null);
|
||||||
|
FilterChain currentLink = root;
|
||||||
|
for (size_t i = 1; i < filters.length; i++) {
|
||||||
|
currentLink.next = new FilterChain(filters[i], null);
|
||||||
|
currentLink = currentLink.next;
|
||||||
|
}
|
||||||
|
// Add an "end cap" to the chain to make sure the last filter gets called.
|
||||||
|
currentLink.next = new FilterChain(null, null);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
assert(FilterChain.build([]) is null);
|
||||||
|
|
||||||
|
class SimpleFilter : HttpRequestFilter {
|
||||||
|
int id;
|
||||||
|
bool shortCircuit;
|
||||||
|
this(int id, bool shortCircuit = false) {
|
||||||
|
this.id = id;
|
||||||
|
this.shortCircuit = shortCircuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response, FilterChain filterChain) {
|
||||||
|
import std.conv : to;
|
||||||
|
response.headers.add("filter-" ~ id.to!string, id.to!string);
|
||||||
|
if (!shortCircuit) filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that all filters are applied.
|
||||||
|
FilterChain fc = FilterChain.build([
|
||||||
|
new SimpleFilter(1),
|
||||||
|
new SimpleFilter(2),
|
||||||
|
new SimpleFilter(3)
|
||||||
|
]);
|
||||||
|
ServerHttpRequest request = ServerHttpRequestBuilder().build();
|
||||||
|
ServerHttpResponse response = ServerHttpResponseBuilder().build();
|
||||||
|
fc.doFilter(request, response);
|
||||||
|
assert(response.headers.contains("filter-1"));
|
||||||
|
assert(response.headers.contains("filter-2"));
|
||||||
|
assert(response.headers.contains("filter-3"));
|
||||||
|
|
||||||
|
// Test that if we short-circuit, any further filters are NOT applied.
|
||||||
|
FilterChain fc2 = FilterChain.build([
|
||||||
|
new SimpleFilter(1),
|
||||||
|
new SimpleFilter(2, true),
|
||||||
|
new SimpleFilter(3)
|
||||||
|
]);
|
||||||
|
ServerHttpRequest request2 = ServerHttpRequestBuilder().build();
|
||||||
|
ServerHttpResponse response2 = ServerHttpResponseBuilder().build();
|
||||||
|
fc2.doFilter(request2, response2);
|
||||||
|
assert(response2.headers.contains("filter-1"));
|
||||||
|
assert(response2.headers.contains("filter-2"));
|
||||||
|
assert(!response2.headers.contains("filter-3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple base filter that should always sit at the bottom of the filter
|
||||||
|
* chain, which just calls a request handler.
|
||||||
|
*/
|
||||||
|
class BaseHandlerRequestFilter : HttpRequestFilter {
|
||||||
|
/// The request handler that'll be called.
|
||||||
|
private HttpRequestHandler handler;
|
||||||
|
|
||||||
|
this(HttpRequestHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doFilter(ref ServerHttpRequest request, ref ServerHttpResponse response, FilterChain filterChain) {
|
||||||
|
handler.handle(request, response);
|
||||||
|
// Don't call filterChain.doFilter because this is always the last part of the filter chain.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FilteredHandler is a request handler you can add to your server to apply
|
||||||
|
* a filter chain to an underlying request handler.
|
||||||
|
*/
|
||||||
|
class FilteredHandler : HttpRequestHandler {
|
||||||
|
/// The internal filter chain that this handler calls.
|
||||||
|
private FilterChain filterChain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a filtered handler that applies the given filter chain. Note
|
||||||
|
* that you should probabconstly use the other constructor for most cases, but
|
||||||
|
* if you really want to provide a custom filter chain, you'll most likely
|
||||||
|
* want to add the `BaseHandlerRequestFilter` as the last one in the chain.
|
||||||
|
* Params:
|
||||||
|
* filterChain = The filter chain to use.
|
||||||
|
*/
|
||||||
|
this(FilterChain filterChain) {
|
||||||
|
this.filterChain = filterChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a filtered handler that applies the given set of filters, in
|
||||||
|
* order, before potentially calling the given base handler.
|
||||||
|
* Params:
|
||||||
|
* filters = The set of filters to apply to all requests.
|
||||||
|
* baseHandler = The base handler that'll be called if an incoming
|
||||||
|
* request is passed successfully through all filters.
|
||||||
|
*/
|
||||||
|
this(HttpRequestFilter[] filters, HttpRequestHandler baseHandler) {
|
||||||
|
HttpRequestFilter[] allFilters = filters ~ [cast(HttpRequestFilter) new BaseHandlerRequestFilter(baseHandler)];
|
||||||
|
this.filterChain = FilterChain.build(allFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an incoming request by simply calling the filter chain on it.
|
||||||
|
* Params:
|
||||||
|
* request = The request.
|
||||||
|
* response = The response.
|
||||||
|
*/
|
||||||
|
void handle(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Defines a path-matching request handler, that will match incoming requests
|
||||||
|
* against a set of mappings based on the URL and HTTP method, and call a
|
||||||
|
* handler depending on what's matched.
|
||||||
|
*/
|
||||||
module handy_http_handlers.path_handler;
|
module handy_http_handlers.path_handler;
|
||||||
|
|
||||||
import handy_http_primitives;
|
import handy_http_primitives;
|
||||||
import path_matcher;
|
import path_matcher;
|
||||||
import slf4d;
|
import slf4d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key used to read and write path-handler related data to the request's
|
||||||
|
* context data attribute.
|
||||||
|
*/
|
||||||
private immutable REQUEST_CONTEXT_DATA_KEY = "pathHandler";
|
private immutable REQUEST_CONTEXT_DATA_KEY = "pathHandler";
|
||||||
|
|
||||||
/// Internal struct holding details about a handler mapping.
|
/// Internal struct holding details about a handler mapping.
|
||||||
|
|
Loading…
Reference in New Issue