Axum 设计与实现

前言

作者 杨艺韬 · 2,590 字

前言

三行代码与一座冰山

你写了一个 Rust Web 服务。三行代码就跑起来了:

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 参数需要你理解 FromRequestPartsFromRequest 的分裂。你想在一个 handler 里同时读 body 和读 header,编译器告诉你"body 已经被消费了"——因为 FromRequest 只能出现在最后一个参数位置。你想给 serve 加一段连接级别的逻辑(比如限流、证书校验),翻了半天文档发现 serve 是一个黑盒,要自定义就得自己写 accept 循环。你看到 Handler<T, S> 那个幽灵参数 T,grep 了一下发现它是一个零大小标记类型——但编译器就是靠它区分"这个 handler 接收几个参数、每个参数是什么类型"。

三行代码底下藏着一整座冰山。

为什么读源码

Rust Web 生态有一个鲜明的特点:框架不是银弹,抽象才是

Axum 不是重新发明轮子的框架。它的 Routermatchit 做路径匹配;它的 handler 系统建立在 tower::Service 之上;它的 HTTP 服务端用 hyper-utilauto::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(FromRequestIntoResponse),axum 实现路由和 handler,axum-macros 提供过程宏,axum-extra 放"不够通用"的扩展。这四个 crate 之间的依赖方向是单向的:axum-extraaxumaxum-coreaxum-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_readycall 的语义。

这本书不是 Rust 语言教程。 本书不会解释什么是 trait、什么是泛型、什么是生命周期。如果你对 impl Trait for Typewhere T: Clone + Send + 'staticPin<&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::Servicehandler/service.rs:28);ServeMakeService 模式为每个连接 clone 出一个 Routerserve/mod.rs:103)。读完这本书,"Tower 抽象如何落地"这个问题的答案会变成你脑子里的常识。

这本书是一份"类型驱动设计"的教材。 Axum 的核心设计几乎全部是用 Rust 类型系统完成的,而不是运行时检查:Router<S> 的类型状态模式、Handler<T, S> 的 coherence marker、FromRequest vs FromRequestParts 的 body 所有权分裂、IntoResponseParts 的元组级响应组合、Infallible 的错误消除。这些设计选择在编译期就排除了整类错误——你不可能忘记提供 state、不可能在非末位参数消费 body、不可能让错误逃逸到 hyper。读通之后,你会获得一种"用类型编码约束"的设计直觉。

读者画像

前置知识

必须掌握:

最好掌握:

完全不需要:

本书的阅读路径

本书共 23 章(含前言),按七条主线展开。章节之间的依赖关系如下:

graph TD
    P[00 前言]
    C1[01 为什么需要 Axum]

    subgraph 路由系统
      C2[02 Router]
      C3[03 MethodRouter]
      C4[04 嵌套与合并]
    end

    subgraph Handler与提取器
      C5[05 Handler trait]
      C6[06 FromRequest]
      C7[07 内置提取器]
      C8[08 高级提取器]
    end

    subgraph 响应与错误
      C9[09 IntoResponse]
      C10[10 响应类型实战]
      C11[11 响应Parts]
      C12[12 错误处理]
    end

    subgraph 中间件
      C13[13 from_fn]
      C14[14 map系列]
    end

    subgraph 运行与状态
      C15[15 Serve]
      C16[16 Listener/Executor]
      C17[17 Body与流]
      C18[18 State/FromRef]
    end

    subgraph 元编程与扩展
      C19[19 axum-macros]
      C20[20 axum-extra]
    end

    subgraph 工程实践
      C21[21 测试]
      C22[22 生产实战]
    end

    P --> C1
    C1 --> C2 --> C3 --> C4
    C4 --> C5 --> C6 --> C7 --> C8
    C8 --> C9 --> C10 --> C11 --> C12
    C12 --> C13 --> C14
    C5 --> C15 --> C16
    C16 --> C17
    C7 --> C18
    C14 --> C19 --> C20
    C15 --> C21 --> C22

两条典型的阅读路径:

  1. 顺序阅读(推荐):从前言到第 22 章一路读下来。第 5 章 Handler trait 和第 6 章 FromRequest 是整本书的枢纽——如果没读它们就跳到中间件或 serve,会产生"为什么这里要这么写"的疑惑。

  2. 实战路径:第 1 → 2 → 5 → 6 → 7 → 9 → 13 → 15 → 22。这条路径先建立核心心智模型(路由 → handler → 提取器 → 响应 → 中间件 → serve),然后直接跳到生产实战。中间跳过的章节可以按需回看。

每一章的结构保持一致:

关于工具

本书在引用源码时会频繁用到以下命令:

# 获取与本书一致的源码
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 ripgrep

Axum 源码里大量使用 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_executorExecutor trait(正是这个 commit),未来的版本可能会带来更多 API 变化。但本书强调的是设计决策工程思路,这些内容在未来的版本里大概率依然成立。

好了,序言结束。翻到第 1 章——为什么需要 Axum——我们开始这场读码之旅。