mcpField
A
axum-web-framework

axum-web-framework

github.com/manutej开发62651 次安装40 周前更新
代码仓库

关于 axum-web-framework

这项技能介绍如何使用 Rust Web 框架 Axum 构建 Web 应用和 API,面向异步 Rust 开发者。内容涵盖路由、类型安全的请求提取、中间件、状态管理和错误处理。

主要功能

  1. 01配置路由与嵌套路由器
  2. 02提取类型安全的请求数据
  3. 03管理共享应用状态
  4. 04组合 Tower 中间件
  5. 05将错误转换为 HTTP 响应

适用场景

  1. 01使用异步 Rust 构建 REST API
  2. 02添加身份验证或速率限制
  3. 03创建 WebSocket 或 SSE 服务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/manutej/luxor-claude-marketplace --skill axum-web-framework

安装后在 Claude Code 中输入/axum-web-framework确认已加载。

附带文件EXAMPLES.mdREADME.md

详细介绍

Axum Web Framework

一份全面的技能指南,介绍如何使用 Axum 构建可用于生产环境的 Web 应用和 API。Axum 是一个基于 Tokio 和 Tower 构建的简洁、模块化 Rust Web 框架。掌握路由、提取器、中间件、状态管理、错误处理和部署模式。

何时使用此技能

在以下情况下使用此技能:

  • 使用 Rust 和 async/await 构建 REST API
  • 创建兼具高性能和类型安全的 Web 服务
  • 借助 Tokio 生态系统集成开发微服务
  • 实现 WebSocket 服务端或服务器发送事件(SSE)
  • 使用 Rust 后端构建 GraphQL API
  • 创建需要集成 Tower 的中间件密集型应用
  • 开发需要健壮错误处理的生产级 Web 应用
  • 构建需要精细控制 HTTP 请求和响应处理的系统
  • 实现身份验证、授权和安全中间件
  • 使用异步 Rust 创建实时 Web 应用
  • 开发需要请求验证和转换的 API
  • 使用复杂路由和嵌套路由器构建 Web 服务
  • 实现速率限制、超时和背压处理
  • 创建需要自定义提取器和响应类型的 Web 应用

核心概念

Axum 架构理念

Axum 构建于三大基本支柱之上:

  1. Tower 服务:Axum 的所有功能都基于 Tower 的 Service trait 构建,支持组合和中间件集成
  2. 类型安全的提取器:请求数据提取在编译时进行检查,避免运行时解析错误
  3. 尽量减少样板代码:采用简洁的 API,减少繁琐代码,同时保持明确性
路由器

Router 是 Axum 的核心构建模块。它根据路径和方法将 HTTP 请求映射到处理器。

主要属性:

  • 按定义顺序匹配路由
  • 可嵌套路由器,实现模块化组织
  • 可在路由器、路由或方法级别应用中间件
  • 状态类型采用泛型,便于灵活管理状态
  • 实现 Tower 的 Service trait,支持组合

创建路由器:

use axum::{Router, routing::get};

let app = Router::new()
    .route("/", get(handler))
    .route("/users/:id", get(get_user))
    .route("/posts", get(list_posts).post(create_post));
处理器

处理器是用于处理请求并返回响应的异步函数。Axum 借助强大的类型系统支持多种处理器签名。

处理器要求:

  • 必须是异步函数
  • 可使用提取器从请求中提取数据
  • 必须返回实现了 IntoResponse 的类型
  • 最多可有 16 个参数(且都必须是提取器)

常见处理器模式:

// Simple handler
async fn handler() -> &'static str {
    "Hello, World!"
}

// Handler with path parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
    format!("User ID: {}", user_id)
}

// Handler with multiple extractors
async fn create_user(
    State(state): State<AppState>,
    Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
    // Implementation
}
提取器

提取器是实现了 FromRequest 或 FromRequestParts 的类型,可从请求中以类型安全的方式提取数据。

内置提取器:

  1. Path - 提取路径参数
  2. Query - 提取查询字符串参数
  3. Json - 解析 JSON 请求体
  4. Form - 解析表单编码的请求体
  5. State - 访问共享应用状态
  6. Extension - 访问请求扩展
  7. Headers - 访问请求标头
  8. Method - 获取 HTTP 方法
  9. Uri - 获取请求 URI
  10. Request - 获取完整请求
  11. Bytes - 将原始请求体作为字节获取
  12. String - 将请求体作为 UTF-8 字符串获取
  13. Multipart - 处理 multipart/form-data

提取器顺序:

  • 消耗请求体的提取器必须放在最后
  • 在同一处理器中使用多个请求体提取器会导致编译错误
  • State 等非请求体提取器可按任意顺序排列
响应

任何实现了 IntoResponse 的类型都可以从处理器返回。Axum 提供了多种内置实现。

内置响应类型:

  • String、&'static str - 文本响应
  • Json<T> - JSON 响应
  • Html<String> - HTML 响应
  • StatusCode - 仅包含状态码的响应
  • (StatusCode, T) - 包含状态码和响应体的响应
  • (Parts, T) - 包含自定义标头和响应体的响应
  • Response - 完全控制响应
  • Result<T, E> - 错误处理(其中 E: IntoResponse)
