跳转到正文
zeno's blog
返回

axum(一):设计哲学-类型驱动的零成本 Web 框架

专题: axum

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>。这意味着:

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 其他框架

维度axumactix-webwarprocket
性能接近顶级(差 actix-web 10-15%)最快与 axum 相当良好
路由方式函数调用宏注解Filter 组合子宏注解
中间件tower::Layer 原生自有 Transform traitFilterFairing
类型安全编译期全覆盖部分运行时编译期(错误难读)部分运行时
unsafe#![forbid]使用(性能优化)极少极少
async 运行时tokio 专属actix-rt / tokiotokiotokio
生态84k dependents,快速增长最大最成熟萎缩中中等
背后组织tokio 项目(官方)社区驱动seanmonstar(hyper 作者)个人

版本演进

版本时间关键变化
0.12021.07首次发布
0.62022.11State<T> 取代 ExtensionFromRequestPartsFromRequest 分离
0.72023.11hyper 1.0 支持;axum::serve 取代 hyper::Server;自有 Body 类型
0.82024路径语法 /:id/{id};handler 要求 Sync;WebSocket over HTTP/2
0.8.92026.04最新稳定版,MSRV: Rust 1.80

分享这篇文章:

上一篇
axum(二):Handler 与 Extractor-编译期请求解析的魔法
下一篇
微服务(四):No-mock 趋势与测试策略