Add docs on handlers.
Build and Deploy Documentation Site / build-and-deploy (push) Successful in 24s Details

This commit is contained in:
Andrew Lalis 2026-04-03 21:32:02 -04:00
parent 620bf6e3a2
commit 6475dcc533
1 changed files with 96 additions and 1 deletions

View File

@ -1,4 +1,99 @@
# Handy-Http Handlers
[DDoc Reference](ddoc/handlers/index.html)
WIP
The [Handlers](https://git.andrewlalis.com/Handy-Http/handlers) package defines a few very convenient [`HttpRequestHandler`](ddoc/primitives/handy_http_primitives.handler.HttpRequestHandler.html) implementations for you to use when setting up your Handy-Http server.
## The Path Handler
The [`PathHandler`](ddoc/handlers/handy_http_handlers.path_handler.PathHandler.html) 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.
```d
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`](ddoc/handlers/handy_http_handlers.path_handler.PathHandler.registerHandlers.html) 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.
```d
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](https://www.oracle.com/java/technologies/filters.html), the [`FilteredHandler`](ddoc/handlers/handy_http_handlers.filtered_handler.FilteredHandler.html) gives you the ability to compose a *filter chain* of pluggable [`HttpRequestFilter`](ddoc/handlers/handy_http_handlers.filtered_handler.HttpRequestFilter.html)s 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`](ddoc/handlers/handy_http_handlers.filtered_handler.HttpRequestFilter.html) 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.
```d
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](https://git.andrewlalis.com/andrew/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:
```d
// 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:
1. A CORS filter that adds various CORS headers to responses.
2. A content-length filter that rejects requests with missing or too-large content.
3. A rate-limiter filter that uses a token bucket algorithm to help prevent spam from clients.
4. 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.