状态管理

Axum 使用 State 提取器在处理程序之间共享数据。状态必须实现 Clone,通常会用 Arc 包装以共享所有权。

状态模式:

  1. 简单状态:
#[derive(Clone)]
struct AppState {
    api_key: String,
}

let app = Router::new()
    .route("/", get(handler))
    .with_state(AppState {
        api_key: "secret".to_string(),
    });
  1. 使用 Arc 的共享状态:
#[derive(Clone)]
struct AppState {
    db_pool: Arc<DatabasePool>,
    cache: Arc<RwLock<Cache>>,
}
  1. 多种状态类型:
// Define separate state types for different router sections
let api_router: Router<ApiState> = Router::new()
    .route("/api/data", get(api_handler));

let app_router: Router<AppState> = Router::new()
    .route("/app", get(app_handler));

// Combine with final state
let app = Router::new()
    .nest("/", app_router.with_state(app_state))
    .nest("/", api_router.with_state(api_state));
中间件

Axum 中间件来自 Tower,可用于转换请求和响应、记录日志、身份验证等。

中间件类别:

  1. Tower 中间件 - 来自 tower 和 tower-http crate
  2. 自定义中间件 - 使用 middleware::from_fn
  3. 服务中间件 - 实现 Tower 的 Service trait
  4. Layer 模式 - 使用 Tower 的 Layer 实现可组合性

中间件应用顺序:

  • 使用 .layer() 应用时,按从下到上的顺序执行(包装之前的层)
  • 使用 ServiceBuilder 应用时,按从上到下的顺序执行(更直观)
  • Router::layer 上的中间件在路由之后运行
  • 包装在 Router 外的中间件(使用 Layer::layer)在路由之前运行
错误处理

Axum 的错误处理基于 IntoResponse trait,可将自定义错误类型转换为 HTTP 响应。

错误处理策略:

  1. Result 类型:
async fn handler() -> Result<Json<Data>, StatusCode> {
    // Returns 200 OK or error status code
}
  1. 自定义错误类型:
enum AppError {
    Database(sqlx::Error),
    NotFound,
    Unauthorized,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        // Convert to HTTP response
    }
}
  1. HandleErrorLayer:
use axum::error_handling::HandleErrorLayer;

let app = Router::new()
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_error))
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
    );
Tower 集成

Axum 基于 Tower 构建,可实现强大的中间件组合和服务抽象。

Tower 核心概念:

  1. Service Trait - 异步请求处理
  2. Layer Trait - 中间件工厂模式
  3. ServiceBuilder - 便捷的中间件组合
  4. Timeout - 请求超时处理
  5. RateLimit - 请求速率限制
  6. LoadShed - 背压管理
  7. Buffer - 请求缓冲

路由

基本路由

路由将 HTTP 方法和路径映射到处理程序:

use axum::{
    Router,
    routing::{get, post, put, delete, patch},
};

let app = Router::new()
    .route("/", get(root))
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
    .route("/posts/:id/comments", get(get_comments).post(add_comment));
路径参数

从路径中提取动态片段:

use axum::extract::Path;
use serde::Deserialize;

// Single parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
    format!("User {}", user_id)
}

// Multiple parameters
#[derive(Deserialize)]
struct PostPath {
    user_id: u32,
    post_id: u32,
}

async fn get_post(Path(params): Path<PostPath>) -> String {
    format!("User {} Post {}", params.user_id, params.post_id)
}

// Using tuple for multiple params
async fn get_comment(
    Path((post_id, comment_id)): Path<(u32, u32)>
) -> String {
    format!("Post {} Comment {}", post_id, comment_id)
}
通配符路由

捕获剩余的路径片段:

// Captures all remaining path
async fn handler(Path(path): Path<String>) -> String {
    format!("Captured path: {}", path)
}

let app = Router::new()
    .route("/{*key}", get(handler));

// GET /foo/bar/baz -> path = "foo/bar/baz"

重要: 嵌套路由器会剥离匹配的前缀,而通配符路由会保留完整的 URI。

嵌套路由器

使用路由器嵌套将路由组织到各个模块中:

use axum::{Router, routing::get};

fn api_routes() -> Router {
    Router::new()
        .route("/users", get(list_users))
        .route("/users/:id", get(get_user))
        .route("/posts", get(list_posts))
}

fn admin_routes() -> Router {
    Router::new()
        .route("/dashboard", get(dashboard))
        .route("/settings", get(settings))
}

let app = Router::new()
    .nest("/api", api_routes())
    .nest("/admin", admin_routes())
    .route("/", get(root));

嵌套行为:

  • 将匹配的前缀从 URI 中剥离,再传递给嵌套路由器
  • 嵌套路由器中的处理程序只能看到相对于嵌套点的路径
  • 如果子路由器未定义回退处理程序,则继承父级的回退处理程序
  • 可在嵌套之前或之后应用中间件
回退处理程序

处理未匹配的路由:

use axum::{http::StatusCode, handler::Handler};

async fn fallback() -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "Not Found")
}

let app = Router::new()
    .route("/", get(handler))
    .fallback(fallback);

回退继承:

