状态:State 与 FromRef
主线位置:两条主线都有。装配主线的最后一步是
with_state:它把所有「欠着状态」的 handler 连同一份状态装成Route,Router<AppState>变成Router<()>,这时才能交给axum::serve。请求主线上,State<T>是一个普通的FromRequestParts提取器,从 handler 手里那份状态用FromRef取出一份T。
flowchart TB
subgraph build["启动时:装配"]
h["handler:State AppState"] --> mr["get:MethodRouter AppState"]
mr --> r1["Router AppState:欠状态"]
r1 --> ws["with_state(state)"]
ws --> route["端点各带一份 state"]
route --> r0["Router ():可 serve"]
end
subgraph perreq["每个请求"]
c1["Route clone:第 1 次"] --> c2["call:第 2 次"]
c2 --> c3["FromRef:第 3 次"]
end
r0 --> c1
State 本身只有几行。这一页的重点是它背后的类型参数:Router<S> 里的 S 在装配时怎么出现、怎么消失,以及状态在每个请求里被复制了几次。
Router<S>:S 是还欠着的状态
Router 的文档第一句就说明了 S 的含义:
/// The router type for composing handlers and services.
///
/// `Router<S>` means a router that is _missing_ a state of type `S` to be able
/// to handle requests. Thus, only `Router<()>` (i.e. without missing state) can
/// be passed to [`serve`]. See [`Router::with_state`] for more details.
///
/// [`serve`]: crate::serve()
#[must_use]
pub struct Router<S = ()> {
inner: Arc<RouterInner<S>>,
}
S 不是「路由器持有的状态」,而是「路由器还缺的状态」。它从 handler 的参数一路推出来。State 的提取器实现只对满足 FromRef 的外层状态成立:
impl<OuterState, InnerState> FromRequestParts<OuterState> for State<InnerState>
where
InnerState: FromRef<OuterState>,
OuterState: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(
_parts: &mut Parts,
state: &OuterState,
) -> Result<Self, Self::Rejection> {
let inner_state = InnerState::from_ref(state);
Ok(Self(inner_state))
}
}
async fn handler(State(s): State<AppState>) 要成为 Handler<T, S>,每个参数都得实现 FromRequestParts<S>(Handler trait 一章讲宏展开)。于是 get(handler) 推出 MethodRouter<AppState>,Router::route 要求 MethodRouter<S> 和自己的 S 一致,整个 Router 也就成了 Router<AppState>。
只有 Router<()> 实现了 tower Service,见 impl Service<Request<B>> for Router<()>:
impl<B> Service<Request<B>> for Router<()>
where
B: HttpBody<Data = bytes::Bytes> + Send + 'static,
B::Error: Into<axum_core::BoxError>,
{
// ...
fn call(&mut self, req: Request<B>) -> Self::Future {
let req = req.map(Body::new);
self.call_with_state(req, ())
}
}
axum::serve 要的 Service<IncomingStream> 也只给了 Router<()>。所以忘了 with_state 不会等到运行时才发现,编译就过不去:
axum = "=0.8.9"
tokio = { version = "=1.54.1", features = ["full"] }
use axum::{Router, extract::State, routing::get};
#[derive(Clone)]
struct AppState;
async fn handler(State(_s): State<AppState>) -> &'static str { "ok" }
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
rustc 1.99.0 的第一条错误(后面还有一条 Serve<...> is not a future,是同一个原因):
error[E0277]: the trait bound `for<'a> Router<AppState>: tower_service::Service<IncomingStream<'a, tokio::net::TcpListener>>` is not satisfied
--> src/bin/missing_state.rs:12:25
|
12 | axum::serve(listener, app).await.unwrap();
| ----------- ^^^ the trait `for<'a> tower_service::Service<IncomingStream<'a, tokio::net::TcpListener>>` is not implemented for `Router<AppState>`
| |
| required by a bound introduced by this call
|
help: `Router` implements trait `tower_service::Service<Request>`
--> axum-0.8.9/src/routing/mod.rs:549:5
|
549 | / impl<L> Service<serve::IncomingStream<'_, L>> for Router<()>
550 | | where
551 | | L: serve::Listener,
| |___________________________^ `tower_service::Service<IncomingStream<'_, L>>`
...
569 | / impl<B> Service<Request<B>> for Router<()>
570 | | where
571 | | B: HttpBody<Data = bytes::Bytes> + Send + 'static,
572 | | B::Error: Into<axum_core::BoxError>,
| |________________________________________^ `tower_service::Service<axum::http::Request<B>>`
报错里的关键是 Router<AppState>:类型参数直接写出了缺的是什么。如果路由在一个返回 Router(即 Router<()>)的函数里组装,错误更直白:
error[E0308]: mismatched types
--> src/bin/missing_state2.rs:13:3
|
12 | fn app() -> Router {
| ------ expected `Router` because of return type
13 | api()
| ^^^^^ expected `Router`, found `Router<AppState>`
|
= note: expected struct `Router<()>`
found struct `Router<AppState>`
给错了状态类型也一样在编译期暴露。handler 要 State<Db>、路由器却 with_state(AppState),而 Db 没有实现 FromRef<AppState>:
async fn handler(State(_db): State<Db>) -> &'static str { "ok" }
fn main() {
let _app: Router = Router::new()
.route("/", get(handler))
.with_state(AppState);
}
error[E0308]: mismatched types
--> src/bin/wrong_state.rs:14:17
|
14 | .with_state(AppState);
| ---------- ^^^^^^^^ expected `Db`, found `AppState`
| |
| arguments to this method are incorrect
|
note: method defined here
--> axum-0.8.9/src/routing/mod.rs:408:12
|
408 | pub fn with_state<S2>(self, state: S) -> Router<S2> {
| ^^^^^^^^^^
FromRef 只有 T: Clone 时 FromRef<T> for T 这一个实现可用,类型推断就把 S 定成了 Db,错误落在 with_state 的参数上。
with_state:把状态装进每个端点
Router::with_state 把状态分发给三处:
pub fn with_state<S2>(self, state: S) -> Router<S2> {
map_inner!(self, this => RouterInner {
path_router: this.path_router.with_state(state.clone()),
fallback_router: this.fallback_router.with_state(state.clone()),
default_fallback: this.default_fallback,
catch_all_fallback: this.catch_all_fallback.with_state(state),
})
}
PathRouter::with_state 给每个路由的 MethodRouter 一份 state.clone(),MethodRouter::with_state 再给每个方法槽一份。真正干活的是 MethodEndpoint::with_state:
fn with_state<S2>(self, state: &S) -> MethodEndpoint<S2, E> {
match self {
MethodEndpoint::None => MethodEndpoint::None,
MethodEndpoint::Route(route) => MethodEndpoint::Route(route),
MethodEndpoint::BoxedHandler(handler) => {
MethodEndpoint::Route(handler.into_route(state.clone()))
}
}
}
装配时,get(handler) 并不知道状态,只能先把 handler 擦成 BoxedIntoRoute<S, E>:一个「给我一份 S,我就变成 Route」的盒子。盒子里的转换函数在 BoxedIntoRoute::from_handler:
Self(Box::new(MakeErasedHandler {
handler,
into_route: |handler, state| Route::new(Handler::with_state(handler, state)),
}))
Handler::with_state 返回 HandlerService,就是 handler 和 state 两个字段;Route::new 再把它装进 BoxCloneSyncService(tower 卷 装箱)。with_state 之后,BoxedHandler 全部变成了 Route,S 从类型里消失。Route 的结构和装箱的代价在 route 与装箱 一章。
返回类型是 Router<S2> 而不是 Router<()>:S2 由调用方决定。这让状态不同的子路由可以先各自填好状态,再 nest 进去:
#[derive(Clone)]
struct AppState { name: &'static str }
#[derive(Clone)]
struct ApiState { version: u32 }
async fn root(State(s): State<AppState>) -> String { format!("app = {}", s.name) }
async fn api(State(s): State<ApiState>) -> String { format!("api v{}", s.version) }
#[tokio::main]
async fn main() {
// Router<ApiState> --with_state--> Router<AppState> (S2 inferred from .nest)
let api_router = Router::new()
.route("/info", get(api))
.with_state(ApiState { version: 2 });
let app: Router = Router::new()
.route("/", get(root))
.nest("/api", api_router)
.with_state(AppState { name: "demo" });
for uri in ["/", "/api/info"] {
let res = app.clone().oneshot(Request::get(uri).body(Body::empty()).unwrap()).await.unwrap();
let body = res.into_body().collect().await.unwrap().to_bytes();
println!("{uri:<10} {}", String::from_utf8_lossy(&body));
}
}
/ app = demo
/api/info api v2
api_router 的 with_state 返回值被 nest 推成 Router<AppState>:它已经不缺 ApiState 了,但要和外层一起「欠一个 AppState」。nest 要求两边 S 相同(pub fn nest(self, path: &str, router: Router<S>)),这正是 S2 的用处。
with_state 返回 Router<S2>,是 0.6.0 正式版前最后一刻改的0.6.0 的预发布版里,with_state 先是构造函数(Router::with_state(state).route(...)),rc.5 改成在末尾调用、产出一个单独的 RouterService 类型(#1532)。正式版前一天合并的 #1552 又改了一次:签名变成 pub fn with_state<S2>(self, state: S) -> Router<S2, B>,PR 的说法是「you get back a new Router and you get decide what state it needs」,由此可以 nest、merge 状态不同的路由器;同时「Router<()> now implements Service and RouterService has been removed」。PR 也写明了代价:没调用过 with_state 的路由器被直接 call 时,没有机会提前把端点转成 Route,「That means we need to do it for each request」。首次发布于 axum-v0.6.0。
这个代价今天还在。MethodRouter::call_with_state 碰到还没转换的端点,每个请求现做一次:
MethodEndpoint::BoxedHandler(handler) => {
let route = handler.clone().into_route(state);
return route.oneshot_inner_owned($req);
}
所以 with_state.md 建议不需要状态的路由器也调一次 .with_state(())。into_make_service 自己会调;直接 axum::serve(listener, router) 时,Service<IncomingStream> 的 call 每条连接执行一次 self.clone().with_state(()),也不会落到每请求转换。只有拿 Router<()> 直接当 Service 调用(例如测试里 oneshot)才会走这条慢路径。
FromRef:从总状态取子状态
State 只认一个状态类型,handler 想只拿其中一部分,靠 FromRef:
// NOTE: This trait is defined in axum-core, even though it is mainly used with `State` which is
// defined in axum. That allows crate authors to use it when implementing extractors.
pub trait FromRef<T> {
/// Converts to this type from a reference to the input type.
fn from_ref(input: &T) -> Self;
}
impl<T> FromRef<T> for T
where
T: Clone,
{
fn from_ref(input: &T) -> Self {
input.clone()
}
}
两点:
- 输入是引用,输出是值。
State<AppState>走的是下面那个 blanket 实现,就是一次clone。 - trait 放在 axum-core 而不是 axum,注释说明了原因:写提取器的库只依赖 axum-core 就能要求「用户的状态里能取出我的状态」(
MyLibraryState: FromRef<S>),state.rs的文档「For library authors」一节给了这个写法。axum-core 为什么单独成 crate 见 源码地图。
#[derive(FromRef)](要开 macros feature)为结构体的每个字段各生成一个实现。生成代码在 axum-macros 的 expand_field:
let body = if let Some(field_ident) = &field.ident {
if matches!(field_ty, Type::Reference(_)) {
quote_spanned! {span=> state.#field_ident }
} else {
quote_spanned! {span=> state.#field_ident.clone() }
}
} else {
// ...
quote_spanned! {span=> state.#idx.clone() }
};
quote_spanned! {span=>
#[allow(clippy::clone_on_copy, clippy::clone_on_ref_ptr)]
impl ::axum::extract::FromRef<#state> for #field_ty {
fn from_ref(state: &#state) -> Self {
#body
}
}
}
每个字段一句 state.field.clone()。由此有几条限制:结构体不能带泛型(expand 一开头就报 `#[derive(FromRef)]` doesn't support generics);两个字段类型相同会生成两个冲突的 impl,要给其中一个加 #[from_ref(skip)];字段类型必须是 Clone。
问: 既然 from_ref 拿到的是 &AppState,为什么不能直接写 State<&Db>,省掉一次 clone?
答: 提取器的返回值和状态的借用没有任何生命周期关联:FromRequestParts::from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, ...> 里的 Self 不能借用 state。原因在 handler 的调用方式上:Handler::call(self, req, state: S) 按值收下状态,把它移进一个 Pin<Box<dyn Future + Send>>,提取器拿到的 &state 只在这个 future 内部有效,而 Self 要能被移来移去、交给你的函数。想要便宜的「共享」,就让 clone 本身便宜:把大的字段包进 Arc。
每个请求 clone 几次
lib.rs 的「Sharing state with handlers」一节写的是「State is cloned for every request. Wrapping your state in Arc makes those clones cheap.」。到底几次,可以数出来:给状态写一个会计数的 Clone。
axum = "=0.8.9"
tokio = { version = "=1.54.1", features = ["full"] }
tower = { version = "=0.5.3", features = ["util"] }
use std::sync::atomic::{AtomicUsize, Ordering};
use axum::{Router, body::Body, extract::State, http::Request, routing::get};
use tower::ServiceExt;
static CLONES: AtomicUsize = AtomicUsize::new(0);
struct AppState;
impl Clone for AppState {
fn clone(&self) -> Self {
CLONES.fetch_add(1, Ordering::SeqCst);
AppState
}
}
async fn with_state(State(_s): State<AppState>) -> &'static str { "ok" }
async fn without_state() -> &'static str { "ok" }
fn report(label: &str) {
println!("{label:<28} clones = {}", CLONES.swap(0, Ordering::SeqCst));
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/a", get(with_state))
.route("/b", get(without_state).post(without_state))
.with_state(AppState);
report("with_state (2 routes)");
for path in ["/a", "/a", "/b"] {
let req = Request::get(path).body(Body::empty()).unwrap();
let res = app.clone().oneshot(req).await.unwrap();
report(&format!("GET {path} -> {}", res.status()));
}
}
app.clone() 只是 Arc 计数加一(Router 的 Clone 是 Arc::clone(&self.inner)),不碰状态。
with_state 那一行打印几次?GET /a 一次请求里 AppState::clone 被调用几次?GET /b 的 handler 根本不要状态,又是几次?M2 Max、rustc 1.99.0、--release。答案在本页最后。
State 与 Extension
在 0.6 之前,共享状态的标准做法是 Extension:用 .layer(Extension(value)) 把值放进每个请求的 http::Extensions,handler 再按类型取出来。两边的代码:
// the layer: AddExtension::call
fn call(&mut self, mut req: Request<ResBody>) -> Self::Future {
req.extensions_mut().insert(self.value.clone());
self.inner.call(req)
}
// the extractor
fn from_extensions(extensions: &Extensions) -> Option<Self> {
extensions.get::<T>().cloned().map(Extension)
}
(AddExtension::call、Extension::from_extensions。)Extensions 是一个以 TypeId 为键的 map,类型对不上、或者这个请求根本没经过那层 layer,都要到运行时取的那一刻才知道,结果是 MissingExtension,500。下面的 /after 在 .layer 之后才加,不在 layer 里(layer 只包已有的路由,见 layer 与 route_layer),照样编译通过:
use axum::{Extension, Router, body::Body, http::Request, routing::get};
use http_body_util::BodyExt;
use tower::ServiceExt;
#[derive(Clone)]
struct Db;
async fn handler(Extension(_db): Extension<Db>) -> &'static str { "ok" }
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/before", get(handler))
.layer(Extension(Db))
// added after .layer(): not wrapped by it
.route("/after", get(handler));
for uri in ["/before", "/after"] {
let req = Request::get(uri).body(Body::empty()).unwrap();
let res = app.clone().oneshot(req).await.unwrap();
let status = res.status();
let body = res.into_body().collect().await.unwrap().to_bytes();
println!("{uri:<8} {} {}", status.as_u16(), String::from_utf8_lossy(&body));
}
}
/before 200 ok
/after 500 Missing request extension: Extension of type `extension::Db` was not found. Perhaps you forgot to add it? See `axum::Extension`.
(依赖再加 http-body-util = "0.1"。)
State<T> | Extension<T> | |
|---|---|---|
| 值放在哪 | 每个 Route 里的 HandlerService.state | 每个请求的 Extensions,由 layer 每次插入 |
| 怎么找到 | FromRef,编译期确定 | 按 TypeId 查 map,运行时 |
| 缺了会怎样 | 编译错误(上一节) | 500 Missing request extension |
| 能不能每个请求不同 | 不能,with_state 时就定了 | 能,中间件可以按请求插入 |
最后一行是 Extension 留下来的理由。State 的文档原话:「State is global and used in every request a router with state receives. For accessing data derived from requests, such as authorization data, see [Extension].」认证中间件解析出的用户、请求 ID 这类每个请求不同的东西,仍然走 extensions;ConnectInfo 也是这样实现的,见 Path、Query 与请求元数据。
State 是 0.6 才有的,此前只有 Extension#1155(2022-08-17 合并,首次发布于 axum-v0.6.0-rc.1,正式版 axum-v0.6.0)引入了 State。PR 描述:「Attempting to extract states that haven't seen set results in compile errors, rather than runtime errors.」做法是给 MethodRouter、FromRequest、Handler 都加上状态类型参数:「All extractors just requires some generic state S but State fixes that to be some specific type.」这就是今天 FromRequestParts<S> 那个 S 的来历。CHANGELOG 里给出的迁移建议是:「We recommend migrating from Extension to State for sharing application state since that is more type safe and faster.」FromRef trait 也是这个 PR 加的(rc.1 的 CHANGELOG 已经用 impl FromRef<AppState> for HttpClient 演示子状态),#[derive(FromRef)] 晚一些,是 #1430,首次进入 axum-v0.6.0-rc.3。
动脑筋答案
连跑 3 次,输出相同:
with_state (2 routes) clones = 7
GET /a -> 200 OK clones = 3
GET /a -> 200 OK clones = 3
GET /b -> 200 OK clones = 2
装配时 7 次。 Router::with_state 给 path_router 和 fallback_router 各一份(2);PathRouter::with_state 给两个路由的 MethodRouter 各一份(2);MethodEndpoint::with_state 给每个非空方法槽一份:/a 的 GET,/b 的 GET 和 POST(3)。最后那份被移进 catch_all_fallback,默认 fallback 不需要状态,没有 clone。
GET /a 每次 3 次,分别在:
MethodRouter::call_with_state里的route.clone().oneshot_inner_owned($req)。Route是BoxCloneSyncService,克隆它就是克隆里面的HandlerService,而HandlerService的Clone是state: self.state.clone()。HandlerService::call:Handler::call(handler, req, self.state.clone()),因为Handler::call按值收状态。State<AppState>提取时InnerState::from_ref(state),走FromRef<T> for T的input.clone()。
GET /b 2 次:前两次和 handler 要不要状态无关,第三次只在有 State 参数时发生。
再看子状态。AppState 里放一个 Db,#[derive(FromRef)],handler 只要 State<Db>,两个类型各自计数(AppState 的 Clone 会顺带 clone 一次 db):
axum = { version = "=0.8.9", features = ["macros"] }
#[derive(FromRef)]
struct AppState {
db: Db,
#[from_ref(skip)]
_secret: String,
}
impl Clone for AppState {
fn clone(&self) -> Self {
APP.fetch_add(1, SeqCst);
AppState { db: self.db.clone(), _secret: self._secret.clone() }
}
}
async fn handler(State(_db): State<Db>) {}
一次请求(连跑 3 次相同):
one request: AppState::clone = 2, Db::clone = 3
State<Db> 并没有让整个 AppState 少 clone:前两次照样复制整个 AppState(连带复制两次 Db),第三次是生成的 from_ref 里的 state.db.clone()。子状态省掉的只是 handler 拿到的那一份。状态里如果有 String、Vec、HashMap 这类深拷贝的字段,每个请求就要复制两到三遍,这就是文档建议把状态包进 Arc(或让每个字段本身就是 Arc、连接池句柄这类廉价 clone 的类型)的原因。
小结
Router<S>的S是「还欠着的状态」,从 handler 的State<T>参数经FromRef推出来。只有Router<()>实现Service,忘了with_state是编译错误。with_state把每个BoxedHandler连同一份状态装成Route(HandlerService { handler, state }),返回Router<S2>,S2由调用方定,所以子路由可以先填自己的状态再nest。- 没调用
with_state就直接当Service用,每个请求都要现做into_route;into_make_service和axum::serve会替你提前做。 FromRef在 axum-core,from_ref(&T) -> Self;derive 为每个字段生成state.field.clone()。- 带
State的请求实测 clone 状态 3 次,不带的 2 次;状态要么本身廉价可 clone,要么包进Arc。 Extension运行时按类型查找,缺了是 500;适合每个请求不同的数据,应用级状态用State。