AkurAI Build
Menu

Public repository / 93e2d2fc0fb93a097bfad0a2e0ed7f3d7b9b776b

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.