axum について
Axumは、HTTP APIを構築するRust開発者向けのWebフレームワークです。型付きエクストラクター、Towerミドルウェアの合成、テスト可能なルーターを備えています。
主な機能
- 01型付きリクエストエクストラクター
- 02合成可能なTowerミドルウェア
- 03構造化HTTPエラー
- 04グレースフルシャットダウン
- 05ソケット不要のルーターテスト
活用シーン
- 01型付きHTTP APIルートの構築
- 02ハンドラー間でのアプリ状態共有
- 03トレース、タイムアウト、CORSの追加
- 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
}
主な概念
Router とハンドラー
ハンドラーは、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() })
}
依存関係とクレート構成
クレートをバイナリとしてビルドするだけでなく、ライブラリとして公開する場合は、ライブラリ利用者の依存関係が肥大化しないよう、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)共有状態(DB プール、設定、クライアント)
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 レイヤー)
本番環境に適したレイヤー(トレーシング、タイムアウト、リクエスト ID、CORS)には tower-http を使用してください。
✅ 正しい例:トレース+タイムアウト+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(またはそれ以上)を設定します。
- デフォルトの 90 秒のタイムアウトは、ネットワークストレージ(EBS/EFS)での fsync の途中に発生し、書き込みが切り詰められる可能性があります。
- 120 秒あれば、処理中の HTTP リクエストを完了させる時間に加え、ファイルシステムの同期に備える余裕も確保できます。
launchd(macOS):launchctl kickstart -k(SIGKILL を送信し、処理中の I/O を切り詰める)の代わりに、launchctl bootout(SIGTERM を送信して終了を待つ)を使用します。
クライアント側での再接続:サービスに接続する 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 を選んでください。
- 成熟したエコシステムとアクタースタイルのランタイムモデルを重視する場合は、Actix Web を選んでください。
- 関数型フィルターとミニマリズムを重視する場合は、Warp を選んでください。
アンチパターン
- 非同期ランタイムをブロックする(
std::thread::sleep、ハンドラー内でのブロッキング I/O)。 - リクエスト処理で
unwrap()を使う。代わりに構造化されたエラーを返してください。 - タイムアウトなしで実行する。リクエストのタイムアウトと上流の期限を設定してください。
リソース
- Axum のドキュメント:https://docs.rs/axum
- Tower HTTP レイヤー:https://docs.rs/tower-http
- Tracing:https://docs.rs/tracing