Appearance
前言
三行代码与一座冰山
你写了一个 Rust Web 服务。三行代码就跑起来了:
rust
let app = Router::new().route("/", get(handler));
let listener = TcpListener::bind("0.0.0.0:3000").await?;
serve(listener, app).await?;浏览器打开 http://localhost:3000,看到了响应。你觉得框架把一切都安排好了。
直到有一天,你想给 Path<T> 提取器返回一个自定义 JSON 错误而不是默认的 InvalidPath,发现 #[derive(FromRequest)] 的 rejection 参数需要你理解 FromRequestParts 与 FromRequest 的分裂。你想在一个 handler 里同时读 body 和读 header,编译器告诉你"body 已经被消费了"——因为 FromRequest 只能出现在最后一个参数位置。你想给 serve 加一段连接级别的逻辑(比如限流、证书校验),翻了半天文档发现 serve 是一个黑盒,要自定义就得自己写 accept 循环。你看到 Handler<T, S> 那个幽灵参数 T,grep 了一下发现它是一个零大小标记类型——但编译器就是靠它区分"这个 handler 接收几个参数、每个参数是什么类型"。
三行代码底下藏着一整座冰山。
为什么读源码
Rust Web 生态有一个鲜明的特点:框架不是银弹,抽象才是。
Axum 不是重新发明轮子的框架。它的 Router 用 matchit 做路径匹配;它的 handler 系统建立在 tower::Service 之上;它的 HTTP 服务端用 hyper-util 的 auto::Builder 驱动;它的 body 类型是 http_body::Body 的 type-erased 包装。Axum 做的事情是把这些已经成熟的抽象以一种极致符合人体工学的方式粘在一起——同时保留在任意层级向下拆解的能力。
这意味着两件事。
第一件,你必须理解底层才能用好框架。你在 Axum 里写的每一段 handler、每一个中间件、每一次 with_state,最终都会变成 tower::Service::call 的一次调用。Router<S> 到 Router<()> 的类型转换不是语法糖——它是类型状态模式在编译期的强制约束。from_fn 中间件的 Next::run(req) 不是"调用下一个 handler"——它是 BoxCloneSyncService<Request, Response, Infallible>::call(req) 的包装。理解这些,你才能在编译器报出一串 Handler<T, S> not implemented 的时候准确定位问题,而不是靠猜和试。
第二件,Axum 是 Rust 抽象设计的范本。整个 axum 工作区 35,000 行源码,拆成四个 crate:axum-core 定义核心 trait(FromRequest、IntoResponse),axum 实现路由和 handler,axum-macros 提供过程宏,axum-extra 放"不够通用"的扩展。这四个 crate 之间的依赖方向是单向的:axum-extra → axum → axum-core ← axum-macros。任何一个想写 axum 生态库的人,只需要依赖 axum-core,就能实现自己的提取器和响应类型,而不需要拉入整个 axum。这种 crate 边界的设计本身就是一堂工程课。
这本书不是什么
这本书不是 Axum 使用手册。 Router::new().route().get().post() 怎么写、Json<T> 怎么提取、State<S> 怎么注入——这些官方文档和 examples 已经覆盖得很好。本书的每一章都在回答"为什么这样设计"和"底层怎么实现",而不是"怎么用"。
这本书不是 Hyper / Tower 入门。 本书假设你已经读过《Hyper 与 Tower:工业级 HTTP 栈》,或者至少熟悉 Service / Layer / Body 这三个核心概念。第 5 章 Handler trait 会直接引用 Tower 的 Service trait,不会从零解释 poll_ready 和 call 的语义。
这本书不是 Rust 语言教程。 本书不会解释什么是 trait、什么是泛型、什么是生命周期。如果你对 impl Trait for Type、where T: Clone + Send + 'static、Pin<&mut Self> 这些还感到陌生,建议先补齐 Rust 基础再回来。
这本书是什么
这本书是 Axum 的一份"读码日记"。 每一章都像是你和我对着屏幕,一起读 axum/src/routing/method_routing.rs 的 1723 行、一起 grep impl Handler 看宏展开后的 16 个 tuple impl、一起 git blame 那个把 :param 改成 {param} 的 commit。2026 年 4 月锁定的 axum 0.8.9(commit de9f13d)是我们共同的地图——每一处源码引用都会标注 文件:行号,你可以随时在自己的 clone 里对照验证。
这本书是 Tower + Hyper 抽象在 Web 框架中落地的完整案例。 你在《Hyper 与 Tower》里学到的 Service / Layer / Body / MakeService,在 Axum 里有了具体的、面向业务的形态:Router<()> 实现了 Service<Request<B>>(routing/mod.rs:86);Handler<T, S> 通过 HandlerService 适配成 tower::Service(handler/service.rs:28);Serve 用 MakeService 模式为每个连接 clone 出一个 Router(serve/mod.rs:103)。读完这本书,"Tower 抽象如何落地"这个问题的答案会变成你脑子里的常识。
这本书是一份"类型驱动设计"的教材。 Axum 的核心设计几乎全部是用 Rust 类型系统完成的,而不是运行时检查:Router<S> 的类型状态模式、Handler<T, S> 的 coherence marker、FromRequest vs FromRequestParts 的 body 所有权分裂、IntoResponseParts 的元组级响应组合、Infallible 的错误消除。这些设计选择在编译期就排除了整类错误——你不可能忘记提供 state、不可能在非末位参数消费 body、不可能让错误逃逸到 hyper。读通之后,你会获得一种"用类型编码约束"的设计直觉。
读者画像
- Rust Web 开发者:天天写 Axum handler,想理解
#[debug_handler]报的那些错到底在说什么、State和Extension有什么区别、为什么 body 只能被消费一次。 - 框架/库作者:准备写自己的提取器、中间件、或 axum 生态库,想从源码层面理解
axum-core的 trait 设计约束——为什么FromRequestParts不能消费 body、为什么IntoResponseParts::into_response_parts返回Result而不是直接 panic。 - 性能工程师:在生产环境遇到路由匹配热点、中间件栈过深、连接泄漏、优雅关闭不干净,需要从框架源码找根因。
- 读过《Hyper 与 Tower》的读者:已经理解了
Service/Layer/Body心智模型,想看这些抽象在真实 Web 框架中如何落地。 - Rust 语言爱好者:对类型状态、零成本抽象、宏元编程在工业项目中的运用感兴趣——Axum 是这些技术的集大成案例。
前置知识
必须掌握:
- trait 与泛型:
impl Trait for Type、关联类型、where 子句。Handler 和提取器系统的设计完全建立在 trait 之上。 - 生命周期:
'staticbound 在异步代码中意味着什么。Handler<T, S>的Sized + 'static约束贯穿全书。 Future与Pin:知道async fn展开后是一个状态机。第 5 章 Handler trait 的 blanket impl 涉及复杂的 Future 类型推导。async/await语法:会写async fn,理解.await的语义。- Tower 基础:知道
Service/Layer/Layer::layer的基本含义。如果不熟悉,先读《Hyper 与 Tower》第 2-3 章。
最好掌握:
- Tokio 基础:
tokio::spawn、tokio::select!、TcpListener。第 15 章 serve 和第 16 章 listener 会直接读这些代码。 - HTTP 基础:请求方法、状态码、Content-Type、keep-alive。
- Hyper 基础:知道 hyper 是 Rust 的 HTTP 协议栈实现,axum 建立在它之上。
完全不需要:
- 能手写过程宏。
axum-macros的实现细节在第 19 章,但你不需要自己写过 proc macro。 - 网络协议实现经验。Axum 不直接处理协议,那是 Hyper 的事。
- Actix-web / Rocket 使用经验。本书不会和其他框架做对比入门,只在设计决策点偶尔对照。
本书的阅读路径
本书共 23 章(含前言),按七条主线展开。章节之间的依赖关系如下:
两条典型的阅读路径:
顺序阅读(推荐):从前言到第 22 章一路读下来。第 5 章 Handler trait 和第 6 章 FromRequest 是整本书的枢纽——如果没读它们就跳到中间件或 serve,会产生"为什么这里要这么写"的疑惑。
实战路径:第 1 → 2 → 5 → 6 → 7 → 9 → 13 → 15 → 22。这条路径先建立核心心智模型(路由 → handler → 提取器 → 响应 → 中间件 → serve),然后直接跳到生产实战。中间跳过的章节可以按需回看。
每一章的结构保持一致:
- 一个真实场景:从一行业务代码或一个编译错误切入。
- 问题背景:为什么这个问题不是显然的。
- 候选方案对比:其他语言/框架的做法、每种的 trade-off。
- Axum 的选择:直接对着源码读决定。
- 工程落地:怎么把这一章的理解用到你的日常工作里。
关于工具
本书在引用源码时会频繁用到以下命令:
bash
# 获取与本书一致的源码
git clone https://github.com/tokio-rs/axum.git
cd axum && git checkout de9f13d
# 展开宏(Handler 的 all_the_tuples! 宏展开后的代码才是真正的 impl)
cargo install cargo-expand
cargo expand --lib
# ripgrep 用来快速定位符号
cargo install ripgrepAxum 源码里大量使用 all_the_tuples! 宏来实现 1-16 个参数的 tuple impl,还有 pin_project! 来安全地投影 Pin<&mut Self>。cargo expand 是读懂这些代码的必备工具——第 5 章 Handler trait 会用展开后的代码来解释编译器如何匹配你的 handler 函数签名。
关于版本
本书所引用的源码,全部锁定于 2026 年 4 月 23 日的版本快照(axum 0.8.9 / axum-core 0.5.6 / axum-macros 0.5.1 / axum-extra 0.12.6,commit de9f13d)。Axum 是仍在积极演进的项目——2026 年 4 月刚刚加入了 Serve::with_executor 和 Executor trait(正是这个 commit),未来的版本可能会带来更多 API 变化。但本书强调的是设计决策和工程思路,这些内容在未来的版本里大概率依然成立。
好了,序言结束。翻到第 1 章——为什么需要 Axum——我们开始这场读码之旅。