Middleware
10/30/24About 1 min
Middleware Architecture
Middleware is a core mechanism for processing HTTP requests in our application. It allows us to execute common, reusable operations (logging, authentication, permission checks) before or after the request reaches the final business handler.
Our middleware system follows the classic Onion Model. Think of each middleware as a layer of onion skin: requests penetrate from outer to inner layers, reach the core (Handler), and then responses travel back from inner to outer.
Middleware Execution Flow (Onion Model)
A typical protected request flows through our middleware in this order:
- Request In ->
OperateLogMid(Operation Log Middleware - starts timer)- ->
AuthMid(Request Context Builder Middleware) - ->
from_extractor::<UserInfo>()(JWT Authentication Middleware) - ->
ApiMid(API Permission Verification Middleware) - ->
Your Handler(Core Business Logic) - <-
ApiMid - <-
from_extractor::<UserInfo>() - <-
AuthMid - <-
OperateLogMid(Operation Log Middleware - calculates duration & logs) - <- Response Out
Core Components: Request Extensions
Middlewares share data through Axum's Request Extensions mechanism. We use two custom extension structs:
ReqCtx: Contains request context info (normalized path, method, params, etc.)UserInfo: Contains current logged-in user info parsed from JWT Token
Middleware Details
1. OperateLogMid - Operation Log Middleware (Outermost)
- Responsibility: Records detailed operation logs (request info, response data, duration, user info)
- Key Design: Async non-blocking logging via
tokio::spawn, doesn't block the response
2. AuthMid - Request Context Builder
- Responsibility: Parses HTTP request and builds a structured
ReqCtxobject - Key Flow: Extracts URI, Path, Query, Method; reads request body; constructs
ReqCtx
3. UserInfo Extractor - JWT Authentication
- Responsibility: Validates JWT Token and provides user identity
- Implemented via
FromRequestPartstrait
4. ApiMid - API Permission Verification
- Responsibility: Checks if the current user has permission to access the requested API
- Decision: If authorized, passes to next handler; if not, returns 403/404
How to Apply Middleware
// main.rs
let protected_api = WebApi::routers();
let protected_api_with_middleware = protected_api
.layer(middleware::from_fn(operate_log_fn_mid)) // Outermost: Operation Log
.layer(middleware::from_fn(auth_fn_mid)) // 2nd: Context Build
.layer(middleware::from_extractor_with_state::<UserInfo, AppState>(app_state.clone())) // 3rd: JWT Auth
.layer(middleware::from_fn(api_fn_mid)); // Innermost: API Permission
let app = Router::new()
.merge(public_api)
.merge(protected_api_with_middleware);Note: .layer() call order determines the onion layers. The first .layer() is the outermost layer.