Writing Custom Middleware
1. Welcome and Icebreaker:
If you could add any feature to every HTTP request, what would it be?
2. Outline the Session:
Agenda:
- Concept Explanation: How to write your own middleware in Axum/tower.
- Live Coding: Implement a simple custom middleware (e.g., request timing).
- Tasks: Practice by building and applying your own middleware.
Learning Outcomes:
- Understand the basics of
tower::Layerandtower::Service. - Write and use custom middleware in Axum.
Expectations: Try to write a middleware that does something unique for your app!
3. Concept Explanation:
Custom middleware lets you add logic before or after requests are handled. In Axum, you can create middleware by implementing the Layer and Service traits, or by using middleware::from_fn for simple cases.
4. Live Coding Demonstration:
use axum::{Router, routing::get, middleware};
use std::time::Instant;
async fn hello() -> &'static str {
"Hello, Axum!"
}
async fn timing_middleware(req: axum::extract::Request, next: axum::middleware::Next) -> axum::response::Response {
let start = Instant::now();
let response = next.run(req).await;
println!("Request took {:?}", start.elapsed());
response
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(hello))
.layer(middleware::from_fn(timing_middleware));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Best Practices:
- Keep custom middleware simple and focused.
- Use logging or metrics to observe middleware effects.
Common Pitfalls:
- Forgetting to call
next.run(req).await. - Blocking the async runtime with long-running operations.
5. Tasks
- Write a middleware that checks for a custom header and rejects requests if it’s missing.
- Add a middleware that logs the request body size.
- Chain your custom middleware with built-in middleware and observe the order.