Handler trait
主线位置:两条主线都经过这里。装配主线的第一步:
async fn因为宏生成的 impl 而成为Handler<T, S>,get(handler)把它连同T一起擦进BoxedIntoRoute,with_state时变成HandlerService。请求主线的最后一段:Handler::call拆开请求,从左到右跑提取器,调用你的函数,把返回值变成Response。
flowchart TB fnitem["async fn handler(a, b)"] --> implh["宏实现 Handler"] implh --> getnode["get(handler)"] getnode --> boxed["BoxedIntoRoute:擦掉 T"] boxed --> ws["with_state:HandlerService"] ws --> route["Route(路由一章)"] req["Request"] --> split["into_parts"] split --> pa["A::from_request_parts"] pa -->|Err| rej["rejection.into_response"] pa --> join["from_parts 拼回 Request"] join --> pb["B::from_request"] pb -->|Err| rej pb --> callfn["调用函数,into_response"]
axum 没有路由宏,处理函数就是一个普通的 async fn。它能被 get(...) 接受,是因为 handler/mod.rs 用一个 macro_rules! 宏,给「参数都是提取器、返回值实现 IntoResponse 的函数」实现了 Handler trait。本页讲这个 trait 和这组 impl;提取器那两个 trait 见 FromRequestParts 与 FromRequest,状态参数 S 见 State 与 FromRef。
定义
Handler 去掉文档和两个默认方法的方法体后是这样:
#[diagnostic::on_unimplemented(
note = "Consider using `#[axum::debug_handler]` to improve the error message"
)]
pub trait Handler<T, S>: Clone + Send + Sync + Sized + 'static {
/// The type of future calling this handler returns.
type Future: Future<Output = Response> + Send + 'static;
/// Call the handler with the given request.
fn call(self, req: Request, state: S) -> Self::Future;
fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>
// ...
fn with_state(self, state: S) -> HandlerService<Self, T, S>
// ...
}
| 位置 | 选择 | 原因 |
|---|---|---|
T | 类型参数,调用方从不手写 | 标记类型,下一节专讲 |
S | 类型参数 | 处理函数要的状态类型;State<S> 提取器靠它在编译期检查 |
call(self, ...) | 按值消费 handler | 每个请求先 clone 一份再调用,所以要求 Clone |
Send + Sync + 'static | 超 trait | handler 要被装箱存进 Router、跨线程共享 |
Future 的输出 | Response,不是 Result | 提取失败、业务出错都已经变成响应;到 tower 那一层才包成 Result<Response, Infallible> |
on_unimplemented 的 note 就是编译错误里那句「Consider using #[axum::debug_handler] to improve the error message」,后面几个编译错误里都能看到它。
标记类型 T:为什么非要它
trait 文档的「About type parameter T」一节(第 129 行起)这样解释:
/// The type parameter `T` is a workaround for trait coherence rules, allowing us to
/// write blanket implementations of `Handler` over many types of handler functions
/// with different numbers of arguments, without the compiler forbidding us from doing
/// so because one type `F` can in theory implement both `Fn(A) -> X` and `Fn(A, B) -> Y`.
/// `T` is a placeholder taking on a representation of the parameters of the handler function,
/// as well as other similar 'coherence rule workaround' discriminators,
/// allowing us to select one function signature to use as a `Handler`.
问题出在 impl 的写法上。想给「所有一个参数的函数」实现一个 trait,参数类型 A 只出现在 where 子句的 FnOnce(A) 里。下面这段不依赖 axum,只用 std:
trait Handler {
fn call(self) -> String;
}
impl<F, A> Handler for F
where
F: FnOnce(A) -> String,
A: Default,
{
fn call(self) -> String {
self(A::default())
}
}
fn main() {}
rustc 1.99.0 直接拒绝:
error[E0207]: the type parameter `A` is not constrained by the impl trait, self type, or predicates
--> src/bin/marker_missing.rs:5:9
|
5 | impl<F, A> Handler for F
| ^ unconstrained type parameter
FnOnce(A) 里的 A 是 trait 的泛型参数,不是关联类型,它不能由 F 唯一确定:一个类型在原则上可以对多组参数类型都实现 FnOnce。编译器因此要求 impl 里的每个类型参数都出现在 trait 或 self 类型里。把参数类型放进 trait 的类型参数,A 就被约束住了,而且 1 个参数和 2 个参数的两个 impl 是 Handler<(A,)> 和 Handler<(A, B)> 两个不同的 trait,不再互相冲突:
trait Handler<T> {
fn call(self) -> String;
}
impl<F, A> Handler<(A,)> for F
where
F: FnOnce(A) -> String,
A: Default,
{
fn call(self) -> String {
self(A::default())
}
}
impl<F, A, B> Handler<(A, B)> for F
where
F: FnOnce(A, B) -> String,
A: Default,
B: Default,
{
fn call(self) -> String {
self(A::default(), B::default())
}
}
fn run<T, H: Handler<T>>(h: H) -> String {
h.call()
}
fn one(n: u8) -> String {
format!("one({n})")
}
fn two(s: String, b: bool) -> String {
format!("two({s:?}, {b})")
}
fn main() {
println!("{}", run(one));
println!("{}", run(two));
}
one(0)
two("", false)
run(one) 和 run(two) 都没有写 T。函数项类型只实现了一种 FnOnce 签名,编译器能从这个签名推出唯一的 T。axum 的 get、post 也是这样:签名是 pub fn get<H, T, S>(handler: H) -> MethodRouter<S, Infallible> where H: Handler<T, S>(top_level_handler_fn! 生成),T 永远是推出来的。
axum 里 T 实际取的值:
| impl | T | 为什么是这个形状 |
|---|---|---|
| 0 个参数的函数 | ((),) | 宏要求至少有一个「最后一个参数」,0 个参数单独写 |
| 宏生成的 1–16 个参数 | (M, T1, ..., Tn) | M 是最后一个提取器的 FromRequest<S, M> 标记,同样必须出现在 trait 里,否则又是 E0207 |
T: IntoResponse | private::IntoResponseHandler | 一个没有值的私有枚举,只用来和函数的 impl 区分开 |
Layered<L, H, T, S> | 内层 handler 的 T 原样传出 | 套了 layer 之后仍是同一个「签名」 |
MethodRouter<S> | () | 让 MethodRouter 本身也能当 handler 用 |
文档里「other similar 'coherence rule workaround' discriminators」说的就是 IntoResponseHandler 这类不表示参数的标记。M 的两个取值 ViaParts / ViaRequest 在 FromRequestParts 与 FromRequest 一页。
T 只活在装配主线上。get(handler) 最后调到 BoxedIntoRoute::from_handler,它把 handler 装进 MakeErasedHandler,返回的 BoxedIntoRoute<S, E> 里已经没有 T。从这里往后,不同签名的 handler 是同一种类型,可以放进同一张路由表,见 路由与装箱。
宏:impl_handler 与 all_the_tuples
impl_handler! 原文:
macro_rules! impl_handler {
(
[$($ty:ident),*], $last:ident
) => {
#[diagnostic::do_not_recommend]
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, S, Res, M, $($ty,)* $last> Handler<(M, $($ty,)* $last,), S> for F
where
F: FnOnce($($ty,)* $last,) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
S: Send + Sync + 'static,
Res: IntoResponse,
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S, M> + Send,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
fn call(self, req: Request, state: S) -> Self::Future {
let (mut parts, body) = req.into_parts();
Box::pin(async move {
$(
let $ty = match $ty::from_request_parts(&mut parts, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*
let req = Request::from_parts(parts, body);
let $last = match $last::from_request(req, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
self($($ty,)* $last,).await.into_response()
})
}
}
};
}
all_the_tuples!(impl_handler);
宏的输入把参数分成两段:[$($ty),*] 是前面的参数,$last 是最后一个。前一段每个都要 FromRequestParts<S>,最后一个要 FromRequest<S, M>。这个分段就是「只有最后一个参数能拿 body」在类型上的样子。参数名直接用类型参数名(let T1 = ...),所以要 #[allow(non_snake_case)]。
all_the_tuples! 在 axum 自己的 macros.rs 里,16 行,每行调一次传进来的宏:
macro_rules! all_the_tuples {
($name:ident) => {
$name!([], T1);
$name!([T1], T2);
$name!([T1, T2], T3);
// ...
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
};
}
axum-core 里有一份内容相同的 all_the_tuples!,给提取器元组用(tuple.rs)。那一份没有 #[macro_export],只在 axum-core 内部可见,所以 axum 另写了一份。axum 里同一个 all_the_tuples! 还用在 middleware/from_fn.rs 和 map_request.rs 上。
用 cargo expand -p axum --lib 展开 axum 0.8.9,$name!([T1], T2) 这一行生成的 impl 是:
#[diagnostic::do_not_recommend]
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, S, Res, M, T1, T2> Handler<(M, T1, T2), S> for F
where
F: FnOnce(T1, T2) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Res> + Send,
S: Send + Sync + 'static,
Res: IntoResponse,
T1: FromRequestParts<S> + Send,
T2: FromRequest<S, M> + Send,
{
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
fn call(self, req: Request, state: S) -> Self::Future {
let (mut parts, body) = req.into_parts();
Box::pin(async move {
let T1 = match T1::from_request_parts(&mut parts, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
let req = Request::from_parts(parts, body);
let T2 = match T2::from_request(req, &state).await {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
self(T1, T2).await.into_response()
})
}
}
同一份展开里 Handler 一共有 20 个 impl:0 个参数 1 个、宏生成 16 个(最后一个是 Handler<(M, T1, ..., T16,), S>)、IntoResponseHandler、Layered、MethodRouter 各 1 个。
第 17 个参数
宏只展开到 T16,再多一个参数就没有 impl 可选。16 个 Method 参数的函数能编译,17 个的不能:
axum = { version = "=0.8.9", default-features = false }
use axum::{Router, http::Method, routing::get};
type M = Method;
async fn sixteen(
_: M, _: M, _: M, _: M, _: M, _: M, _: M, _: M,
_: M, _: M, _: M, _: M, _: M, _: M, _: M, _: M,
) {}
async fn seventeen(
_: M, _: M, _: M, _: M, _: M, _: M, _: M, _: M,
_: M, _: M, _: M, _: M, _: M, _: M, _: M, _: M, _: M,
) {}
fn main() {
let _app: Router = Router::new()
.route("/16", get(sixteen))
.route("/17", get(seventeen));
}
rustc 1.99.0 只报 /17 那一行(末尾三条 note 和汇总行省略:两条说长类型名写进了文件,一条说错误来自宏 top_level_handler_fn):
error[E0277]: the trait bound `fn(Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method) -> _ {seventeen}: Handler<_, _>` is not satisfied
--> src/bin/seventeen.rs:18:23
|
18 | .route("/17", get(seventeen));
| --- ^^^^^^^^^ unsatisfied trait bound
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<_, _>` is not implemented for fn item `fn(Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method, Method) -> _ {seventeen}`
= note: Consider using `#[axum::debug_handler]` to improve the error message
note: required by a bound in `axum::routing::get`
--> axum-0.8.9/src/routing/method_routing.rs:167:16
|
167 | H: Handler<T, S>,
| ^^^^^^^^^^^^^ required by this bound in `get`
...
441 | top_level_handler_fn!(get, GET);
| -------------------------------
| | |
| | required by a bound in this function
| in this macro invocation
报错只说 Handler<_, _> 没实现,不说「最多 16 个」。参数确实需要更多时,可以把几个提取器写成一个元组参数:(A, B, C) 本身实现了 FromRequestParts(axum-core 的 tuple.rs,同样由 all_the_tuples! 生成,见 FromRequestParts 与 FromRequest),一个元组只占一个参数位置。
上面宏原文里每个 Handler impl 都带着 #[diagnostic::do_not_recommend],它告诉编译器在报错时不要拿这个 impl 当建议。这个属性是 #3588 加的,PR 说明只有一句「See trybuild test output changes :)」。axum-core 0.5.6 的 CHANGELOG 写的是「Improve error messages with #[diagnostic::do_not_recommend].」
改之前,编译器会在提示里列出「the following other types implement trait」,而 axum 这些 trait 的实现里数量最多的恰恰是宏生成的元组。这个 PR 改动的 trybuild 期望输出 argument_not_extractor.stderr 里,FromRequestParts<S> 的候选列表原来是 ()、(T1, T2)、(T1, T2, T3) ……一直到 (T1, T2, T3, T4, T5, T6, T7, T8),改完变成 ConnectInfo<T>、Extension<T>、HeaderMap、MatchedPath、Method 这些真正能用的提取器。同一个提交给 handler/mod.rs 里的四个 Handler impl(0 参数、impl_handler!、IntoResponseHandler、Layered)和 axum-core 的 tuple.rs、option.rs、request_parts.rs 都加了这个属性。第一个包含它的版本是 axum 0.8.9 和 axum-core 0.5.6,正是本卷的版本。
Handler::call:顺序、短路、装箱
宏生成的 call 做四件事:
req.into_parts()把请求拆成Parts(方法、URI、版本、头、extensions)和 body。- 从左到右对前面的参数逐个
from_request_parts(&mut parts, &state).await。所有这些提取器共享同一个&mut Parts,前一个写进 extensions 的东西后一个看得到。 Request::from_parts(parts, body)拼回一个完整请求,交给最后一个参数的from_request,它拿到的是所有权,可以消费 body。- 调用函数,
.await,into_response()。
每一步的 Err(rejection) => return rejection.into_response() 是短路:一个提取器失败,async 块直接返回,后面的提取器不跑,你的函数也不会被调用。提取器之间是顺序 .await,没有并发。这两点用下面的动脑筋验证。
整个过程包在 Box::pin(async move { ... }) 里,所以函数 handler 的 Future 是 Pin<Box<dyn Future<Output = Response> + Send>>,每个请求一次堆分配。self 被移进 async 块;状态 state: S 也是按值传进来、在块里以 &state 借给每个提取器。
axum = { version = "=0.8.9", default-features = false }
tokio = { version = "=1.54.1", features = ["macros", "rt"] }
tower = { version = "=0.5.3", features = ["util"] }
http-body-util = "0.1"
use axum::{
Router,
body::Body,
extract::{FromRequest, FromRequestParts, Request},
http::{StatusCode, request::Parts},
routing::post,
};
use http_body_util::BodyExt;
use std::convert::Infallible;
use tower::ServiceExt;
#[derive(Clone, Default)]
struct Trail(Vec<u8>);
struct Step<const N: u8>;
impl<S: Send + Sync, const N: u8> FromRequestParts<S> for Step<N> {
type Rejection = Infallible;
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
let trail = parts.extensions.get_or_insert_default::<Trail>();
trail.0.push(N);
println!(" Step<{N}> trail = {:?}", trail.0);
Ok(Step)
}
}
struct Guard;
impl<S: Send + Sync> FromRequestParts<S> for Guard {
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
let ok = parts.headers.contains_key("x-token");
println!(" Guard x-token present = {ok}");
if ok { Ok(Guard) } else { Err((StatusCode::UNAUTHORIZED, "missing x-token")) }
}
}
struct Text(String);
impl<S: Send + Sync> FromRequest<S> for Text {
type Rejection = Infallible;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let trail = req.extensions().get::<Trail>().map(|t| t.0.clone());
let body = String::from_request(req, state).await.unwrap_or_default();
println!(" Text trail = {trail:?}, body = {body:?}");
Ok(Text(body))
}
}
async fn handler(_: Step<1>, _: Guard, _: Step<2>, Text(body): Text) -> String {
println!(" handler body = {body:?}");
format!("got {body}")
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let app = Router::new().route("/", post(handler));
for token in [true, false] {
println!("request with x-token = {token}");
let mut req = Request::post("/");
if token {
req = req.header("x-token", "t");
}
let res = app.clone().oneshot(req.body(Body::from("hi")).unwrap()).await.unwrap();
let status = res.status();
let bytes = res.into_body().collect().await.unwrap().to_bytes();
println!(" -> {status} {bytes:?}");
}
}
Step<N> 把自己的编号追加到 extensions 里的 Trail,Guard 在没有 x-token 头时拒绝,Text 是自定义的 body 提取器。第一个请求带 x-token,第二个不带。第二个请求在 -> ... 之前会打印几行?
不是函数的 Handler
Handler 还有三个不是宏生成的实现。
T: IntoResponse:第 271 行,get("Hello, World!") 这种固定响应靠它。它的 future 是 std::future::Ready<Response>,不装箱:
impl<T, S> Handler<private::IntoResponseHandler, S> for T
where
T: IntoResponse + Clone + Send + Sync + 'static,
{
type Future = std::future::Ready<Response>;
fn call(self, _req: Request, _state: S) -> Self::Future {
std::future::ready(self.into_response())
}
}
Layered:Handler::layer 返回的 Layered<L, H, T, S> 只存 layer 和 handler。它的 call 在每个请求里现场组装服务:
fn call(self, req: Request, state: S) -> Self::Future {
use futures_util::future::{FutureExt, Map};
let svc = self.handler.with_state(state);
let svc = self.layer.layer(svc);
let future: Map<
_,
fn(
Result<
<L::Service as Service<Request>>::Response,
<L::Service as Service<Request>>::Error,
>,
) -> _,
> = svc.oneshot(req).map(|result| match result {
Ok(res) => res.into_response(),
Err(err) => match err {},
});
future::LayeredFuture::new(future)
}
with_state 得到 HandlerService,layer.layer(...) 套上中间件,oneshot 先 poll_ready 再 call(ServiceExt 与 Oneshot)。Err(err) => match err {} 要求中间件的错误类型是 Infallible,impl 的约束里写着 L::Service: Service<Request, Error = Infallible>;会出错的 tower 中间件要先处理错误,见 layer 与 route_layer。Layer 本身见 Layer 与 Stack。
MethodRouter<S>:第 1310 行,T = (),call 转给 call_with_state,见 MethodRouter。
问: 函数 handler 的 Future 为什么是 Pin<Box<dyn Future>>?提取器 trait 的方法都直接返回 impl Future,Handler 为什么不也这样,省一次分配?
答: Handler::Future 是关联类型,必须能被别人写出名字。HandlerService 作为 tower Service 要声明 type Future = super::future::IntoServiceFuture<H::Future>,用到的就是这个名字。宏里的 async move { ... } 块的类型是匿名的,要把它写进关联类型只能用 type Future = impl Future<...>,而这在 rustc 1.99.0 上还不稳定。在一个不依赖 axum 的小文件里写 type Fut = impl Future<Output = u8>;,报错的第一行是:
error[E0658]: `impl Trait` in associated types is unstable
所以只能装箱擦掉类型。提取器不一样:from_request_parts 返回的 future 只在 Handler::call 那个 async 块里被 .await,没人需要写出它的名字,返回位置的 impl Future 就够了。
变成 Service:with_state 与 HandlerService
with_state(state) 返回 HandlerService<H, T, S>,三个字段:handler、state、_marker: PhantomData<fn() -> T>。fn() -> T 的写法让 HandlerService 的 Send / Sync 不受 T 影响(文件里的 traits 测试就是拿一个 NotSendSync 当 T 断言它仍然 Send + Sync)。
它的 Service 实现(第 148 行):
impl<H, T, S, B> Service<Request<B>> for HandlerService<H, T, S>
where
H: Handler<T, S> + Clone + Send + 'static,
B: HttpBody<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>,
S: Clone + Send + Sync,
{
type Response = Response;
type Error = Infallible;
type Future = super::future::IntoServiceFuture<H::Future>;
#[inline]
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// `IntoService` can only be constructed from async functions which are always ready, or
// from `Layered` which buffers in `<Layered as Handler>::call` and is therefore
// also always ready.
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<B>) -> Self::Future {
use futures_util::future::FutureExt;
let req = req.map(Body::new);
let handler = self.handler.clone();
let future = Handler::call(handler, req, self.state.clone());
let future = future.map(Ok as _);
super::future::IntoServiceFuture::new(future)
}
}
poll_ready永远Ready:handler 没有背压可言;Layered的中间件在自己的oneshot里poll_ready,不经过这里。- 请求 body 可以是任何
HttpBody<Data = Bytes>,进来先Body::new擦成 axum 的Body,所以Handler::call只需要处理Request<Body>。 - 每个请求
clone一次 handler、一次状态。函数项是零大小类型,clone不花什么;状态的clone代价见 State 与 FromRef。 future.map(Ok as _)把Response包成Result<Response, Infallible>,满足 tower 的形状。
没有状态的 handler 可以用 HandlerWithoutStateExt 的 into_service(),它就是 self.with_state(())。HandlerService 还对 serve::IncomingStream 实现了 Service(第 185 行,每条连接 clone 一份自己),所以 axum::serve(listener, handler.into_service()) 不需要 Router 也能跑。
在 Router 里,handler 不会直接以 HandlerService 的身份出现:from_handler 存的闭包是 |handler, state| Route::new(Handler::with_state(handler, state)),with_state 时才把 HandlerService 装进 Route,见 路由与装箱。
动脑筋答案
M2 Max、rustc 1.99.0、--release,连跑 3 次输出相同:
request with x-token = true
Step<1> trail = [1]
Guard x-token present = true
Step<2> trail = [1, 2]
Text trail = Some([1, 2]), body = "hi"
handler body = "hi"
-> 200 OK b"got hi"
request with x-token = false
Step<1> trail = [1]
Guard x-token present = false
-> 401 Unauthorized b"missing x-token"
答案是 2 行。
- 第一个请求:顺序就是参数顺序。
Step<2>看到的trail是[1, 2],说明它和Step<1>改的是同一个Parts;Text拿到的是拼回去的Request,extensions 里同样有[1, 2],body 也还在。 - 第二个请求:
Step<1>跑了,Guard返回Err,宏里的return rejection.into_response()让async块就此结束。Step<2>、Text、handler都没有打印,body 根本没被读。响应是Guard的 rejection(StatusCode::UNAUTHORIZED, "missing x-token")转成的 401。
小结
Handler<T, S>的T是标记类型:把参数类型放进 trait 参数,否则 impl 里的参数类型不受约束(E0207);不同参数个数的 impl 也因此互不冲突。T总是推出来的,from_handler装箱后就被擦掉。impl_handler!经all_the_tuples!展开出 1–16 个参数的 impl,0 个参数单独一个;第 17 个参数只会得到「Handler<_, _>is not satisfied」。- 前 n-1 个参数要
FromRequestParts<S>,共享&mut Parts;最后一个要FromRequest<S, M>,拿走整个请求。 call从左到右顺序.await提取器,任何一个失败就把 rejection 变成响应返回,后面的提取器和函数都不执行。- 函数 handler 的 future 是装箱的;
IntoResponse值作 handler 用Ready,Layered每个请求现场layer再oneshot。 HandlerService的poll_ready恒为Ready,call每次克隆 handler 和状态,把Response包成Result<_, Infallible>。