Middleware¶
Middleware is opt-in policy that runs around your handlers: a pre-request hook that returns a Decision, and a post-response hook that observes or rewrites the outgoing response. The same middleware runs on HTTP/1.1, HTTP/2, and HTTP/3; it operates on the unified RequestView and Response, not on a protocol. Like the rest of swerver, the chain is allocation-free on the hot path; middleware that needs to produce a body borrows a managed pool buffer (middleware.respondManaged) rather than allocating.
The decision¶
A pre-request middleware returns a Decision that tells the chain what to do next:
pub const Decision = union(enum) {
allow, // continue to the next middleware
skip, // skip the remaining middleware
reject: Response, // stop and return this response
modify: struct { // add headers, then continue
response_headers: []Header,
continue_chain: bool,
},
rate_limit_backpressure: u64, // pause reads for N ms (backpressure)
};
You rarely write Decision by hand: the built-in middleware below covers the common policies, and reject/rate_limit_backpressure are how they short-circuit (a 402, a 429, an auth 401).
Wiring middleware¶
| Scope | How |
|---|---|
| Global | ServerBuilder.middleware(chain): runs on every request. |
| Route / group | RouteBuilder.withMiddleware(...) on a route, or a Router.group(prefix): localized policy. |
Several built-ins are also driven by configuration rather than wired in code: when you run the prebuilt server, auth, rate limiting, body validation, and response caching are configured per-route in the JSON config (routes[].auth, routes[].rate_limit, routes[].body_schema, routes[].cache). See Reverse proxy & gateway and Configuration for those route blocks.
Built-in middleware¶
| Middleware | What it does |
|---|---|
| Authentication | Per-route auth: api_key (named keys from a header or query param), jwt (HS256/384/512, RS256; checks issuer/audience, forwards claims as headers), forward_auth (delegate to an external service), anonymous (fixed subject), and chain (try methods in order, first success wins). |
| Rate limiting | Token bucket (requests_per_second, burst_size), keyed per-IP or per-consumer. Returns 429 Too Many Requests with a Retry-After header when the bucket is empty, and integrates with read backpressure. |
| Security headers | Injects HSTS, CSP, Referrer-Policy, and CORS headers before responses leave the connection, with TLS-aware behavior (e.g. HSTS only over TLS). |
| Response compression | gzip/deflate based on Accept-Encoding, applied automatically to compressible content types above a size threshold. |
| Access logging | Combined or JSON access logs, configured via the access_log block. |
| Request body validation | JSON Schema validation of request bodies, configured per-route via routes[].body_schema (type, required, properties, length/range constraints). Invalid bodies are rejected before the handler runs. |
| OpenTelemetry | Exports traces to an OTLP collector with configurable service name, sample rate, flush interval, and batch size. |
| Prometheus metrics | Serves /metrics as Prometheus text (request/response counters, latency histograms, and QUIC stats, labeled per protocol) generated with zero heap allocations. |
| Health probes | Intercepts GET /.healthz (liveness, always 200) and GET /.ready (readiness, 200 when pools/listeners/TLS/QUIC are up, else 503) before the router sees them. Bodies are empty. |
| x402 payments | On a route tagged with x402 config, returns 402 Payment Required with the payment challenge until a valid payment is presented. |
Order and short-circuiting¶
The chain runs in registration order, with health and metrics intercepting their probe paths before the app router ever sees them. Any middleware can short-circuit with reject (auth failures, rate-limit 429, x402 402); later middleware and the handler don't run. Post-response hooks (security headers, access logging, metrics, OpenTelemetry) then observe or decorate whatever response was produced, including short-circuited ones.
Probes never hit your router
Because the health and metrics middleware intercept /.healthz, /.ready, and /metrics ahead of routing, you can't accidentally shadow them with an app route, and your router never has to handle them.
For per-route config blocks (auth, rate_limit, body_schema, cache) and the upstream/proxy gateway features, continue to Reverse proxy & gateway. For the metrics and tracing surface in production, see Observability.