Skip to content

Middleware

Middleware is an async chain that wraps every request. Oxide ships a Middleware trait + Next driver and lets you attach middleware globally (all routes), per-group, or per-route.

Custom middleware

Each middleware is a struct that implements the Middleware trait. Convention: put them under src/app/http/middleware/.

src/app/http/middleware/log_requests.rs
use std::sync::Arc;
use std::time::Instant;
use async_trait::async_trait;
use oxide_http::{Middleware, Next, Request, Response};
pub struct LogRequests;
impl LogRequests {
pub fn new() -> Arc<dyn Middleware> {
Arc::new(Self)
}
}
#[async_trait]
impl Middleware for LogRequests {
async fn handle(&self, req: Request, next: Next) -> Response {
let method = req.method().clone();
let path = req.path().to_string();
let started = Instant::now();
let response = next.run(req).await;
tracing::info!(
%method, %path,
status = response.status_code().as_u16(),
elapsed_ms = started.elapsed().as_millis(),
"request"
);
response
}
}

Attaching middleware

// Per-group:
app.middleware([LogRequests::new()]).group("/api", |r| {
r.get("/health", health_handler);
});
// Per-route (single-route group):
app.middleware([auth_middleware::<User>()])
.get("/me", UserController::me);

Global middleware

Wire middleware globally via a service provider using app.use_middleware(mw). The middleware runs on every request — including requests that would otherwise 404 or 405.

Oxide’s built-in CORS uses this pattern:

src/bootstrap.rs
app.register(ConfigServiceProvider)
.register(CorsServiceProvider) // adds Cors::... via app.use_middleware
.register(DatabaseServiceProvider)
.register(AppServiceProvider)
.register(RouteServiceProvider)
.boot();

Built-in middleware

MiddlewarePurpose
Cors::new() / Cors::builder()Cross-origin headers; wired globally via CorsServiceProvider reading config/cors.toml.
oxide_auth::auth_middleware::<U>()Verify Bearer token; on success attach Authed<U> to the request.
oxide_auth::abilities([...])Require all listed abilities on the current token.
oxide_auth::ability([...])Require any of the listed abilities.

Chain order

Execution is outer → inner:

app.middleware([A, B]).get("/x", handler);
// Order: A.handle → B.handle → handler → B (post) → A (post)

Global middleware registered via app.use_middleware runs outside any per-route middleware.

Early return

Middleware short-circuits by returning a Response without calling next.run(req).await. The inner chain and the handler never run:

#[async_trait]
impl Middleware for RequireBearer {
async fn handle(&self, req: Request, next: Next) -> Response {
if req.header("authorization").is_none() {
return Response::unauthorized();
}
next.run(req).await
}
}