跳到主要内容

Axum 框架

问题

axum 是如何设计的?核心概念有哪些?

答案

axum 由 tokio 团队开发,设计哲学是利用类型系统和 Tower 生态,无宏、无中间语法。

完整示例

use axum::{
extract::{Path, Query, State, Json},
http::StatusCode,
response::IntoResponse,
routing::{get, post},
Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

// 应用状态
#[derive(Clone)]
struct AppState {
db: Arc<RwLock<Vec<User>>>,
}

#[derive(Serialize, Deserialize, Clone)]
struct User {
id: u64,
name: String,
}

#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}

// === Handler 函数 ===

// GET /users?page=1&per_page=10
async fn list_users(
State(state): State<AppState>, // 提取状态
Query(params): Query<Pagination>, // 提取查询参数
) -> Json<Vec<User>> {
let users = state.db.read().await;
Json(users.clone())
}

// GET /users/:id
async fn get_user(
State(state): State<AppState>,
Path(id): Path<u64>, // 提取路径参数
) -> Result<Json<User>, StatusCode> {
let users = state.db.read().await;
users
.iter()
.find(|u| u.id == id)
.cloned()
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}

// POST /users
async fn create_user(
State(state): State<AppState>,
Json(user): Json<User>, // 提取 JSON body
) -> impl IntoResponse {
state.db.write().await.push(user);
StatusCode::CREATED
}

#[tokio::main]
async fn main() {
let state = AppState {
db: Arc::new(RwLock::new(vec![])),
};

let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(get_user))
.with_state(state);

let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}

核心概念

概念说明
Router路由定义,支持嵌套
Handler异步函数,参数是提取器
Extractor从请求中提取数据(Path、Query、Json、State 等)
State应用共享状态
LayerTower 中间件
IntoResponse响应类型转换 trait

提取器(Extractor)

axum 的核心设计:函数参数即请求解析

提取器来源示例
Path<T>URL 路径/users/{id}
Query<T>查询参数?page=1
Json<T>请求体JSON body
State<T>共享状态数据库连接
Extension<T>扩展数据中间件注入
HeaderMap请求头所有 header

常见面试问题

Q1: axum 和 Express/Gin 有什么根本区别?

答案

axum 利用 Rust 类型系统实现编译时安全

  • 提取器参数类型不匹配 → 编译错误
  • 缺少 State → 编译错误
  • 响应类型不实现 IntoResponse → 编译错误

Express/Gin 的错误通常在运行时才发现(如忘记解析 body、类型转换失败)。

Q2: 如何组织大型 axum 项目的路由?

答案

// 按模块拆分路由
fn user_routes() -> Router<AppState> {
Router::new()
.route("/", get(list_users).post(create_user))
.route("/{id}", get(get_user).put(update_user).delete(delete_user))
}

fn auth_routes() -> Router<AppState> {
Router::new()
.route("/login", post(login))
.route("/register", post(register))
}

// 主路由合并
let app = Router::new()
.nest("/api/users", user_routes())
.nest("/api/auth", auth_routes())
.with_state(state);

相关链接