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;
 | 
			
		||||
 | 
			
		||||
import handy_http_primitives;
 | 
			
		||||
import path_matcher;
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
/// Internal struct holding details about a handler mapping.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue