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 | 应用共享状态 |
| Layer | Tower 中间件 |
| 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);