async fn api_fallback() -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "API endpoint not found")
}

let api = Router::new()
    .route("/users", get(list_users))
    .fallback(api_fallback);

let app = Router::new()
    .nest("/api", api)
    .fallback(fallback); // Used for non-/api routes
方法路由

在同一路由上处理多种 HTTP 方法:

use axum::routing::{get, post, MethodRouter};

// Multiple methods on one route
let app = Router::new()
    .route("/users", get(list_users).post(create_user));

// Different handlers per method
let app = Router::new()
    .route("/resource",
        get(get_resource)
            .post(create_resource)
            .put(update_resource)
            .delete(delete_resource)
            .patch(patch_resource)
    );

深入了解提取器

Path 提取器

提取带类型的路径参数:

use axum::extract::Path;
use serde::Deserialize;

// Simple extraction
async fn user_by_id(Path(id): Path<u32>) -> String {
    format!("User {}", id)
}

// Complex extraction
#[derive(Deserialize)]
struct Params {
    org: String,
    repo: String,
    issue: u32,
}

async fn github_issue(Path(params): Path<Params>) -> String {
    format!("{}/{} issue #{}", params.org, params.repo, params.issue)
}

let app = Router::new()
    .route("/users/:id", get(user_by_id))
    .route("/repos/:org/:repo/issues/:issue", get(github_issue));
Query 提取器

提取查询字符串参数:

use axum::extract::Query;
use serde::Deserialize;

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    per_page: Option<u32>,
}

async fn list_users(Query(pagination): Query<Pagination>) -> String {
    let page = pagination.page.unwrap_or(1);
    let per_page = pagination.per_page.unwrap_or(20);
    format!("Page {} with {} items", page, per_page)
}

// GET /users?page=2&per_page=50
Json 提取器

解析 JSON 请求体:

use axum::extract::Json;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    username: String,
    email: String,
}

#[derive(Serialize)]
struct User {
    id: u32,
    username: String,
    email: String,
}

async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
    let user = User {
        id: 123,
        username: payload.username,
        email: payload.email,
    };
    Json(user)
}

JSON 错误处理:

use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;

async fn create_user(
    payload: Result<Json<CreateUser>, JsonRejection>
) -> Result<Json<User>, (StatusCode, String)> {
    match payload {
        Ok(Json(create_user)) => {
            // Valid JSON
            Ok(Json(User { /* ... */ }))
        }
        Err(JsonRejection::MissingJsonContentType(_)) => {
            Err((
                StatusCode::BAD_REQUEST,
                "Missing `Content-Type: application/json`".to_string(),
            ))
        }
        Err(JsonRejection::JsonDataError(err)) => {
            Err((
                StatusCode::BAD_REQUEST,
                format!("Invalid JSON: {}", err),
            ))
        }
        Err(JsonRejection::JsonSyntaxError(err)) => {
            Err((
                StatusCode::BAD_REQUEST,
                format!("JSON syntax error: {}", err),
            ))
        }
        Err(_) => {
            Err((
                StatusCode::INTERNAL_SERVER_ERROR,
                "Unknown error".to_string(),
            ))
        }
    }
}
State 提取器

访问共享的应用状态:

use axum::extract::State;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db_pool: Arc<DatabasePool>,
    api_key: String,
}

async fn handler(State(state): State<AppState>) -> String {
    format!("API Key: {}", state.api_key)
}

let state = AppState {
    db_pool: Arc::new(DatabasePool::new()),
    api_key: "secret".to_string(),
};

let app = Router::new()
    .route("/", get(handler))
    .with_state(state);
Extension 提取器

访问请求扩展(适用于中间件):

use axum::extract::Extension;

#[derive(Clone)]
struct CurrentUser {
    id: u32,
    username: String,
}

async fn handler(Extension(user): Extension<CurrentUser>) -> String {
    format!("Hello, {}", user.username)
}

// Set by middleware:
async fn auth_middleware(
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let user = CurrentUser {
        id: 1,
        username: "alice".to_string(),
    };
    req.extensions_mut().insert(user);
    Ok(next.run(req).await)
}
Form 提取器

解析表单编码的请求体:

use axum::extract::Form;
use serde::Deserialize;

#[derive(Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}

async fn login(Form(form): Form<LoginForm>) -> String {
    format!("Logging in user: {}", form.username)
}
Headers 提取器

访问请求标头:

use axum::http::HeaderMap;

async fn handler(headers: HeaderMap) -> String {
    let user_agent = headers
        .get("user-agent")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("unknown");
    format!("User-Agent: {}", user_agent)
}
自定义提取器

通过实现 FromRequest 或 FromRequestParts 创建自定义提取器:

use axum::{
    extract::{FromRequest, Request},
    response::{IntoResponse, Response},
    http::StatusCode,
    async_trait,
};

struct AuthenticatedUser {
    id: u32,
    username: String,
}

#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request_parts(
        parts: &mut Parts,
        state: &S,
    ) -> Result<Self, Self::Rejection> {
        // Extract and validate auth token
        let auth_header = parts
            .headers
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .ok_or_else(|| {
                (StatusCode::UNAUTHORIZED, "Missing authorization header")
                    .into_response()
            })?;

        // Validate token and return user
        Ok(AuthenticatedUser {
            id: 1,
            username: "alice".to_string(),
        })
    }
}

