Rust in Everything
基于 axum 0.8.9

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超 traithandler 要被装箱存进 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 实际取的值:

implT为什么是这个形状
0 个参数的函数((),)宏要求至少有一个「最后一个参数」,0 个参数单独写
宏生成的 1–16 个参数(M, T1, ..., Tn)M 是最后一个提取器的 FromRequest<S, M> 标记,同样必须出现在 trait 里,否则又是 E0207
T: IntoResponseprivate::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),一个元组只占一个参数位置。

📝NOTE
冷知识:报错里的候选列表曾经全是元组

上面宏原文里每个 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 做四件事:

  1. req.into_parts() 把请求拆成 Parts(方法、URI、版本、头、extensions)和 body。
  2. 从左到右对前面的参数逐个 from_request_parts(&mut parts, &state).await。所有这些提取器共享同一个 &mut Parts,前一个写进 extensions 的东西后一个看得到。
  3. Request::from_parts(parts, body) 拼回一个完整请求,交给最后一个参数的 from_request,它拿到的是所有权,可以消费 body。
  4. 调用函数,.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 借给每个提取器。

❗IMPORTANT
动脑筋:第二个请求会打印几行提取日志?
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。

📝NOTE
没有蠢问题

问: 函数 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>。

💬 关联讨论

发起讨论
加载中...