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:

  1. Concept Explanation: How to write your own middleware in Axum/tower.
  2. Live Coding: Implement a simple custom middleware (e.g., request timing).
  3. Tasks: Practice by building and applying your own middleware.

Learning Outcomes:

  • Understand the basics of tower::Layer and tower::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.