async fn protected_route(user: AuthenticatedUser) -> String {
    format!("Hello, {}", user.username)
}

中间件

应用中间件

应用中间件的三种方式:

  1. 路由器级别 - 影响所有路由
  2. 路由级别 - 影响指定路由
  3. 方法级别 - 影响路由上的指定方法
use axum::{Router, routing::get, middleware};
use tower_http::trace::TraceLayer;

// 1. Router-level
let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new_for_http());

// 2. Route-level
let app = Router::new()
    .route("/protected", get(handler))
    .route_layer(middleware::from_fn(auth_middleware));

// 3. Method-level
let app = Router::new()
    .route("/resource",
        get(handler)
            .route_layer(middleware::from_fn(read_only_auth))
            .post(create_handler)
            .route_layer(middleware::from_fn(write_auth))
    );
中间件执行顺序

使用连续的 .layer() 调用时(从下到上):

let app = Router::new()
    .route("/", get(handler))
    .layer(layer_three)  // Executes third
    .layer(layer_two)    // Executes second
    .layer(layer_one);   // Executes first

使用 ServiceBuilder 时(从上到下):

use tower::ServiceBuilder;

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(layer_one)    // Executes first
            .layer(layer_two)    // Executes second
            .layer(layer_three)  // Executes third
    );
常见 Tower 中间件

TraceLayer - HTTP 请求跟踪:

use tower_http::trace::TraceLayer;

let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new_for_http());

CompressionLayer - 响应压缩:

use tower_http::compression::CompressionLayer;

let app = Router::new()
    .route("/", get(handler))
    .layer(CompressionLayer::new());

CorsLayer - CORS 处理:

use tower_http::cors::{CorsLayer, Any};

let cors = CorsLayer::new()
    .allow_origin(Any)
    .allow_methods(Any)
    .allow_headers(Any);

let app = Router::new()
    .route("/api/data", get(handler))
    .layer(cors);

TimeoutLayer - 请求超时:

use tower::timeout::TimeoutLayer;
use std::time::Duration;

let app = Router::new()
    .route("/", get(handler))
    .layer(TimeoutLayer::new(Duration::from_secs(30)));
HandleErrorLayer

将中间件错误转换为 HTTP 响应:

use axum::{
    error_handling::HandleErrorLayer,
    http::{StatusCode, Method, Uri},
    BoxError,
};
use tower::ServiceBuilder;
use std::time::Duration;

async fn handle_timeout_error(
    method: Method,
    uri: Uri,
    err: BoxError,
) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (
            StatusCode::REQUEST_TIMEOUT,
            format!("`{} {}` request timed out", method, uri),
        )
    } else {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("`{} {}` failed with {}", method, uri, err),
        )
    }
}

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_timeout_error))
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
    );
使用 from_fn 自定义中间件

使用异步函数创建自定义中间件:

use axum::{
    middleware::{self, Next},
    extract::Request,
    response::Response,
    http::StatusCode,
};

async fn auth_middleware(
    req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get("authorization")
        .and_then(|h| h.to_str().ok());

    if let Some(auth_header) = auth_header {
        if validate_token(auth_header).await {
            Ok(next.run(req).await)
        } else {
            Err(StatusCode::UNAUTHORIZED)
        }
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

let app = Router::new()
    .route("/protected", get(handler))
    .layer(middleware::from_fn(auth_middleware));

将数据从中间件传递给处理器:

use axum::extract::Extension;

#[derive(Clone)]
struct CurrentUser {
    id: u32,
    username: String,
}

async fn auth_middleware(
    mut req: Request,
    next: Next,
) -> Result<Response, StatusCode> {
    let auth_header = req.headers()
        .get("authorization")
        .and_then(|h| h.to_str().ok())
        .ok_or(StatusCode::UNAUTHORIZED)?;

    if let Some(user) = authorize_user(auth_header).await {
        req.extensions_mut().insert(user);
        Ok(next.run(req).await)
    } else {
        Err(StatusCode::UNAUTHORIZED)
    }
}

async fn handler(Extension(user): Extension<CurrentUser>) -> String {
    format!("Hello, {}", user.username)
}

let app = Router::new()
    .route("/", get(handler))
    .layer(middleware::from_fn(auth_middleware));
自定义 Tower 中间件

实现 Tower 的 Service 和 Layer 特征,实现全面控制:

use tower::{Service, Layer};
use axum::{response::Response, extract::Request};
use std::task::{Context, Poll};
use futures_core::future::BoxFuture;

#[derive(Clone)]
struct MyLayer;

impl<S> Layer<S> for MyLayer {
    type Service = MyMiddleware<S>;

    fn layer(&self, inner: S) -> Self::Service {
        MyMiddleware { inner }
    }
}

#[derive(Clone)]
struct MyMiddleware<S> {
    inner: S,
}

impl<S> Service<Request> for MyMiddleware<S>
where
    S: Service<Request, Response = Response> + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, request: Request) -> Self::Future {
        // Process request
        let future = self.inner.call(request);
        Box::pin(async move {
            let response: Response = future.await?;
            // Process response
            Ok(response)
        })
    }
}

