1. axum middleware
axum crate 本身也有一些基础的 middleware 机制,在实现简单的逻辑时可以不用接入 tower。
1.1. 调用顺序
根据这篇文章的介绍,原生 axum middleware 的声明层级和调用顺序其实是反过来的,比较恶心。例如这么声明:
use axum::{routing::get, Router};
async fn handler() {}
let app = Router::new()
.route("/", get(handler))
.layer(layer_one)
.layer(layer_two)
.layer(layer_three);
那么实际上,在处理请求时各层的调用顺序是:
requests
|
v
+----- layer_three -----+
| +---- layer_two ----+ |
| | +-- layer_one --+ | |
| | | | | |
| | | handler | | |
| | | | | |
| | +-- layer_one --+ | |
| +---- layer_two ----+ |
+----- layer_three -----+
|
v
responses
确实有点反直觉。如果要符合直觉,则还是要用 tower 的方式:
use tower::ServiceBuilder;
use axum::{routing::get, Router};
async fn handler() {}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(layer_one)
.layer(layer_two)
.layer(layer_three),
);
1.2. 简单的例子
用一个简单的例子来解释 axum 中间件的用法。假设我们需要引入一个对 handler 处理时间的计时层,则该 layer 的函数需要这么写:
use axum::{
body::Body,
http::{Request, Response},
middleware::Next,
};
pub async fn log_request_duration(req: Request<Body>, next: Next) -> Response<Body> {
let uri: String = req.uri().to_string();
let start_time = Instant::now();
let response = next.run(req).await;
let ms = start_time.elapsed().as_millis();
info!(
"Request {} took: {}ms, [{}]",
uri,
ms,
if ms >= 100 { "SLOW" } else { "OK" }
);
response
}
还是比较清楚的,可以看到中间件 layer 拥有了 Request 的所有权,因此可以在进入业务 handler 之前对 Request 进行修改。中间件之间的层级调用则是通过 next.run(req).await
。
在路由上,只需要使用 axum::middleware::from_fn
即可:
let app = Router::new()
.route("/", get(handler))
.layer(axum::middleware::from_fn(crate::log_request_duration));