
为什么选择 Axum 构建 Web API
Axum 将 Rust 的安全保证与符合人体工程学的 Web 原语相结合。它基于 Tokio 和 Tower 构建,为您提供可组合、高性能的 HTTP 服务器。
设置
# Cargo.toml
[dependencies]
axum = { version = "0.7", features = ["macros"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
基本应用
use axum::{
routing::{get, post},
Router,
Json,
extract::{Path, State},
http::StatusCode,
};
use std::sync::Arc;
#[tokio::main]
async fn main() {
tracing_subscriber::init();
let pool = sqlx::PgPool::connect(&std::env::var("DATABASE_URL").unwrap())
.await
.expect("Failed to connect to database");
let state = Arc::new(AppState { pool });
let app = Router::new()
.route("/api/users", get(list_users).post(create_user))
.route("/api/users/:id", get(get_user).put(update_user).delete(delete_user))
.with_state(state)
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
状态管理
#[derive(Clone)]
struct AppState {
pool: sqlx::PgPool,
}
// 通过 Arc 共享状态
type SharedState = Arc<AppState>;
使用提取器的处理器
use axum::extract::{Path, Query, State, Json};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct ListQuery {
page: Option<u32>,
per_page: Option<u32>,
}
#[derive(Serialize, sqlx::FromRow)]
struct User {
id: uuid::Uuid,
name: String,
email: String,
created_at: chrono::DateTime<chrono::Utc>,
}
async fn list_users(
State(state): State<SharedState>,
Query(params): Query<ListQuery>,
) -> Result<Json<Vec<User>>, AppError> {
let page = params.page.unwrap_or(1) as i64;
let per_page = params.per_page.unwrap_or(20) as i64;
let offset = (page - 1) * per_page;
let users = sqlx::query_as::<_, User>(
"SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2"
)
.bind(per_page)
.bind(offset)
.fetch_all(&state.pool)
.await?;
Ok(Json(users))
}
async fn get_user(
State(state): State<SharedState>,
Path(id): Path<uuid::Uuid>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as::<_, User>(
"SELECT id, name, email, created_at FROM users WHERE id = $1"
)
.bind(id)
.fetch_optional(&state.pool)
.await?
.ok_or(AppError::NotFound)?;
Ok(Json(user))
}
请求体验证
use axum::extract::rejection::JsonRejection;
use validator::Validate;
#[derive(Deserialize, Validate)]
struct CreateUserRequest {
#[validate(length(min = 2, max = 100))]
name: String,
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
async fn create_user(
State(state): State<SharedState>,
Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<User>), AppError> {
payload.validate().map_err(AppError::Validation)?;
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (id, name, email) VALUES ($1, $2, $3) RETURNING *"
)
.bind(uuid::Uuid::new_v4())
.bind(&payload.name)
.bind(&payload.email)
.fetch_one(&state.pool)
.await?;
Ok((StatusCode::CREATED, Json(user)))
}
错误处理
use axum::{
response::{IntoResponse, Response},
http::StatusCode,
Json,
};
#[derive(Debug)]
enum AppError {
NotFound,
Database(sqlx::Error),
Validation(validator::ValidationErrors),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound => (
StatusCode::NOT_FOUND,
"Resource not found".to_string(),
),
AppError::Database(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Database error: {e}"),
),
AppError::Validation(e) => (
StatusCode::UNPROCESSABLE_ENTITY,
format!("Validation error: {e}"),
),
};
(status, Json(serde_json::json!({ "error": message }))).into_response()
}
}
impl From<sqlx::Error> for AppError {
fn from(e: sqlx::Error) -> Self {
AppError::Database(e)
}
}
使用 Tower 的中间件
use tower_http::auth::RequireAuthorizationLayer;
use tower_http::trace::TraceLayer;
use axum::middleware::{self, Next};
async fn auth_middleware(
State(state): State<SharedState>,
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let token = req
.headers()
.get("Authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.ok_or(StatusCode::UNAUTHORIZED)?;
let claims = verify_jwt(token, &state.jwt_secret)
.map_err(|_| StatusCode::UNAUTHORIZED)?;
req.extensions_mut().insert(claims);
Ok(next.run(req).await)
}
// 应用到特定路由
let protected = Router::new()
.route("/api/profile", get(get_profile))
.route_layer(middleware::from_fn_with_state(state.clone(), auth_middleware));
测试
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt;
#[tokio::test]
async fn test_get_user_not_found() {
let app = create_test_app().await;
let response = app
.oneshot(
Request::builder()
.uri("/api/users/00000000-0000-0000-0000-000000000000")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
}
性能数据
Axum 在单台机器上经常基准测试达到 500k+ req/s —— 与 Go 的 Gin 类似,远超 Python/Node 在 CPU 密集型工作上的表现。