mcpField
A
axum

axum

github.com/bobmatnyc开发78491 次安装15 周前更新
代码仓库

关于 axum

Axum 是面向 Rust 开发者的 Web 框架,适用于构建 HTTP API,提供类型化提取器、Tower 中间件组合和可测试的路由器。

主要功能

  1. 01类型化请求提取器
  2. 02可组合的 Tower 中间件
  3. 03结构化 HTTP 错误
  4. 04优雅关闭
  5. 05无需套接字的路由测试

适用场景

  1. 01构建类型化 HTTP API 路由
  2. 02在处理器间共享应用状态
  3. 03添加跟踪、超时和 CORS
  4. 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();应改为返回结构化错误。
  • 不设置超时;应添加请求超时和上游截止时间。

资源

评论

支持 Markdown