Axum 是 Tokio 生态里很受欢迎的 Web 框架,类型安全、性能好,和 tower 中间件配合也很自然。本文用一个最小可运行的 API 项目,说明日常开发中最常用的几个概念。
最小项目结构
src/
main.rs # 入口,组装路由
routes/
mod.rs # 路由注册
handlers.rs
state.rs # 共享状态
Cargo.toml 核心依赖:
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
第一个 Handler
use axum::{routing::get, Json, Router};
use serde_json::{json, Value};
async fn health() -> Json<Value> {
Json(json!({ "status": "ok" }))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/health", get(health));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
访问 GET /health 应返回 JSON。生产环境记得把绑定地址和端口做成配置项。
共享状态:AppState
多个 Handler 需要访问数据库、配置或缓存时,用 Arc 包一层状态:
use std::sync::Arc;
use axum::extract::State;
#[derive(Clone)]
struct AppState {
db: Arc<DbPool>,
}
async fn list_items(State(st): State<AppState>) -> Json<Value> {
let rows = st.db.query_all().await;
Json(json!({ "items": rows }))
}
let state = AppState { db: Arc::new(pool) };
let app = Router::new()
.route("/items", get(list_items))
.with_state(state);
State 提取器会在每个请求里克隆一份 AppState(内部通常是 Arc,克隆成本低)。
路径参数与查询参数
use axum::extract::{Path, Query};
use serde::Deserialize;
async fn get_post(Path(slug): Path<String>) -> Json<Value> {
Json(json!({ "slug": slug }))
}
#[derive(Deserialize)]
struct PageQuery {
page: Option<u32>,
q: Option<String>,
}
async fn search(Query(q): Query<PageQuery>) -> Json<Value> {
Json(json!({ "page": q.page.unwrap_or(1), "q": q.q }))
}
路径参数用 Path,查询字符串用 Query + serde 反序列化,和写普通结构体一样直观。
统一错误响应
不要把 unwrap 散落在 Handler 里。可以定义业务错误类型,实现 IntoResponse:
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
enum ApiError {
NotFound,
BadRequest(String),
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, msg) = match self {
ApiError::NotFound => (StatusCode::NOT_FOUND, "资源不存在"),
ApiError::BadRequest(ref s) => (StatusCode::BAD_REQUEST, s.as_str()),
};
(status, Json(json!({ "status": "error", "message": msg }))).into_response()
}
}
Handler 返回 Result<Json<Value>, ApiError>,成功走 Ok,失败自动转成对应 HTTP 状态码。
中间件:鉴权与日志
Axum 基于 Tower,常用写法:
use axum::middleware;
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/public", get(public_handler))
.nest(
"/admin",
Router::new()
.route("/posts", get(admin_posts))
.layer(middleware::from_fn(require_auth)),
)
.layer(TraceLayer::new_for_http());
公开路由和需登录路由用 nest 分组,中间件只挂在需要的子树上,结构清晰。
小结
| 场景 | 推荐做法 |
|---|---|
| JSON API | Json<T> + serde |
| 共享资源 | State<AppState> + Arc |
| 错误处理 | 自定义 IntoResponse |
| 鉴权 / 日志 | tower 中间件分层挂载 |
Axum 的学习曲线比脚本语言框架陡一点,但编译期能拦住大量低级错误。个人爬虫、博客、小工具的后端,用 Rust + Axum 往往比「Python 脚本 + 临时 HTTP 库」更省心——尤其是要长期挂着跑的时候。
评论