源码地图
主线位置:不在主线上。本页说明两条主线经过的代码在哪个 crate、哪个目录,以及要打开哪个 feature 才能编译到它。
一个 axum 程序依赖的几个 crate(实线是 crate 之间的依赖,虚线是「通常由你自己加进来」):
flowchart TB app[你的程序] --> axum[axum] app -.-> thttp[tower-http] app -.-> macros["axum-macros:macros"] axum --> core[axum-core] axum --> matchit[matchit] axum --> tower[tower] axum --> hyper["hyper:http1 或 http2"] axum --> hutil["hyper-util:tokio"] core --> http[http 与 http-body] core --> tsvc[tower-service 与 tower-layer] thttp --> tsvc
几个 crate 各管什么
| crate | 版本 | 管什么 | src/ 行数 | 本卷 |
|---|---|---|---|---|
axum | 0.8.9 | Router、MethodRouter、Handler、内置提取器(Path、Query、Json、Form、State……)、from_fn 等中间件工具、axum::serve | 19 424 | 全卷 |
axum-core | 0.5.6 | 两个提取器 trait FromRequestParts / FromRequest、IntoResponse / IntoResponseParts、Body、DefaultBodyLimit、FromRef | 3 129 | 02、03 |
axum-macros | 0.5.1 | #[debug_handler]、#[debug_middleware],以及 FromRequest、FromRequestParts、FromRef、TypedPath 的派生宏 | 3 688 | 只讲 debug_handler 和 FromRequest 派生生成了什么 |
axum-extra | 0.12.6 | 不够通用、没放进 axum 的东西:cookie、typed header、更多提取器和响应类型 | 7 933 | 不讲 |
tower-http | 0.7.1 | 和框架无关的 HTTP 中间件:静态文件、压缩、超时、body 大小限制、CORS、Trace、CatchPanic 等 | 31 619 | 06,只讲最常用的几个 |
行数是各 tag 下 src/ 里 .rs 文件的总行数,含测试和注释,只用来估分量。axum 的 19 424 行里有 2 447 行是 routing/tests/;另外 src/docs/ 下还有 3 012 行 markdown,不算在内。
axum 自己不实现 HTTP,也不实现路由树:协议在 hyper,路径匹配在 matchit 0.8.4(Cargo.toml 里写死了 matchit = "=0.8.4",连补丁版本都不放开)。tower-http 不依赖 axum,它的 crate 文档说中间件只依赖 http 和 http-body 两个 crate,所以「compatible with any library or framework that also uses those crates」。
axum-core 为什么单独成 crate
axum-core 的 crate 文档只有一句话的定位:
Libraries authors that want to provide [`FromRequest`] or [`IntoResponse`] implementations
should depend on the [`axum-core`] crate, instead of `axum` if possible.
axum 的 crate 文档「Building integrations for axum」一节说得更具体:axum-core「contains core types and traits and is less likely to receive breaking changes」。
写一个提取器或者一个响应类型,只需要这几个 trait,不需要路由、不需要服务器。依赖 axum-core 而不是 axum,好处有两个:
- 依赖少。axum-core 0.5.6 的必选依赖只有 bytes、futures-core、http、http-body、http-body-util、mime、pin-project-lite、sync_wrapper、tower-layer、tower-service 十个,没有 hyper、没有 tokio、没有 matchit。
- 少跟着升级。axum 的路由、服务器改了要发破坏性版本,只要 trait 没变,axum-core 不用跟着升大版本,库也就不用跟着发版。axum-core 的
Cargo.toml还用cargo_check_external_types列出公开 API 里允许出现的外部类型(bytes、http、http_body、futures_core、tower_layer),防止不稳定的依赖漏进公开 API。
axum 把这些 trait 原样重新导出(axum::extract::FromRequest 就是 axum_core::extract::FromRequest),用 axum 写应用的人感觉不到这层拆分。
axum 0.3 之前,FromRequest 和 IntoResponse 都定义在 axum 里。issue #561(「Move FromRequest and IntoResponse into their own crate?」)指出,给 axum 做集成的库(maud、tower-cookies 等)大多只用这两个 trait,「These traits are somewhat more stable than the rest of axum」,可是 axum 一升版本,这些库就得跟着升,「to pull in changes that are essentially irrelevant, such as routing」。提议的做法是「Basically the same pattern as futures-core and tower-service」,并且对放进去的东西「be quite conservative」:「Ideally only what has to be moved should be moved.」
#564 照此拆出 axum-core,随 axum 0.4.0(2021-12-02)和 axum-core 0.1.0 发布。axum 的 CHANGELOG 写的是:「This crate is intended for library authors to depend on, rather than axum itself, if possible. axum-core has a smaller API and will thus receive fewer breaking changes.」代价也写在 PR 里:一些宏要在两个 crate 里各写一份,hyper::body::to_bytes 也被复制了一份到 axum-core,「Luckily it had no dependencies on hyper itself.」这就是今天 axum-core 不依赖 hyper 的由来。
axum 的 feature
[features]
default = [
"form",
"http1",
"json",
"matched-path",
"original-uri",
"query",
"tokio",
"tower-log",
"tracing",
]
form = ["dep:form_urlencoded", "dep:serde_urlencoded", "dep:serde_path_to_error"]
http1 = ["dep:hyper", "hyper?/http1", "hyper-util?/http1"]
http2 = ["dep:hyper", "hyper?/http2", "hyper-util?/http2"]
json = ["dep:serde_json", "dep:serde_path_to_error"]
macros = ["dep:axum-macros"]
matched-path = []
multipart = ["dep:multer"]
original-uri = []
query = ["dep:form_urlencoded", "dep:serde_urlencoded", "dep:serde_path_to_error"]
tokio = ["dep:hyper-util", "dep:tokio", "tokio/net", "tokio/rt", "tower/make", "tokio/macros"]
tower-log = ["tower/log"]
tracing = ["dep:tracing", "axum-core/tracing"]
ws = ["dep:hyper", "tokio", "dep:tokio-tungstenite", "dep:sha1", "dep:base64"]
默认开 9 个,手动开的 4 个是 http2、macros、multipart、ws。每个 feature 管到哪里:
| feature | 默认 | 打开了什么 | 本卷 |
|---|---|---|---|
http1 / http2 | 前者 | hyper 和 hyper-util 对应的协议;axum::serve 要 tokio 加两者之一才编译 | hyper 卷 |
tokio | 是 | axum::serve、extract::connect_info、SSE 的 keep_alive | 01、02、03 |
json | 是 | Json 提取器和响应 | 02、03 |
form / query | 是 | Form、Query 提取器 | 02 |
matched-path | 是 | 路由时把匹配到的路由模板存进 extensions,MatchedPath 提取器 | 02、04 |
original-uri | 是 | 路由前把原始 URI 存进 extensions,OriginalUri 提取器;嵌套的服务里 Uri 会被去掉前缀,原件从这里取 | 02、04 |
tracing | 是 | 内置提取器失败时用 tracing 记一条日志 | 02 |
tower-log | 是 | 只是打开 tower 的 log feature | — |
macros | 否 | 重新导出 axum-macros 的 debug_handler、debug_middleware | 10 |
multipart | 否 | Multipart 提取器(底下是 multer) | 02 |
ws | 否 | extract::ws,WebSocket 升级 | 07 |
matched-path 和 original-uri 是空 feature,不拉任何依赖,只是在 PathRouter::call_with_state 里打开两段 #[cfg] 代码:每个请求多往 extensions 里插一两个值。
crate 文档里也有一张 feature 表,但 tokio 那一行写的「Enables tokio as a dependency and axum::serve, SSE and extract::connect_info types」已经过时:0.8.5 的 CHANGELOG 记着「The sse module and Sse type no longer depend on the tokio feature」(#3154)。在 0.8.9 里 response/mod.rs 的 pub mod sse; 不挂任何 feature,只有 Sse::keep_alive 和它用的 KeepAliveStream 要 tokio(要用 tokio 的定时器)。
把默认 feature 全关掉,axum 就不依赖 hyper 和 tokio 了。cargo tree -e normal --depth 1 的直接依赖(M2 Max、rustc 1.99.0):
| 依赖写法 | axum 的直接依赖 |
|---|---|
axum = { version = "=0.8.9", default-features = false } | 17 个:axum-core、bytes、futures-util、http、http-body、http-body-util、itoa、matchit、memchr、mime、percent-encoding、pin-project-lite、serde_core、sync_wrapper、tower、tower-layer、tower-service |
axum = "=0.8.9" | 25 个:上面 17 个,加 form_urlencoded、hyper、hyper-util、serde_json、serde_path_to_error、serde_urlencoded、tokio、tracing |
不依赖 hyper 和 tokio 的 axum 仍然有完整的 Router、Handler、提取器,只是没有 axum::serve:Router<()> 本身就是一个 tower Service,可以用 oneshot 直接调用,也可以交给任何接受 tower 服务的服务器。下一部分的「一个请求穿过 axum」就是这样跑的。不过 crate 文档的「Compatibility」一节也说得很清楚:「axum is designed to work with tokio and hyper. Runtime and transport layer independence is not a goal, at least for the time being.」
src/ 的目录
下表的行数是 tag axum-v0.8.9 里各目录 .rs 文件的总行数,含测试和注释:
| 路径 | 行数 | 管什么 | 本卷 |
|---|---|---|---|
routing/ | 6 486 | Router、PathRouter(包着 matchit)、MethodRouter、Route、nest 用的 StripPrefix;其中 tests/ 2 447 行 | 04 |
extract/ | 5 804 | 内置提取器:Path(自带一个 serde Deserializer)、Query、State、MatchedPath、ConnectInfo、ws、multipart,以及各种 rejection | 02、07 |
middleware/ | 1 715 | from_fn、from_extractor、map_request、map_response | 05 |
response/ | 1 229 | Redirect、Html、NoContent、SSE;IntoResponse 本身在 axum-core | 03 |
serve/ | 895 | axum::serve、Listener trait、with_graceful_shutdown | 01(细节在 hyper 卷) |
handler/ | 702 | Handler trait 和它的宏实现、HandlerService、Handler::layer | 02 |
| 根目录的文件 | 1 883 | lib.rs(535 行,几乎全是文档)、json.rs、form.rs、extension.rs、boxed.rs(BoxedIntoRoute)等 | 02、04 |
test_helpers/ | 394 | 只在测试和 __private feature 下编译:起一个真实服务器、用 reqwest 发请求的 TestClient | 08 |
error_handling/ | 262 | HandleError、HandleErrorLayer:把返回错误的 tower 服务变成 Infallible | 05 |
body/ | 54 | 重新导出 axum-core 的 Body,加 to_bytes | 02 |
routing/ 和 extract/ 占了一半以上,这也是本卷篇幅的分布:02 讲提取器,04 讲路由。
src/docs/:一半的 API 文档在 markdown 里
src/docs/ 下有 21 个 .md 文件、3 012 行。它们不是给人直接读的,而是用 #[doc = include_str!(...)] 拼进 API 文档:整个 src/ 里有 23 处 include_str!。比如 Router::route 的文档:
#[doc = include_str!("../docs/routing/route.md")]
#[track_caller]
pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self {
handler/mod.rs 的模块文档也是两段 markdown 拼起来的,第二段 debugging_handler_type_errors.md 就是 docs.rs 上「Debugging handler type errors」那一节。读源码时看到一个方法上面只有一行 include_str!,文档在 src/docs/ 里对应的文件。这些 markdown 里的代码块和普通文档注释一样会被 cargo test --doc 编译运行,所以它们和代码是同步的。
axum-macros:只在 debug 下起作用
#[debug_handler] 的文档第一句是「Generates better error messages when applied to handler functions.」它不改变 handler 的行为,只是在函数旁边生成一串检查代码,让「参数不是提取器」「future 不是 Send」这类错误报在具体的参数上,而不是报成一句 Handler<_, _> 不满足。它的实现第一行就是:
pub fn debug_handler(_attr: TokenStream, input: TokenStream) -> TokenStream {
#[cfg(not(debug_assertions))]
return input;
release 构建时原样返回输入,什么也不生成。对比报错的例子放在常见坑。
MSRV
rust-version = "1.80",在 workspace 级别统一设置,axum、axum-core、axum-macros 都继承它;edition 2021。workspace 还对所有成员打开了 unsafe_code = "forbid",README 的「Safety」一节说的「This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust」就是这一行。
小结
- axum 管路由、handler、提取器和
serve;axum-core 只放提取器和响应的 trait,给库作者依赖;axum-macros 是可选的诊断与派生宏;axum-extra 不进本卷;tower-http 是与框架无关的中间件集合。 - axum-core 在 axum 0.4.0 拆出(#564),理由是这些 trait 比 axum 其余部分稳定,集成库不该因为路由改动而跟着升级;它不依赖 hyper 和 tokio。
- 默认开
form、http1、json、matched-path、original-uri、query、tokio、tower-log、tracing;http2、macros、multipart、ws要手动开。全关时 axum 不依赖 hyper 和 tokio,但没有axum::serve。 - crate 文档的 feature 表里「
tokio打开 SSE」已过时:0.8.5 起只有Sse::keep_alive需要tokio。 routing/和extract/占了src/的一半以上;src/docs/的 21 个 markdown 通过include_str!拼成 API 文档。#[debug_handler]在 release 下不生成任何代码。MSRV 1.80,全 workspace 禁用unsafe。