关于 axum
Axum 是面向 Rust 开发者的 Web 框架,适用于构建 HTTP API,提供类型化提取器、Tower 中间件组合和可测试的路由器。
主要功能
- 01类型化请求提取器
- 02可组合的 Tower 中间件
- 03结构化 HTTP 错误
- 04优雅关闭
- 05无需套接字的路由测试
适用场景
- 01构建类型化 HTTP API 路由
- 02在处理器间共享应用状态
- 03添加跟踪、超时和 CORS
- 04无需套接字测试请求响应
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill axum安装后在 Claude Code 中输入/axum确认已加载。
附带文件metadata.json
详细介绍
Axum(Rust)- 生产级 Web API
概述
Axum 是一个基于 Hyper 和 Tower 构建的 Rust Web 框架。使用它可以实现类型安全的请求处理、可组合的中间件、结构化错误和出色的可测试性。
快速开始
最小服务器
✅ 正确:类型化处理函数 + JSON 响应
use axum::{routing::get, Json, Router};
use serde::Serialize;
use std::net::SocketAddr;
#[derive(Serialize)]
struct Health {
status: &'static str,
}
async fn health() -> Json<Health> {
Json(Health { status: "ok" })
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/health", get(health));
let addr: SocketAddr = "0.0.0.0:3000".parse().unwrap();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
❌ 错误:阻塞异步运行时
async fn handler() {
std::thread::sleep(std::time::Duration::from_secs(1)); // blocks executor
}
核心概念
路由器 + 处理函数
处理函数是返回实现了 IntoResponse 的内容的异步函数。
✅ 正确:路由嵌套
use axum::{routing::get, Router};
fn router() -> Router {
let api = Router::new()
.route("/users", get(list_users))
.route("/users/:id", get(get_user));
Router::new().nest("/api/v1", api)
}
async fn list_users() -> &'static str { "[]" }
async fn get_user() -> &'static str { "{}" }
提取器
优先使用提取器在边界处进行解析和验证:
Path<T>:类型化路径参数Query<T>:查询字符串Json<T>:JSON 请求体State<T>:共享应用状态
✅ 正确:类型化路径 + JSON
use axum::{extract::Path, Json};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
email: String,
}
#[derive(Serialize)]
struct User {
id: String,
email: String,
}
async fn create_user(Json(body): Json<CreateUser>) -> Json<User> {
Json(User { id: "1".into(), email: body.email })
}
async fn get_user(Path(id): Path<String>) -> Json<User> {
Json(User { id, email: "[email protected]".into() })
}
依赖项与 Crate 结构
如果你的 crate 除了构建为二进制文件外,还会作为库发布,请隔离 HTTP 技术栈,避免增加库使用者的依赖负担。
✅ 正确:可选 HTTP 功能
[dependencies]
axum = { version = "0.7", optional = true }
tower-http = { version = "0.5", optional = true }
tokio = { version = "1", features = ["full"] }
[features]
default = ["http-server"]
http-server = ["axum", "tower-http"]
[[bin]]
name = "my-service"
required-features = ["http-server"]
这样:
- 库使用者(
cargo add my-lib)只会获得核心逻辑,不会引入 HTTP 开销。 - 二进制构建默认包含服务器:
cargo install my-crate可以按预期工作。 - 可显式选择不启用:将
cargo add my-crate --no-default-features用于库场景。
❌ 错误:无条件添加 HTTP 依赖项
# Never do this if the crate is also a library:
axum = "0.7"
tower-http = "0.5"
# Library users now pull in the entire web stack
生产环境模式
1)共享状态(数据库连接池、配置、客户端)
使用 State<Arc<AppState>>,并尽可能保持状态不可变。
✅ 正确:通过 Arc 传递 AppState
use axum::{extract::State, routing::get, Router};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
build_sha: &'static str,
}
async fn version(State(state): State<Arc<AppState>>) -> String {
state.build_sha.to_string()
}
fn app(state: Arc<AppState>) -> Router {
Router::new().route("/version", get(version)).with_state(state)
}
2)结构化错误处理(IntoResponse)
集中处理错误到 HTTP 状态码和 JSON 的映射。
✅ 正确:AppError 转换为响应
use axum::{http::StatusCode, response::IntoResponse, Json};
use serde::Serialize;
#[derive(Debug)]
enum AppError {
NotFound,
BadRequest(&'static str),
Internal,
}
#[derive(Serialize)]
struct ErrorBody {
error: &'static str,
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, msg) = match self {
AppError::NotFound => (StatusCode::NOT_FOUND, "not_found"),
AppError::BadRequest(_) => (StatusCode::BAD_REQUEST, "bad_request"),
AppError::Internal => (StatusCode::INTERNAL_SERVER_ERROR, "internal"),
};
(status, Json(ErrorBody { error: msg })).into_response()
}
}
3)中间件(Tower 层)
使用 tower-http 提供生产级层:追踪、超时、请求 ID、CORS。
✅ 正确:追踪 + 超时 + CORS
use axum::{routing::get, Router};
use std::time::Duration;
use tower::ServiceBuilder;
use tower_http::{
cors::{Any, CorsLayer},
timeout::TimeoutLayer,
trace::TraceLayer,
};
fn app() -> Router {
let layers = ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(TimeoutLayer::new(Duration::from_secs(10)))
.layer(CorsLayer::new().allow_origin(Any));
Router::new()
.route("/health", get(|| async { "ok" }))
.layer(layers)
}
4)优雅关闭
收到 SIGINT/SIGTERM 时终止,并等待正在处理的请求完成。
✅ 正确:使用 with_graceful_shutdown
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c().await.ok();
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.ok()
.and_then(|mut s| s.recv().await);
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {}
_ = terminate => {}
}
}
#[tokio::main]
async fn main() {
let app = app();
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}
运维:受监督环境中的优雅关闭
上面的信号处理属于应用层。在生产环境中,实际的终止时限由监督程序(systemd、launchd、容器编排器)控制。
systemd(Linux):在 .service 文件中设置 KillSignal=SIGTERM 和 TimeoutStopSec=120(或更高)。
- 默认的 90s 超时可能会在网络存储(EBS/EFS)上执行 fsync 时触发,从而截断写入。
- 120s 可为正在处理的 HTTP 请求留出完成时间,并为文件系统同步提供安全余量。
launchd(macOS):使用 launchctl bootout(发送 SIGTERM 并等待),而不是 launchctl kickstart -k(发送 SIGKILL,会截断正在进行的 I/O)。
客户端重新连接:让连接到该服务的 HTTP 客户端和 MCP 桥接程序实现指数退避(从 200ms 开始,最高为 30s),使短暂重启不会造成明显影响,也不会向上游级联传播错误。
测试
使用 tower::ServiceExt 无需套接字即可测试路由器。
✅ 正确:请求/响应测试
use axum::{body::Body, http::Request, Router};
use tower::ServiceExt;
#[tokio::test]
async fn health_returns_ok() {
let app: Router = super::app();
let res = app
.oneshot(Request::builder().uri("/health").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(res.status(), 200);
}
决策树
Axum 与其他 Rust 框架的比较
- 如果需要组合 Tower 中间件和类型化提取器,优先选择 Axum。
- 如果需要成熟的生态系统和 Actor 风格的运行时模型,优先选择 Actix Web。
- 如果需要函数式过滤器和极简设计,优先选择 Warp。
反模式
- 阻塞异步运行时(在处理函数中使用
std::thread::sleep或执行阻塞 I/O)。 - 在请求处理路径中使用
unwrap();应改为返回结构化错误。 - 不设置超时;应添加请求超时和上游截止时间。
资源
- Axum 文档:https://docs.rs/axum
- Tower HTTP 层:https://docs.rs/tower-http
- Tracing:https://docs.rs/tracing