let app = Router::new()
    .route("/", get(handler))
    .layer(MyLayer);
可访问状态的中间件

创建可访问应用状态的中间件:

use axum::extract::State;
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

#[derive(Clone)]
struct MyLayer {
    state: AppState,
}

impl<S> Layer<S> for MyLayer {
    type Service = MyService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        MyService {
            inner,
            state: self.state.clone(),
        }
    }
}

#[derive(Clone)]
struct MyService<S> {
    inner: S,
    state: AppState,
}

impl<S> Service<Request> for MyService<S>
where
    S: Service<Request>,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request) -> Self::Future {
        // Use self.state here
        self.inner.call(req)
    }
}

let state = AppState {
    db: Arc::new(Database::new()),
};

let app = Router::new()
    .route("/", get(handler))
    .layer(MyLayer { state: state.clone() })
    .with_state(state);
路由前的中间件

在路由前应用中间件(例如,用于重写 URI):

use tower::Layer;
use axum::ServiceExt;

fn rewrite_request_uri(req: Request) -> Request {
    // Modify request URI
    req
}

let middleware = tower::util::MapRequestLayer::new(rewrite_request_uri);

let app = Router::new()
    .route("/", get(handler));

// Apply layer around entire router
let app_with_middleware = middleware.layer(app);

let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app_with_middleware.into_make_service()).await?;

状态管理

基本状态

使用可克隆类型的简单状态:

use axum::extract::State;

#[derive(Clone)]
struct AppState {
    config: Config,
    api_key: String,
}

async fn handler(State(state): State<AppState>) -> String {
    format!("Config: {:?}, Key: {}", state.config, state.api_key)
}

let state = AppState {
    config: Config::default(),
    api_key: "secret".to_string(),
};

let app = Router::new()
    .route("/", get(handler))
    .with_state(state);
使用 Arc 的共享状态

使用 Arc 共享不易克隆类型的所有权:

use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Clone)]
struct AppState {
    db_pool: Arc<DatabasePool>,
    cache: Arc<RwLock<HashMap<String, String>>>,
    config: Config, // Cheap to clone
}

async fn handler(State(state): State<AppState>) -> Result<String, StatusCode> {
    // Access database pool
    let conn = state.db_pool.get().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    // Access cache (read)
    let cache = state.cache.read().await;
    let value = cache.get("key");

    // Access cache (write)
    drop(cache); // Release read lock
    let mut cache = state.cache.write().await;
    cache.insert("new_key".to_string(), "value".to_string());

    Ok("Success".to_string())
}
多种状态类型

为不同的路由器部分使用不同的状态类型:

#[derive(Clone)]
struct ApiState {
    api_key: String,
}

#[derive(Clone)]
struct AppState {
    db: Arc<Database>,
}

fn api_routes() -> Router<ApiState> {
    Router::new()
        .route("/data", get(|State(state): State<ApiState>| async move {
            format!("API Key: {}", state.api_key)
        }))
}

fn app_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(|State(state): State<AppState>| async move {
            "Users".to_string()
        }))
}

let api_state = ApiState { api_key: "secret".to_string() };
let app_state = AppState { db: Arc::new(Database::new()) };

let app = Router::new()
    .nest("/api", api_routes().with_state(api_state))
    .nest("/app", app_routes().with_state(app_state));
函数中的泛型状态

返回具有泛型状态的路由器,以提高灵活性:

fn routes<S>() -> Router<S>
where
    S: Clone + Send + Sync + 'static,
{
    Router::new()
        .route("/health", get(|| async { "OK" }))
        .route("/version", get(|| async { "1.0.0" }))
}

// Can be combined with any state type
let app = Router::new()
    .merge(routes())
    .route("/", get(handler))
    .with_state(AppState { /* ... */ });
状态转换

串联具有不同状态要求的路由器:

#[derive(Clone)]
struct StateA {
    data_a: String,
}

#[derive(Clone)]
struct StateB {
    data_b: String,
}

let router_a: Router<StateA> = Router::new()
    .route("/a", get(|State(s): State<StateA>| async move { s.data_a }));

// Provide StateA, next missing state is StateB
let router_b: Router<StateB> = router_a.with_state(StateA {
    data_a: "A".to_string(),
});

// Add routes needing StateB
let router_b = router_b
    .route("/b", get(|State(s): State<StateB>| async move { s.data_b }));

// Provide StateB, now we have Router<()>
let app: Router<()> = router_b.with_state(StateB {
    data_b: "B".to_string(),
});

错误处理

使用 Result 进行基本错误处理

在处理器中使用 Result 处理错误:

use axum::http::StatusCode;

async fn handler() -> Result<String, StatusCode> {
    let result = some_operation().await;
    match result {
        Ok(data) => Ok(format!("Success: {}", data)),
        Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
    }
}
自定义错误类型

为自定义错误实现 IntoResponse:

use axum::{
    response::{IntoResponse, Response},
    http::StatusCode,
    Json,
};
use serde::Serialize;

