5.6 KiB
Handy-Http Handlers
The Handlers package defines a few very convenient HttpRequestHandler implementations for you to use when setting up your Handy-Http server.
The Path Handler
The PathHandler is a request handler that hands off incoming requests to different sub-handlers based on the request's URL path and/or the HTTP method the client used (GET, POST, etc.). Most of your typical HTTP server use cases may therefore benefit from using this handler to map your API endpoints to specific paths.
Here's a basic example of how you might set this up for your own usage, by mapping your own handlers (in this case a usersHandler and loginHandler) to different HTTP methods and URLs. At runtime, the path handler will receive all incoming requests, and hand them off to the appropriate handler, or return a 404 if no appropriate mapping exists.
import handy_http_primitives;
import handy_http_transport;
import handy_http_handlers.path_handler;
void main() {
PathHandler pathHandler = new PathHandler();
HttpRequestHandler usersHandler = ...;
pathHandler.addMapping(HttpMethod.GET, "/users", usersHandler);
HttpRequestHandler loginHandler = ...;
pathHandler.addMapping(HttpMethod.POST, "/login", loginHandler);
HttpTransport transport = new TaskPoolHttp1Transport(pathHandler);
transport.start();
}
Registering Handlers Automatically
Instead of manually calling addMapping for each and every endpoint in your API, you can instead use the path mapper's registerHandlers method, which will scan a given module for request handler functions annotated with @PathMapping (or @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, or @PatchMapping). Below is an example of some valid mapping functions.
import handy_http_primitives;
import handy_http_handlers.path_handler;
@PathMapping(HttpMethod.GET, "/status")
void getStatus(ref ServerHttpRequest request, ref ServerHttpResponse response) {
response.status = HttpStatus.OK;
response.writeBodyString("Online");
}
@PostMapping("/login")
void postLogin(ref ServerHttpRequest request, ref ServerHttpResponse response) {
doLogin(request);
response.status = HttpStatus.OK;
response.writeBodyString("You have logged in!");
}
The Filtered Handler
Inspired by Java's Servlet Filters, the FilteredHandler gives you the ability to compose a filter chain of pluggable HttpRequestFilters that are executed before the Filtered Handler's actual base handler.
The idea is fairly abstract, but a common use case that can help to understand the value of filters is user authentication and authorization checks, which should be performed whenever a client makes a request. We can create our own HttpRequestFilter implementation that checks, for example, if the user has provided a valid authentication token in the request's headers, and if not, rejects the request immediately without even letting our base handler see the request.
import handy_http_primitives;
import handy_http_handlers.filtered_handler;
HttpRequestHandler secureApiHandler = ...;
HttpRequestFilter authFilter = new AuthFilter();
HttpRequestHandler filteredHandler = new FilteredHandler(
[authFilter],
secureApiHandler
);
Now, any request to your underlying handler must pass through your auth filter, and you've successfully added some middleware to your HTTP server setup!
Chained Filters
It's called a filter chain because the filters are declared in an ordered list, and applied in that order to each request. Each filter in the chain must pass on the request to the next filter by calling filterChain.doFilter(request, response). If a filter does not pass on the request, then the request processing ends there, which gives each filter the ability to short-circuit any request if it deems it necessary.
For a practical example of a filter chain in action, let's take a look at the Finnow project's api_mapping.d module: https://git.andrewlalis.com/andrew/finnow/src/branch/main/finnow-api/source/api_mapping.d
You can see that the main request handler for the entire API is defined as follows:
// Build the main handler into a filter chain:
return new FilteredHandler(
[
cast(HttpRequestFilter) new CorsFilter(webOrigin),
cast(HttpRequestFilter) new ContentLengthFilter(),
cast(HttpRequestFilter) new TokenBucketRateLimitingFilter(10, 50),
cast(HttpRequestFilter) new ExceptionHandlingFilter()
],
publicHandler
);
The filter chain is therefore defined as:
- A CORS filter that adds various CORS headers to responses.
- A content-length filter that rejects requests with missing or too-large content.
- A rate-limiter filter that uses a token bucket algorithm to help prevent spam from clients.
- An exception handling filter that catches and nicely formats any exceptions thrown by the underlying request handler.
As you can see, filters are an excellent way to plug in additional functionality that's decoupled from the core of your web server's logic, while enhancing the overall behavior.