Axum

bobmatnyc/claude-mpm-skills/toolchains/rust/frameworks/axum

作者 bobmatnyc718070a7d622MIT77 个星标收录于 2026年10月8日更新于 2026年10月8日仓库2个月前更新

Axum (Rust) web framework patterns for production APIs: routers/extractors, state, middleware, error handling, tracing, graceful shutdown, and testing

AI 生成的概览

使用 Axum 框架构建生产级 Rust HTTP API 的参考指南,涵盖路由、提取器、中间件、错误处理与测试。

功能
提供使用 Axum 构建 Rust HTTP 服务的模式说明与代码示例,包括类型化处理函数与提取器、共享状态、结构化错误响应、Tower 中间件层、链路追踪、超时、优雅关闭以及路由测试。还涉及带可选 HTTP 特性的 crate 结构,以及受监管环境下的关闭运维说明。产出的是指导内容与示例代码,而非生成的文件或脚本。
适用场景
适用于构建或审查需要类型化请求处理、可组合中间件、结构化错误、可观测性和优雅关闭的 Axum Rust HTTP API 或服务。也适用于在 Axum 与其他 Rust Web 框架之间做选择,或在不开启套接字的情况下测试路由。
运行要求
不包含脚本,仅为说明性内容。按示例操作需要 Rust 工具链以及 axum、tokio、tower、tower-http、serde 和 tracing 等 crate,并需要网络访问以下载依赖。

Axum (Rust) - Production Web APIs

Overview

Axum is a Rust web framework built on Hyper and Tower. Use it for type-safe request handling with composable middleware, structured errors, and excellent testability.

Quick Start

Minimal server

✅ Correct: typed handler + JSON response

rust
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();}

❌ Wrong: block the async runtime

rust
async fn handler() {    std::thread::sleep(std::time::Duration::from_secs(1)); // blocks executor}

Core Concepts

Router + handlers

Handlers are async functions that return something implementing IntoResponse.

✅ Correct: route nesting

rust
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 { "{}" }

Extractors

Prefer extractors for parsing and validation at the boundary:

  • Path<T>: typed path params
  • Query<T>: query strings
  • Json<T>: JSON bodies
  • State<T>: shared application state

✅ Correct: typed path + JSON

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

Dependencies & Crate Structure

If your crate is published as a library in addition to being built as a binary, isolate the HTTP stack to avoid bloating library consumers.

✅ Correct: optional HTTP feature

toml
[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"]

This way:

  • Library consumers (cargo add my-lib) get just the core logic without the HTTP overhead.
  • Binary builds include the server by default: cargo install my-crate works as expected.
  • Opt-out is explicit: cargo add my-crate --no-default-features for library use.

❌ Wrong: unconditional HTTP dependencies

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

Production Patterns

1) Shared state (DB pool, config, clients)

Use State<Arc<AppState>> and keep state immutable where possible.

✅ Correct: AppState via Arc

rust
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) Structured error handling (IntoResponse)

Centralize error mapping to HTTP status codes and JSON.

✅ Correct: AppError converts into response

rust
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) Middleware (Tower layers)

Use tower-http for production-grade layers: tracing, timeouts, request IDs, CORS.

✅ Correct: trace + timeout + CORS

rust
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) Graceful shutdown

Terminate on SIGINT/SIGTERM and let in-flight requests drain.

✅ Correct: with_graceful_shutdown

rust
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();}
Ops: Graceful Shutdown in Supervised Environments

Signal handling above is application-level. In production, your supervisor (systemd, launchd, container orchestrator) controls the actual termination window.

systemd (Linux): Set KillSignal=SIGTERM and TimeoutStopSec=120 (or higher) in your .service file.

  • The default 90s timeout can fire mid-fsync on networked storage (EBS/EFS), truncating writes.
  • 120s gives in-flight HTTP requests time to drain plus a safety margin for filesystem syncs.

launchd (macOS): Use launchctl bootout (sends SIGTERM and waits) instead of launchctl kickstart -k (SIGKILL, truncates in-flight I/O).

Client-side reconnection: Have HTTP clients and MCP bridges connecting to the service implement exponential backoff (starting 200ms, capped at 30s) so brief restarts are transparent and don't cascade errors upstream.

Testing

Test routers without sockets using tower::ServiceExt.

✅ Correct: request/response test

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

Decision Trees

Axum vs other Rust frameworks

  • Prefer Axum for Tower middleware composition and typed extractors.
  • Prefer Actix Web for a mature ecosystem and actor-style runtime model.
  • Prefer Warp for functional filters and minimalism.

Anti-Patterns

  • Block the async runtime (std::thread::sleep, blocking I/O inside handlers).
  • Use unwrap() in request paths; return structured errors instead.
  • Run without timeouts; add request timeouts and upstream deadlines.

Resources

来源与署名

来源:bobmatnyc/claude-mpm-skills位于toolchains/rust/frameworks/axum提交718070a

许可证: MIT

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架