Table of contents
Open Table of contents
TL;DR
axum 是 tokio 官方的 Web 框架,由 David Pedersen 在 2021 年创建。它的核心设计是:不写自己的中间件系统,直接用 tower::Service;不用宏做路由,用函数调用;不用运行时反射做参数提取,用编译期 trait 解析。整个 crate #![forbid(unsafe_code)]。
为什么要有 axum:Rust Web 框架的空白
2021 年之前,Rust 的 Web 框架格局:
| 框架 | 问题 |
|---|---|
| actix-web | 性能极强,但绑定 actix actor 模型,自己的中间件系统与 tower 不兼容 |
| warp | 用 Filter 组合子做路由,概念优雅但类型错误信息极其难读 |
| rocket | 宏驱动路由(#[get("/")]),最初不支持 async,依赖代码生成 |
这三个框架有一个共同问题:各自实现中间件系统,互不兼容。你为 actix-web 写的中间件不能用在 warp 上,反之亦然。tower 生态中已有的限流、重试、超时、tracing 中间件全部浪费。
axum 的立场很明确:不发明新的中间件系统。
核心设计原则
1. tower 原生
axum 的 Router 实现 tower::Service<Request>。这意味着:
- tower-http 的所有中间件(Compression、CORS、Trace、Timeout)直接
.layer()上去 - 为 hyper 写的中间件能用在 axum 上
- 为 tonic(gRPC)写的中间件也能用在 axum 上
- 你的中间件投资不被框架锁定
2. 零宏路由
// axum:普通函数调用,IDE 能跳转、能重构
let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(get_user).delete(delete_user))
.nest("/api", api_router);
// 对比 rocket:宏注解,IDE 支持有限
#[get("/users/<id>")]
fn get_user(id: u32) -> Json<User> { ... }
axum 的路由是数据——你可以在运行时动态组合、条件添加、跨模块合并。宏路由做不到这些。
3. 编译期提取
Handler 的参数通过 trait(FromRequest / FromRequestParts)在编译期解析。如果提取类型不对、参数顺序错误、返回类型不满足 IntoResponse——编译器直接拒绝,不等到运行时 500 错误。
4. #![forbid(unsafe_code)]
整个 axum crate 不使用任何 unsafe 代码。所有性能关键路径(HTTP 解析、TLS、socket I/O)的 unsafe 下沉到 hyper 和 tokio 中。
架构层次
请求进入
│
▼
┌─────────────────────────────────┐
│ tokio (TcpListener::accept) │ ← 异步运行时 + TCP 接受
├─────────────────────────────────┤
│ hyper (HTTP 协议解析) │ ← HTTP/1.1 + HTTP/2 编解码
├─────────────────────────────────┤
│ tower middleware (.layer()) │ ← Trace / Compression / CORS / Auth
├─────────────────────────────────┤
│ axum Router (路由匹配) │ ← /users/{id} → handler
├─────────────────────────────────┤
│ axum Handler (提取 + 调用) │ ← FromRequest → async fn → IntoResponse
└─────────────────────────────────┘
│
▼
响应返回
Router:路由的核心
Router<S> 的泛型参数 S 代表待提供的状态类型。Router<AppState> 表示「这个 router 还需要一个 AppState 才能运行」。只有 Router<()> 可以传给 axum::serve()。
路由注册
Router::new()
// 静态路径
.route("/health", get(health_check))
// 路径参数(0.8 起用 {} 语法,不再是 :id)
.route("/users/{id}", get(get_user))
// 通配符(0.8 起用 {*path},不再是 *path)
.route("/files/{*path}", get(serve_file))
// 方法路由组合
.route("/items", get(list_items).post(create_item).delete(delete_all))
路由组合
// 嵌套:/api 前缀会被剥离后传给 api_router
let app = Router::new()
.nest("/api", api_router)
.nest("/admin", admin_router);
// 合并:两个 router 的路由合二为一(状态类型必须相同)
let app = public_routes.merge(authenticated_routes);
// 兜底:未匹配路径的处理器
let app = Router::new()
.route("/", get(root))
.fallback(not_found_handler);
// 方法不匹配的兜底(0.8 新增)
let app = Router::new()
.route("/users", get(list_users))
.method_not_allowed_fallback(method_not_allowed_handler);
State 管理:编译期 vs 运行时
旧方案(0.5):Extension(运行时)
// ❌ 旧方案:Extension 基于 TypeId 运行时查找
// 如果忘了添加 extension layer,运行时才 500 错误
let app = Router::new()
.route("/", get(handler))
.layer(Extension(db_pool)); // 忘了加这行?运行时炸
async fn handler(Extension(db): Extension<PgPool>) -> impl IntoResponse { ... }
新方案(0.6+):State(编译期)
// ✓ 新方案:State 通过 Router<S> 类型参数做编译期检查
#[derive(Clone)]
struct AppState {
db: PgPool,
redis: RedisPool,
}
let app = Router::new()
.route("/users", get(list_users))
.with_state(AppState { db, redis });
// 如果 handler 提取的 State 类型与 Router 的 S 不匹配 → 编译错误
async fn list_users(State(state): State<AppState>) -> impl IntoResponse { ... }
子状态(FromRef)
当 handler 只需要状态的一部分时,用 FromRef 派生子状态:
#[derive(Clone)]
struct AppState {
db: PgPool,
redis: RedisPool,
}
// 派生:从 AppState 中提取 PgPool
impl FromRef<AppState> for PgPool {
fn from_ref(state: &AppState) -> PgPool {
state.db.clone()
}
}
// handler 只声明它需要的部分,不需要知道完整的 AppState
async fn list_users(State(db): State<PgPool>) -> impl IntoResponse { ... }
axum vs 其他框架
| 维度 | axum | actix-web | warp | rocket |
|---|---|---|---|---|
| 性能 | 接近顶级(差 actix-web 10-15%) | 最快 | 与 axum 相当 | 良好 |
| 路由方式 | 函数调用 | 宏注解 | Filter 组合子 | 宏注解 |
| 中间件 | tower::Layer 原生 | 自有 Transform trait | Filter | Fairing |
| 类型安全 | 编译期全覆盖 | 部分运行时 | 编译期(错误难读) | 部分运行时 |
| unsafe | #![forbid] | 使用(性能优化) | 极少 | 极少 |
| async 运行时 | tokio 专属 | actix-rt / tokio | tokio | tokio |
| 生态 | 84k dependents,快速增长 | 最大最成熟 | 萎缩中 | 中等 |
| 背后组织 | tokio 项目(官方) | 社区驱动 | seanmonstar(hyper 作者) | 个人 |
版本演进
| 版本 | 时间 | 关键变化 |
|---|---|---|
| 0.1 | 2021.07 | 首次发布 |
| 0.6 | 2022.11 | State<T> 取代 Extension;FromRequestParts 从 FromRequest 分离 |
| 0.7 | 2023.11 | hyper 1.0 支持;axum::serve 取代 hyper::Server;自有 Body 类型 |
| 0.8 | 2024 | 路径语法 /:id → /{id};handler 要求 Sync;WebSocket over HTTP/2 |
| 0.8.9 | 2026.04 | 最新稳定版,MSRV: Rust 1.80 |