Menu
Public repository / caebd6669c276c9c4dd88ff423984ed8df65d490
AkurAI-Framework / site/content/docs/middleware.md
Latest change 0ebc2057fd9a65182855334c219522ae944ad484 - docs: /docs guides for routing, forms, storage, middleware, SEO, styling + dev server by Ólafur Búi Ólafsson
# Middleware
Middleware wraps every request: it can inspect the request, call the next layer,
and post-process the response — for cross-cutting concerns like security
headers, logging, timing, redirects, and auth gates.
## The model
A middleware receives the request and a `next` function. It may call `next` to
continue down the chain, or short-circuit by returning a response itself:
```rust
fn handle(&self, req: &Request, next: &dyn Fn(&Request) -> Reply) -> Reply;
```
Layers compose into a `MiddlewareStack` and run **outermost-first** — the first
one pushed is the outermost, so it sees the request first and the response last.
## Running with a stack
The stack is additive: `Server::run(handler)` is unchanged, and
`Server::run_with(stack, handler)` adds the chain.
```rust
let stack = MiddlewareStack::new()
.push(SecurityHeaders) // outermost: stamps even short-circuited replies
.push(RequestLogger) // logs the final method, path, status
.push(Timing); // adds X-Response-Time-Us
server.run_with(stack, handler)?;
```
Order matters: put `SecurityHeaders` outside any blocklist or auth gate so even a
rejected request gets hardened headers.
## Built-ins
- **`SecurityHeaders`** — `X-Content-Type-Options: nosniff`,
`X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`.
- **`RequestLogger`** — prints `METHOD path -> status` to stderr (std-only).
- **`Timing`** — adds an `X-Response-Time-Us` response header.
These are active on the framework's own site — every response carries the
security and timing headers (try `curl -I` against any page).
## Writing your own
Any closure `Fn(&Request, &dyn Fn(&Request) -> Reply) -> Reply` is a middleware,
so a one-off layer needs no new type:
```rust
stack.push(|req: &Request, next: &dyn Fn(&Request) -> Reply| {
if req.path == "/blocked" {
return Reply::Response(Response::new(403).with_text("nope"));
}
next(req)
})
```
WebSocket and SSE upgrade replies pass through untouched.