正在加载,请稍候…

Rust Axum Web 框架:构建快速安全的 REST API

使用 Rust Axum 框架构建生产级 REST API,结合 Tokio 和 Tower 实现高性能、类型安全的 Web 服务。

Rust Axum Web 框架:构建快速安全的 REST API

为什么选择 Axum 构建 Web API

Axum 将 Rust 的安全保证与符合人体工程学的 Web 原语相结合。它基于 Tokio 和 Tower 构建,为您提供可组合、高性能的 HTTP 服务器。

Rust Axum Web 框架:构建快速安全的 REST API 插图

设置

# 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>;

Rust Axum Web 框架:构建快速安全的 REST API 插图

使用提取器的处理器

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)
    }
}

Rust Axum Web 框架:构建快速安全的 REST API 插图

使用 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 密集型工作上的表现。