#[derive(Debug)]
enum AppError {
    Database(sqlx::Error),
    NotFound,
    Unauthorized,
    ValidationError(String),
}

#[derive(Serialize)]
struct ErrorResponse {
    error: String,
    message: String,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, error_message) = match self {
            AppError::Database(e) => {
                tracing::error!("Database error: {}", e);
                (StatusCode::INTERNAL_SERVER_ERROR, "Database error")
            }
            AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
            AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, &msg),
        };

        let body = Json(ErrorResponse {
            error: status.to_string(),
            message: error_message.to_string(),
        });

        (status, body).into_response()
    }
}

// Use in handler
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
    let user = db.get_user(id).await.map_err(AppError::Database)?;
    user.ok_or(AppError::NotFound).map(Json)
}
提取器拒绝处理

处理提取器拒绝,以提供更清晰的错误消息:

use axum::extract::rejection::JsonRejection;

async fn create_user(
    payload: Result<Json<CreateUser>, JsonRejection>,
) -> Result<Json<User>, AppError> {
    let Json(create_user) = payload.map_err(|err| match err {
        JsonRejection::MissingJsonContentType(_) => {
            AppError::ValidationError("Content-Type must be application/json".to_string())
        }
        JsonRejection::JsonDataError(e) => {
            AppError::ValidationError(format!("Invalid JSON: {}", e))
        }
        JsonRejection::JsonSyntaxError(e) => {
            AppError::ValidationError(format!("JSON syntax error: {}", e))
        }
        _ => AppError::ValidationError("Invalid request body".to_string()),
    })?;

    // Process create_user
    Ok(Json(user))
}
带错误处理的自定义提取器

创建具有自定义拒绝类型的提取器:

use axum::{
    extract::{FromRequest, Request},
    response::{IntoResponse, Response},
    async_trait,
};

struct ValidatedJson<T>(T);

#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
    T: serde::de::DeserializeOwned + Validate,
    S: Send + Sync,
{
    type Rejection = Response;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let Json(data) = Json::<T>::from_request(req, state)
            .await
            .map_err(|err| {
                (
                    StatusCode::BAD_REQUEST,
                    format!("Invalid JSON: {}", err),
                ).into_response()
            })?;

        data.validate().map_err(|err| {
            (
                StatusCode::BAD_REQUEST,
                format!("Validation error: {}", err),
            ).into_response()
        })?;

        Ok(ValidatedJson(data))
    }
}

async fn handler(ValidatedJson(data): ValidatedJson<MyData>) -> String {
    // data is validated
    format!("Received: {:?}", data)
}
中间件错误处理

处理可失败中间件产生的错误:

use axum::error_handling::HandleErrorLayer;
use tower::ServiceBuilder;

async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
    if err.is::<tower::timeout::error::Elapsed>() {
        (
            StatusCode::REQUEST_TIMEOUT,
            "Request took too long".to_string(),
        )
    } else {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Unhandled error: {}", err),
        )
    }
}

let app = Router::new()
    .route("/", get(handler))
    .layer(
        ServiceBuilder::new()
            .layer(HandleErrorLayer::new(handle_timeout_error))
            .layer(TimeoutLayer::new(Duration::from_secs(30)))
    );
可失败服务

路由到可能失败的服务:

use axum::error_handling::HandleError;

async fn fallible_operation() -> Result<(), anyhow::Error> {
    // Operation that might fail
    Ok(())
}

let fallible_service = tower::service_fn(|_req| async {
    fallible_operation().await?;
    Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});

async fn handle_error(err: anyhow::Error) -> (StatusCode, String) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("Something went wrong: {}", err),
    )
}

let app = Router::new().route_service(
    "/",
    HandleError::new(fallible_service, handle_error),
);

构建响应

JSON 响应

以类型安全的方式返回 JSON:

use axum::Json;
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse {
    success: bool,
    data: String,
}

async fn handler() -> Json<ApiResponse> {
    Json(ApiResponse {
        success: true,
        data: "Hello".to_string(),
    })
}
HTML 响应

提供 HTML 内容:

use axum::response::Html;

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

async fn dynamic_html(Path(name): Path<String>) -> Html<String> {
    Html(format!("<h1>Hello, {}</h1>", name))
}
状态码响应

返回不同的状态码:

use axum::http::StatusCode;

async fn handler() -> StatusCode {
    StatusCode::NO_CONTENT
}

async fn with_body() -> (StatusCode, String) {
    (StatusCode::CREATED, "Resource created".to_string())
}

async fn json_with_status() -> (StatusCode, Json<ApiResponse>) {
    (StatusCode::CREATED, Json(ApiResponse { /* ... */ }))
}
自定义标头

为响应添加自定义标头:

use axum::http::{HeaderMap, header};

async fn handler() -> (HeaderMap, String) {
    let mut headers = HeaderMap::new();
    headers.insert(header::CACHE_CONTROL, "max-age=3600".parse().unwrap());
    headers.insert("X-Custom-Header", "value".parse().unwrap());
    (headers, "Response body".to_string())
}
完全控制响应

构建完整响应:

use axum::{
    response::Response,
    http::{StatusCode, header},
    body::Body,
};

