Cookbook / Middleware and state
Middleware
Closure middleware, middleware as a type, scoping to a router or one route, and passing data to handlers.
Middleware wraps requests in the order it is added. Server middleware covers every request, including 404s and static files; router and route middleware cover only their own routes. Use from_fn for a quick closure, or implement Middleware for something reusable.
use axeon::middleware::{from_fn, Middleware, MiddlewareResult, Next};
use axeon::{json, Json, Request, Router, Server, ServerError};
use std::time::Instant;
/// The caller identified by the API key check.
#[derive(Clone)]
struct Caller(String);
/// Rejects requests without the right `X-Api-Key`.
struct ApiKey {
key: String,
}
impl Middleware for ApiKey {
fn call(&self, mut req: Request, next: Next) -> MiddlewareResult {
if req.get_header("x-api-key") != Some(self.key.as_str()) {
return Box::pin(async { Err(ServerError::Unauthorized("missing or wrong API key".into())) });
}
req.insert_extension(Caller("demo-user".into()));
next.handle(req)
}
}
fn main() {
let mut app = Server::new();
// Times every request, including 404s.
app.middleware(from_fn(|req, next| async move {
let start = Instant::now();
let (method, path) = (req.method(), req.path().to_string());
let res = next.handle(req).await?;
println!("[{}] {} {} - {:?}", res.status, method, path, start.elapsed());
Ok(res)
}));
app.get("/public", |_req| async { "open to everyone" });
// Everything under /api needs the key.
let mut api = Router::new();
api.middleware(ApiKey { key: "secret".into() });
api.get("/profile", |req| async move {
let caller = req.extension::<Caller>().map(|c| c.0.clone());
Json(json!({ "user": caller }))
});
app.mount("/api", api);
// Middleware for a single route.
let admins_only = from_fn(|req, next| async move {
if req.get_header("x-admin").is_none() {
return Err(ServerError::Forbidden("admins only".into()));
}
next.handle(req).await
});
app.route("/posts").get(|_req| async { "all posts" });
app.route("/posts").middleware(admins_only).post(|_req| async { (201, "created") });
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl http://127.0.0.1:3000/public
curl -i http://127.0.0.1:3000/api/profile # 401
curl http://127.0.0.1:3000/api/profile -H 'x-api-key: secret' # {"user":"demo-user"}
Notes#
req.insert_extension(value)attaches typed data for later middleware and handlers; read it back withreq.extension::<T>().- Because errors reach middleware as responses,
next.handle(req).await?is safe: headers you add afterwards still appear on error responses. - Prefer a ready-made one when it exists: see Hardening an API.