from_fn 与它的兄弟
主线位置:两条主线都在。装配主线上,
from_fn(f)只是一个存着f的Layer,Router::layer把它套到每个Route外面(layer 与 route_layer)。请求主线上,FromFn::call先像 handler 一样跑提取器,再把内层服务装箱成Next,交给你的函数。
sequenceDiagram participant R as 外层 Route participant F as FromFn participant X as 提取器 participant U as 你的函数 participant N as Next participant I as 内层服务 R->>F: call(req) F->>F: 换出就绪的 inner F->>X: 依次提取,失败即返回 F->>N: 装箱 inner F->>U: f(提取结果, req, next) U->>N: next.run(req) N->>I: call(req) I-->>U: Response U-->>R: into_response
axum::middleware 里有四个把函数或类型变成中间件的入口:from_fn、map_request、map_response、from_extractor,各有一个 _with_state 版本。前三个的源码结构几乎一样:一个存着函数和状态的 Layer,一个按参数个数用宏展开的 Service 实现(from_fn、map_request 用 all_the_tuples! 展开 1–16 个,map_response 手写了 0–16 个提取器的 17 行 impl_service!)。from_extractor 没有函数,只有一个实现。这一页以 from_fn 为主,其余三个只讲不同的地方。
FromFn 是什么
from_fn 就是状态为 () 的 from_fn_with_state:
pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
from_fn_with_state((), f)
}
FromFnLayer 存 f、state 和一个 PhantomData<fn() -> T>,T 是参数类型的元组,作用和 Handler trait 里的标记类型一样:同一个函数类型对不同参数列表有不同的 Service 实现。Layer::layer 把它们连同内层服务放进 FromFn。from_fn 本身是 0.4.6 从 axum-extra 搬进来的(#719,CHANGELOG:「This previously lived in axum-extra but has been moved to axum」)。
FromFn::call:提取器、Next、你的函数
impl_service! 宏为 Next 之前 1–16 个参数的情形各生成一个 Service<Request> 实现。约束和 call:
impl<F, Fut, Out, S, I, $($ty,)* $last> Service<Request> for FromFn<F, S, I, ($($ty,)* $last,)>
where
F: FnMut($($ty,)* $last, Next) -> Fut + Clone + Send + 'static,
$( $ty: FromRequestParts<S> + Send, )*
$last: FromRequest<S> + Send,
Fut: Future<Output = Out> + Send + 'static,
Out: IntoResponse + 'static,
I: Service<Request, Error = Infallible>
// ...
fn call(&mut self, req: Request) -> Self::Future {
let not_ready_inner = self.inner.clone();
let ready_inner = std::mem::replace(&mut self.inner, not_ready_inner);
let mut f = self.f.clone();
let state = self.state.clone();
let (mut parts, body) = req.into_parts();
let future = 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(),
};
let inner = BoxCloneSyncService::new(MapIntoResponse::new(ready_inner));
let next = Next { inner };
f($($ty,)* $last, next).await.into_response()
});
逐段看:
mem::replace换出就绪的那个 inner。poll_ready问的是self.inner,call把这个已就绪的实例拿走,留一个克隆在原处。这是 tower 里在call中克隆内层的标准写法,原因见 tower 卷 poll_ready must be called first。- 提取器和 handler 一模一样。 函数的参数是
(T1, ..., Tn, last, Next):前面的要FromRequestParts<S>,Next前面那一个要FromRequest<S>,通常就是Request。这段展开和impl_handler!生成的call(Handler trait)逐行相同,提取失败就把 rejection 变成响应返回,你的函数和内层服务都不会被调用。 Next是每个请求现做的。 就绪的 inner 包进MapIntoResponse,再BoxCloneSyncService::new装箱。I是这一层包住的服务:在Router::layer下是一个Route,在ServiceBuilder里可能是下一层中间件。- 整个过程是一个
Box::pin的 future。FromFn的Future类型是ResponseFuture,里面就是这个BoxFuture,Error是Infallible。
下面的中间件在 Request 前面放了 Query<Lang> 和 HeaderMap:
axum = "=0.8.9"
serde = { version = "1", features = ["derive"] }
tower = { version = "=0.5.3", features = ["util"] }
tokio = { version = "=1.54.1", features = ["macros", "rt"] }
use std::sync::atomic::{AtomicUsize, Ordering};
use axum::body::Body;
use axum::extract::{FromRequestParts, Query, Request, State};
use axum::http::{request::Parts, HeaderMap, HeaderValue, StatusCode};
use axum::middleware::{from_extractor, from_fn, from_fn_with_state, map_request, map_response, Next};
use axum::response::Response;
use axum::routing::get;
use axum::Router;
use serde::Deserialize;
use tower::ServiceExt;
#[derive(Deserialize)]
struct Lang {
lang: String,
}
// Extractors first, then Request, then Next.
async fn with_extractors(Query(q): Query<Lang>, headers: HeaderMap, req: Request, next: Next) -> Response {
println!(" mw: lang={} ua={:?}", q.lang, headers.get("user-agent"));
next.run(req).await
}
#[derive(Clone)]
struct Limits {
max_len: usize,
}
// State given to from_fn_with_state, independent of the Router's state.
async fn check_len(State(limits): State<Limits>, req: Request, next: Next) -> Result<Response, StatusCode> {
if req.uri().path().len() > limits.max_len {
return Err(StatusCode::URI_TOO_LONG);
}
Ok(next.run(req).await)
}
// map_request: Ok(req) goes on, Err(e) becomes the response.
async fn require_token(req: Request) -> Result<Request, StatusCode> {
if req.headers().contains_key("token") { Ok(req) } else { Err(StatusCode::UNAUTHORIZED) }
}
async fn add_header(mut res: Response) -> Response {
res.headers_mut().insert("x-mw", HeaderValue::from_static("map_response"));
res
}
static AUTH_RUNS: AtomicUsize = AtomicUsize::new(0);
struct Auth;
impl<S: Send + Sync> FromRequestParts<S> for Auth {
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
AUTH_RUNS.fetch_add(1, Ordering::SeqCst);
match parts.headers.get("token") {
Some(v) if v == "secret" => Ok(Auth),
_ => Err((StatusCode::UNAUTHORIZED, "bad token")),
}
}
}
async fn handler() -> &'static str {
println!(" handler");
"ok"
}
async fn handler_with_auth(_auth: Auth) -> &'static str {
println!(" handler");
"ok"
}
async fn send(app: &Router, uri: &str, token: Option<&str>) {
let mut b = Request::get(uri).header("user-agent", "demo");
if let Some(t) = token {
b = b.header("token", t);
}
let res = app.clone().oneshot(b.body(Body::empty()).unwrap()).await.unwrap();
let status = res.status();
let x = res.headers().get("x-mw").cloned();
let body = axum::body::to_bytes(res.into_body(), usize::MAX).await.unwrap();
println!(" {uri} token={token:?} -> {} x-mw={x:?} body={body:?}", status.as_u16());
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
println!("[from_fn with Query + HeaderMap]");
let app = Router::new().route("/", get(handler)).layer(from_fn(with_extractors));
send(&app, "/?lang=zh", None).await;
send(&app, "/", None).await;
println!("[from_fn_with_state]");
let app = Router::new()
.route("/{*rest}", get(handler))
.layer(from_fn_with_state(Limits { max_len: 8 }, check_len));
send(&app, "/short", None).await;
send(&app, "/much-too-long", None).await;
println!("[map_request + map_response]");
let app = Router::new()
.route("/", get(handler))
.layer(map_request(require_token))
.layer(map_response(add_header));
send(&app, "/", Some("x")).await;
send(&app, "/", None).await;
println!("[from_extractor::<Auth>, handler also takes Auth]");
let app = Router::new()
.route("/", get(handler_with_auth))
.route_layer(from_extractor::<Auth>());
for token in [Some("secret"), Some("wrong")] {
AUTH_RUNS.store(0, Ordering::SeqCst);
send(&app, "/", token).await;
println!(" Auth::from_request_parts ran {} time(s)", AUTH_RUNS.load(Ordering::SeqCst));
}
}
后面几节分别用到程序的各段。M2 Max、rustc 1.99.0,连跑 3 次输出相同,第一段:
[from_fn with Query + HeaderMap]
mw: lang=zh ua=Some("demo")
handler
/?lang=zh token=None -> 200 x-mw=None body=b"ok"
/ token=None -> 400 x-mw=None body=b"Failed to deserialize query string: missing field `lang`"
第二个请求没有 lang,Query 的 rejection 直接成了 400,中间件函数没打印,handler 也没跑。中间件里的提取器失败和 handler 里的一样,状态码见 Path、Query 与请求元数据。
(req, next)#1088(首次发布于 axum-v0.6.0-rc.1,正式版 0.6.0)让 from_fn 的函数可以在前面放提取器。PR 描述:「Lately we've had a few people ask about how run extractors from middleware. This was previously possible by building your own RequestParts and then running the extractor. But this adds support to middleware::from_fn to just support extractors directly. I think that is quite clean and doesn't require any new API.」在那之前,中间件要自己从请求造一个 RequestParts 再调提取器。合并时提取器还是基于 RequestParts 的;同在 0.6.0-rc.1 发布的 #1272 引入 FromRequestParts、删掉了 RequestParts,FromFn::call 里那段 $ty::from_request_parts(&mut parts, &state) 才成了今天的样子。
Request 必须是 Next 前面那一个。把它放到 HeaderMap 前面:
async fn mw(req: Request, _headers: HeaderMap, next: Next) -> Response {
next.run(req).await
}
报错只说 FromFn<...> 没有实现 Service(rustc 1.99.0,两条相同错误中的第一条,long-type 文件路径的那行注释已删):
error[E0277]: the trait bound `FromFn<fn(Request<Body>, _, Next) -> _ {mw}, (), Route, _>: Service<_>` is not satisfied
--> src/bin/req_not_last.rs:8:77
|
8 | let _app: Router = Router::new().route("/", get(|| async { "ok" })).layer(from_fn(mw));
| ----- ^^^^^^^^^^^ unsatisfied trait bound
| |
| required by a bound introduced by this call
|
= help: the trait `tower_service::Service<axum::http::Request<Body>>` is not implemented for `FromFn<fn(Request<Body>, HeaderMap, Next) -> _ {mw}, (), Route, _>`
= help: the following other types implement trait `tower_service::Service<Request>`:
axum::middleware::FromFn<F, S, I, (T1, T2)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7, T8)>
axum::middleware::FromFn<F, S, I, (T1, T2, T3, T4, T5, T6, T7, T8, T9)>
and 8 others
note: required by a bound in `Router::<S>::layer`
--> axum-0.8.9/src/routing/mod.rs:306:21
|
303 | pub fn layer<L>(self, layer: L) -> Router<S>
| ----- required by a bound in this associated function
...
306 | L::Service: Service<Request> + Clone + Send + Sync + 'static,
| ^^^^^^^^^^^^^^^^ required by this bound in `Router::<S>::layer`
和 handler 的 #[debug_handler] 对应,axum-macros 有 #[debug_middleware](要开 macros feature;文档写明它在 release 编译时不起作用,所以用 cargo build 看)。加在 mw 上,第一条错误变成:
error: `Request<_>` consumes the request body and thus must be the last argument to the handler function
--> src/bin/req_not_last_dbg.rs:4:18
|
4 | async fn mw(req: Request, _headers: HeaderMap, next: Next) -> Response {
| ^^^^^^^
措辞沿用了 handler 的(「to the handler function」),意思一样:Next 之前的最后一个参数才能消费 body。
Next:一个装箱的服务
Next 只有一个字段:
#[derive(Debug, Clone)]
pub struct Next {
inner: BoxCloneSyncService<Request, Response, Infallible>,
}
impl Next {
/// Execute the remaining middleware stack.
pub async fn run(mut self, req: Request) -> Response {
match self.inner.call(req).await {
Ok(res) => res,
Err(err) => match err {},
}
}
}
- 类型里没有泛型,所以中间件函数的签名能写成固定的
Next。代价是装箱:BoxCloneSyncService是 tower 的类型擦除(类型擦除),每个请求BoxCloneSyncService::new一次,run里调用时 future 再装箱一次。 run拿走self,调用一次就没了。Next实现了Clone和Service<Request>(impl Service<Request> for Next,#1712 加的),所以可以克隆后再调,或者把它当成普通 tower 服务交给别的组件。run直接.call,没有先poll_ready:交给Next的是FromFn::call里换出来的那个已就绪的 inner。
每层 from_fn 每个请求要多付几次堆分配?用一个计数的全局分配器,比较几种挂法。先 with_state(())(axum::serve 也会这么做,见 一个请求穿过 axum),预热一次,再发 10 000 个请求取平均。请求在计数前就造好了,不算在内:
axum = "=0.8.9"
tower = { version = "=0.5.3", features = ["util"] }
tokio = { version = "=1.54.1", features = ["macros", "rt"] }
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use axum::body::Body;
use axum::extract::Request;
use axum::middleware::{from_fn, map_request, Next};
use axum::response::Response;
use axum::routing::get;
use axum::Router;
use tower::{ServiceBuilder, ServiceExt};
use tower::util::MapRequestLayer;
struct Counting;
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: Counting = Counting;
async fn pass(req: Request, next: Next) -> Response {
next.run(req).await
}
async fn pass_map(req: Request) -> Request {
req
}
fn ident(req: Request) -> Request {
req
}
async fn measure(name: &str, app: Router) {
let app = app.with_state(()); // turn handlers into Route up front
let req = || Request::get("/").body(Body::empty()).unwrap();
app.clone().oneshot(req()).await.unwrap(); // warm up
const N: usize = 10_000;
let mut reqs: Vec<_> = (0..N).map(|_| req()).collect();
let before = ALLOCS.load(Ordering::Relaxed);
for r in reqs.drain(..) {
let res = app.clone().oneshot(r).await.unwrap();
drop(res);
}
let allocs = (ALLOCS.load(Ordering::Relaxed) - before) as f64 / N as f64;
println!("{name:<26} allocs/req = {allocs:.1}");
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let base = || Router::new().route("/", get(|| async { "ok" }));
measure("no middleware", base()).await;
measure("tower MapRequestLayer", base().layer(MapRequestLayer::new(ident))).await;
measure("map_request", base().layer(map_request(pass_map))).await;
measure("from_fn x1", base().layer(from_fn(pass))).await;
measure("from_fn x2", base().layer(from_fn(pass)).layer(from_fn(pass))).await;
measure("from_fn x3", base().layer(from_fn(pass)).layer(from_fn(pass)).layer(from_fn(pass))).await;
measure("MapRequestLayer x2", base().layer(MapRequestLayer::new(ident)).layer(MapRequestLayer::new(ident))).await;
measure("MapRequestLayer x3", base().layer(MapRequestLayer::new(ident)).layer(MapRequestLayer::new(ident)).layer(MapRequestLayer::new(ident))).await;
let sb = ServiceBuilder::new()
.layer(MapRequestLayer::new(ident))
.layer(MapRequestLayer::new(ident))
.layer(MapRequestLayer::new(ident));
measure("ServiceBuilder MapReq x3", base().layer(sb)).await;
let sb = ServiceBuilder::new().layer(from_fn(pass)).layer(from_fn(pass)).layer(from_fn(pass));
measure("ServiceBuilder from_fn x3", base().layer(sb)).await;
}
连跑 3 次输出相同:
no middleware allocs/req = 11.0
tower MapRequestLayer allocs/req = 13.0
map_request allocs/req = 14.0
from_fn x1 allocs/req = 16.0
from_fn x2 allocs/req = 23.0
from_fn x3 allocs/req = 32.0
MapRequestLayer x2 allocs/req = 16.0
MapRequestLayer x3 allocs/req = 20.0
ServiceBuilder MapReq x3 allocs/req = 13.0
ServiceBuilder from_fn x3 allocs/req = 22.0
两个因素叠在一起:
- 中间件自己的分配。 三个都放进一个
ServiceBuilder、只调用一次Router::layer时,Route的层数不变,差别只来自中间件本身:tower 的MapRequest一次也不分配(tower 卷 service_fn 与 map 一族:「它们的 future 都不装箱」),3 层还是 13;from_fn3 层是 22,每层多 3 次。map_request比MapRequestLayer多 1 次,是它的Box::pin。from_fn的分配在上面那段call里都能看到:Box::pin的 future、给Next装箱的BoxCloneSyncService::new、Next::run调用时装箱的 future、克隆内层服务。 - 每次
Router::layer多一层Route。Route::layer是Route::new(layer.layer(self)),每调一次.layer就多一层BoxCloneSyncService。Route被调用时会克隆自己的盒子(oneshot_inner里的self.0.clone()),克隆一个外层服务又会连带克隆它里面的Route,层数越多,每次克隆要复制的盒子越多。MapRequestLayer分三次.layer,增量是 2、3、4;from_fn分三次是 5、7、9。axum 仓库的 issue #3913(2026-10 提出,尚未关闭)报告的就是这个:「Every level then clones the rest of the stack again when it calls the next one, so the number of clones per request grows faster than the number of layers」,提议把Route里的盒子放进Arc。
实用的结论和 axum 文档推荐的写法一致:一组中间件放进一个 ServiceBuilder,只调用一次 Router::layer。3 层 from_fn 从 32 次降到 22 次。
from_fn_with_state:中间件自己的状态
from_fn_with_state(state, f) 里的 state 存在 FromFnLayer 上,Layer::layer 时克隆一份给每个 FromFn,call 时再克隆一份给提取器用(上面 call 里的 let state = self.state.clone();)。它和 Router 的状态没有关系:Router::with_state 不会把状态传给中间件,要用就自己再传一份,通常是 from_fn_with_state(app_state.clone(), mw)。中间件函数里的 State<T> 和 handler 里一样经 FromRef 从这份状态里取(状态:State 与 FromRef)。
第二段,Limits 只给中间件用,路由器本身没有状态:
[from_fn_with_state]
handler
/short token=None -> 200 x-mw=None body=b"ok"
/much-too-long token=None -> 414 x-mw=None body=b""
中间件函数返回 Result<Response, StatusCode>:Out 只要求 IntoResponse,Result<T, E> 在 T、E 都实现 IntoResponse 时就满足,Err 那一支就是提前结束。
map_request 与 map_response
map_request 的函数不拿 Next,只返回请求。返回值的约束是 IntoMapRequestResult,一个封闭的 trait,只有两个实现:
impl<B, E> IntoMapRequestResult<B> for Result<Request<B>, E>
where
E: IntoResponse,
{
fn into_map_request_result(self) -> Result<Request<B>, Response> {
self.map_err(IntoResponse::into_response)
}
}
impl<B> IntoMapRequestResult<B> for Request<B> {
fn into_map_request_result(self) -> Result<Request<B>, Response> {
Ok(self)
}
}
call 里拿到 Ok(req) 就调用内层,Err(res) 就直接返回这个响应:
match f($($ty,)* $last).await.into_map_request_result() {
Ok(req) => {
ready_inner.call(req).await.into_response()
}
Err(res) => {
res
}
}
所以 map_request 能提前结束,写鉴权、限流这类「放行或拒绝」的逻辑比 from_fn 少一个参数。它在 Box::pin 之外不装箱 Next,上一节的分配也比 from_fn 少。
map_response 的函数最后一个参数是 Response<ResBody>,前面可以有 FromRequestParts 提取器。它的 call 先跑提取器,再调内层,最后把响应交给你:
match ready_inner.call(req).await {
Ok(res) => {
f($($ty,)* res).await.into_response()
函数被调用时内层已经执行完,它能改响应、换响应,但不能阻止请求进入内层。返回 Result 也可以编译(Result 实现了 IntoResponse),只是 Err 也只是换了一个响应。提取器在内层执行之前就跑完了,提取失败同样直接返回 rejection,内层不会被调用。
第三段,.layer(map_request(require_token)).layer(map_response(add_header)),map_response 后写,在外层:
[map_request + map_response]
handler
/ token=Some("x") -> 200 x-mw=Some("map_response") body=b"ok"
/ token=None -> 401 x-mw=Some("map_response") body=b""
401 来自 map_request 的 Err(StatusCode::UNAUTHORIZED),它也带着 x-mw:外层的 map_response 照样拿到了这个响应。顺序反过来写,401 就不会有这个头(tower 卷 层的顺序反了 的复现)。
from_extractor:把提取器当中间件
from_extractor::<E>() 没有函数,只有一个类型参数。它的 Service 实现 要求 E: FromRequestParts<S>,不能是消费 body 的提取器。call 先装好一个提取的 future,响应 future 是一个两态的状态机:
fn call(&mut self, req: Request<B>) -> Self::Future {
let state = self.state.clone();
let (mut parts, body) = req.into_parts();
let extract_future = Box::pin(async move {
let extracted = E::from_request_parts(&mut parts, &state).await;
let req = Request::from_parts(parts, body);
(req, extracted)
});
ResponseFuture {
state: State::Extracting {
future: extract_future,
},
svc: Some(self.inner.clone()),
}
}
ResponseFuture::poll 在 Extracting 状态里:提取成功(Ok(_))就调用内层、转到 Call 状态;失败就把 rejection into_response() 返回。注意 Ok(_):提取出来的值被丢掉了,handler 拿不到。另外两点:
- 它的
Error是内层的T::Error,不像其他三个固定为Infallible,也不要求内层的错误是Infallible。 - 调用的是
self.inner.clone()出来的副本,没有用mem::replace换出就绪的那个。内层是Route时无所谓(Route::poll_ready永远就绪),内层是需要就绪的 tower 服务时,就会碰到 poll_ready must be called first 那个问题。
axum 文档对它的定位是:「You have a type that you sometimes want to use as an extractor and sometimes as a middleware. If you only need your type as a middleware prefer middleware::from_fn.」
/ 用 route_layer(from_extractor::<Auth>()) 保护,handler 自己也要一个 Auth 参数。带正确 token 时 Auth::from_request_parts 跑几次?带错误 token 呢?答案在本页最后。什么时候写 from_fn
axum 文档 middleware.md 的「Writing middleware」 列了四档写法。按本页看到的代价整理:
| 写法 | 能用提取器 | 中间件自身每个请求的分配 | 能给别的 tower 栈用 |
|---|---|---|---|
from_fn / from_fn_with_state | 能 | Box::pin 的 future、Next 的盒子和它的 future | 不能:要求内层 Error = Infallible、请求是 axum 的 Request |
map_request / map_response | 能 | Box::pin 的 future | 同上 |
from_extractor | 只有那一个提取器 | 提取 future 的 Box::pin | 内层错误类型不限,但提取器是 axum 的 trait;提取结果丢掉 |
tower ServiceBuilder::map_request 等 | 不能 | 无 | 能 |
手写 Service + Layer | 不能(自己从 Parts 取) | 由你决定 | 能,可以发布成 crate |
文档对 from_fn 的建议是:不想自己写 future、并且「You don't intend to publish your middleware as a crate for others to use. Middleware written like this are only compatible with axum」。在乎分配次数时,先看上一节的数字:把多次 .layer 合并进一个 ServiceBuilder,3 层 from_fn 省下的 10 次,比 from_fn 和 MapRequestLayer 之间每层 3 次的差距大。
动脑筋答案
程序最后一段的输出(连跑 3 次相同):
[from_extractor::<Auth>, handler also takes Auth]
handler
/ token=Some("secret") -> 200 x-mw=None body=b"ok"
Auth::from_request_parts ran 2 time(s)
/ token=Some("wrong") -> 401 x-mw=None body=b"bad token"
Auth::from_request_parts ran 1 time(s)
- 带正确 token:2 次。
from_extractor跑一次,成功后把结果丢掉(Ok(_));请求进入 handler,Auth作为参数又提取一次。中间件和 handler 之间没有共享这个值。 - 带错误 token:1 次。 中间件里的那次就失败了,rejection
(401, "bad token")直接返回,handler 没被调用。
所以文档说「sometimes as an extractor and sometimes as a middleware」:同一个类型在一些路由上当中间件挡住请求,在另一些路由上当参数取值。两处都用就要付两次的代价;提取器里如果有查数据库这样的操作,把结果放进 extensions、让 handler 用 Extension<T> 取,或者改用 from_fn 自己插。
小结
from_fn(f)是状态为()的from_fn_with_state;FromFn::call用mem::replace换出就绪的内层,跑提取器,把内层装箱成Next,再调用f。- 中间件函数的参数规则和 handler 一样:前面
FromRequestParts,Next前一个FromRequest(通常是Request),提取失败直接返回 rejection。参数写错时#[debug_middleware](debug 编译)给出可读的报错。 Next是BoxCloneSyncService<Request, Response, Infallible>,每个请求新装一次;实测一层from_fn比 towerMapRequestLayer每个请求多 3 次分配,分多次.layer还会因为Route层层嵌套而越来越多(issue #3913)。把中间件合进一个ServiceBuilder再.layer一次。from_fn_with_state的状态与Router的状态无关,要自己传。map_request返回Result<Request, E>可以提前结束;map_response在内层之后执行,不能挡请求。from_extractor只接受FromRequestParts提取器,提取结果被丢弃,handler 再用同一个提取器会再跑一次。