async fn handler() -> Response {
    Response::builder()
        .status(StatusCode::OK)
        .header(header::CONTENT_TYPE, "application/json")
        .header("X-Custom", "value")
        .body(Body::from(r#"{"status":"ok"}"#))
        .unwrap()
}
流式响应

向客户端流式传输数据:

use axum::response::sse::{Event, Sse};
use futures::stream::{self, Stream};
use std::convert::Infallible;

async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
    let stream = stream::iter(0..10).map(|i| {
        Ok(Event::default().data(format!("Event {}", i)))
    });
    Sse::new(stream)
}
文件下载

提供可供下载的文件:

use axum::{
    response::{Response, IntoResponse},
    http::{header, StatusCode},
    body::Body,
};
use tokio::fs::File;

async fn download_file() -> Result<Response, StatusCode> {
    let file = File::open("path/to/file.pdf")
        .await
        .map_err(|_| StatusCode::NOT_FOUND)?;

    let body = Body::from_stream(tokio_util::io::ReaderStream::new(file));

    Ok(Response::builder()
        .status(StatusCode::OK)
        .header(header::CONTENT_TYPE, "application/pdf")
        .header(
            header::CONTENT_DISPOSITION,
            "attachment; filename=\"file.pdf\"",
        )
        .body(body)
        .unwrap())
}

生产环境模式

数据库集成

集成数据库连接池:

use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<i64>,
) -> Result<Json<User>, AppError> {
    let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
        .fetch_optional(&state.db)
        .await
        .map_err(AppError::Database)?
        .ok_or(AppError::NotFound)?;

    Ok(Json(user))
}

#[tokio::main]
async fn main() {
    let db = PgPoolOptions::new()
        .max_connections(5)
        .connect(&env::var("DATABASE_URL").unwrap())
        .await
        .unwrap();

    let state = AppState { db };

    let app = Router::new()
        .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();
}
配置管理

使用环境变量和配置:

use serde::Deserialize;
use config::{Config, ConfigError, Environment};

#[derive(Debug, Deserialize, Clone)]
struct Settings {
    database_url: String,
    redis_url: String,
    jwt_secret: String,
    port: u16,
}

impl Settings {
    fn new() -> Result<Self, ConfigError> {
        Config::builder()
            .add_source(Environment::default())
            .build()?
            .try_deserialize()
    }
}

#[derive(Clone)]
struct AppState {
    settings: Settings,
    db: PgPool,
}

#[tokio::main]
async fn main() {
    let settings = Settings::new().expect("Failed to load configuration");

    let db = PgPoolOptions::new()
        .connect(&settings.database_url)
        .await
        .expect("Failed to connect to database");

    let state = AppState {
        settings: settings.clone(),
        db,
    };

    let app = Router::new()
        .route("/", get(handler))
        .with_state(state);

    let addr = format!("0.0.0.0:{}", settings.port);
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
结构化日志记录

实现全面的日志记录:

use tracing::{info, error, debug, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() {
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "info".into()),
        )
        .with(tracing_subscriber::fmt::layer())
        .init();

    info!("Starting server");

    let app = Router::new()
        .route("/", get(handler))
        .layer(TraceLayer::new_for_http());

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    info!("Listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

#[instrument]
async fn handler(Path(id): Path<u32>) -> Result<String, AppError> {
    debug!("Handling request for user {}", id);

    match get_user_from_db(id).await {
        Ok(user) => {
            info!("Successfully retrieved user {}", id);
            Ok(format!("User: {}", user))
        }
        Err(e) => {
            error!("Failed to get user {}: {}", id, e);
            Err(AppError::Database(e))
        }
    }
}
优雅关闭

实现优雅关闭处理:

use tokio::signal;

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    println!("Signal received, starting graceful shutdown");
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(handler));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}
健康检查

实现健康检查端点:

use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    status: String,
    database: String,
    cache: String,
}

async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
    let db_status = match sqlx::query("SELECT 1").fetch_one(&state.db).await {
        Ok(_) => "healthy",
        Err(_) => "unhealthy",
    };

    let cache_status = match state.redis.ping().await {
        Ok(_) => "healthy",
        Err(_) => "unhealthy",
    };

    Json(HealthResponse {
        status: if db_status == "healthy" && cache_status == "healthy" {
            "healthy".to_string()
        } else {
            "degraded".to_string()
        },
        database: db_status.to_string(),
        cache: cache_status.to_string(),
    })
}

let app = Router::new()
    .route("/health", get(health_check))
    .route("/ready", get(readiness_check))
    .with_state(state);
速率限制

实现速率限制:

use tower::limit::RateLimitLayer;
use std::time::Duration;

let app = Router::new()
    .route("/api/data", get(handler))
    .layer(RateLimitLayer::new(
        100, // max requests
        Duration::from_secs(60), // per minute
    ));
请求验证

使用自定义提取器验证请求:

use validator::{Validate, ValidationError};

#[derive(Debug, Deserialize, Validate)]
struct CreateUserRequest {
    #[validate(length(min = 3, max = 50))]
    username: String,
    #[validate(email)]
    email: String,
    #[validate(length(min = 8))]
    password: String,
}

struct ValidatedJson<T>(T);

