Middleware
aioduct supports a middleware layer that lets you intercept and modify requests before they are sent and responses after they are received. This is useful for cross-cutting concerns like logging, metrics, authentication token refresh, or header injection.
The Middleware Trait
#![allow(unused)]
fn main() {
pub trait Middleware: Send + Sync + 'static {
fn on_request(&self, request: &mut http::Request<RequestBodySend>, uri: &Uri) { }
fn on_response(&self, response: &mut http::Response<RequestBodySend>, uri: &Uri) { }
}
}
Both methods have default no-op implementations, so you only need to override what you use.
Using Closures
For simple request-only middleware, you can pass a closure directly:
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
let client = TokioClient::builder()
.middleware(|req: &mut http::Request<aioduct::RequestBodySend>, _uri: &http::Uri| {
req.headers_mut().insert(
http::header::HeaderName::from_static("x-custom"),
http::header::HeaderValue::from_static("value"),
);
})
.build()?;
}
Using a Struct
For middleware that needs to modify responses or maintain state, implement the trait on a struct:
#![allow(unused)]
fn main() {
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use aioduct::{TokioClient, RequestBodySend, Middleware};
struct RequestCounter {
count: Arc<AtomicU64>,
}
impl Middleware for RequestCounter {
fn on_request(&self, _req: &mut http::Request<RequestBodySend>, _uri: &http::Uri) {
self.count.fetch_add(1, Ordering::Relaxed);
}
}
let counter = Arc::new(AtomicU64::new(0));
let client = TokioClient::builder()
.middleware(RequestCounter { count: counter.clone() })
.build()?;
}
Stacking Multiple Middleware
You can add multiple middleware layers. They execute in order:
- Request hooks run first-to-last (in the order they were added).
- Response hooks run last-to-first (reverse order).
#![allow(unused)]
fn main() {
use aioduct::TokioClient;
let client = TokioClient::builder()
.middleware(|req: &mut http::Request<aioduct::RequestBodySend>, _uri: &http::Uri| {
// Runs first on request
req.headers_mut().insert(
http::header::HeaderName::from_static("x-trace-id"),
http::header::HeaderValue::from_static("abc123"),
);
})
.middleware(|req: &mut http::Request<aioduct::RequestBodySend>, _uri: &http::Uri| {
// Runs second on request
req.headers_mut().insert(
http::header::HeaderName::from_static("x-auth"),
http::header::HeaderValue::from_static("Bearer tok"),
);
})
.build()?;
}
When Middleware Runs
Middleware hooks run at these points in the request lifecycle:
- The request is fully built (headers, body, query params applied).
on_requestis called for each middleware in order.- The request is sent over the connection.
- The response is received.
on_responseis called for each middleware in reverse order.- Decompression is applied (if enabled).
- The response is returned to the caller.
Note that middleware runs on each individual request, including redirect hops.