#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
    T: DeserializeOwned + Validate,
    S: Send + Sync,
{
    type Rejection = (StatusCode, String);

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let Json(data) = Json::<T>::from_request(req, state)
            .await
            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;

        data.validate()
            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;

        Ok(ValidatedJson(data))
    }
}

async fn create_user(
    ValidatedJson(user): ValidatedJson<CreateUserRequest>,
) -> Result<Json<User>, AppError> {
    // user is validated
    Ok(Json(User { /* ... */ }))
}

测试

处理器单元测试

独立测试处理器:

#[cfg(test)]
mod tests {
    use super::*;
    use axum::body::Body;
    use axum::http::{Request, StatusCode};
    use tower::ServiceExt;

    #[tokio::test]
    async fn test_handler() {
        let app = Router::new().route("/", get(handler));

        let response = app
            .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);
    }
}
集成测试

测试完整应用:

#[cfg(test)]
mod tests {
    use super::*;
    use axum::body::Body;
    use axum::http::{Request, StatusCode, Method};
    use tower::ServiceExt;
    use serde_json::json;

    #[tokio::test]
    async fn test_create_user() {
        let state = AppState::test();
        let app = create_app(state);

        let request_body = json!({
            "username": "testuser",
            "email": "[email protected]"
        });

        let response = app
            .oneshot(
                Request::builder()
                    .method(Method::POST)
                    .uri("/users")
                    .header("content-type", "application/json")
                    .body(Body::from(request_body.to_string()))
                    .unwrap()
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::CREATED);
    }
}

最佳实践

处理器组织
  1. 保持处理器精简 - 将业务逻辑移至服务层
  2. 使用提取器 - 让类型系统处理提取
  3. 返回 Result 类型 - 使用自定义错误类型
  4. 为处理器添加监测 - 添加 tracing 以便观测
状态管理
  1. 对开销较大的类型使用 Arc - 数据库连接池、缓存
  2. 保持状态精简 - 仅包含真正需要共享的内容
  3. 实现 Clone - State 提取器的必需条件
  4. 避免修改 - 必要时使用内部可变性(RwLock、Mutex)
错误处理
  1. 创建自定义错误类型 - 实现 IntoResponse
  2. 提供上下文 - 包含有用的错误消息
  3. 恰当地记录错误 - 使用 tracing
  4. 返回适当的状态码 - 符合 HTTP 语义
中间件
  1. 使用 ServiceBuilder - 排序方式更直观
  2. 在合适的层级应用 - 路由器、路由或方法
  3. 处理错误 - 对可失败中间件使用 HandleErrorLayer
  4. 让中间件职责明确 - 单一职责
性能
  1. 使用连接池 - 用于数据库和外部服务
  2. 启用压缩 - 使用 CompressionLayer 压缩响应
  3. 实现缓存 - 减少冗余操作
  4. 使用背压 - 使用 LoadShedLayer、RateLimitLayer
  5. 优化序列化 - 使用高效的 JSON 库
安全
  1. 验证输入 - 使用带验证的自定义提取器
  2. 实现身份验证 - 使用中间件进行身份验证
  3. 使用 HTTPS - 在生产环境中使用
  4. 设置安全标头 - CORS、CSP 等
  5. 限制速率 - 防止滥用

常见模式

仓储模式
trait UserRepository {
    async fn get(&self, id: u32) -> Result<User, AppError>;
    async fn create(&self, user: CreateUser) -> Result<User, AppError>;
    async fn update(&self, id: u32, user: UpdateUser) -> Result<User, AppError>;
    async fn delete(&self, id: u32) -> Result<(), AppError>;
}

struct PostgresUserRepository {
    pool: PgPool,
}

impl UserRepository for PostgresUserRepository {
    async fn get(&self, id: u32) -> Result<User, AppError> {
        // Implementation
    }
}

#[derive(Clone)]
struct AppState {
    user_repo: Arc<dyn UserRepository + Send + Sync>,
}

async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<u32>,
) -> Result<Json<User>, AppError> {
    let user = state.user_repo.get(id).await?;
    Ok(Json(user))
}
服务层模式
struct UserService {
    repo: Arc<dyn UserRepository + Send + Sync>,
    email_service: Arc<EmailService>,
}

impl UserService {
    async fn create_user(&self, data: CreateUser) -> Result<User, AppError> {
        let user = self.repo.create(data).await?;
        self.email_service.send_welcome_email(&user).await?;
        Ok(user)
    }
}

#[derive(Clone)]
struct AppState {
    user_service: Arc<UserService>,
}
版本化 API
fn v1_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(v1::list_users))
        .route("/users/:id", get(v1::get_user))
}

fn v2_routes() -> Router<AppState> {
    Router::new()
        .route("/users", get(v2::list_users))
        .route("/users/:id", get(v2::get_user))
}

let app = Router::new()
    .nest("/api/v1", v1_routes())
    .nest("/api/v2", v2_routes())
    .with_state(state);

技能版本:1.0.0 最后更新:2025 年 10 月 技能类别:Web 开发、REST API、Rust、异步编程 兼容版本:Axum 0.7+、Tokio 1.0+、Tower 0.4+

评论

支持 Markdown