Axum

axum カスタム Extractor 実装ガイド:FromRequest と DI パターン

ⓘ本ページはプロモーションが含まれています

スポンサードリンク

Extractor の基本概念と axum における位置付け(Middleware と比較)

axum では、リクエストからハンドラが必要とするデータだけを 型安全に 取り出す仕組みとして Extractor が提供されています。
一方、Middleware はリクエスト全体やレスポンス全体を前後で加工することが主目的であり、ハンドラ内部まで直接的にデータを注入できません。この違いを正しく理解すれば、DI(Dependency Injection)的な設計とグローバルな前処理・後処理を自然に組み合わせられます。

  • 呼び出しタイミングRouter がパスをマッチさせた直後、axum の内部パイプラインが各ハンドラ引数の FromRequest::from_request を順次実行します。
  • 組み合わせ方:全体的な前処理は .layer() で Middleware を設定し、個別ルートでは .route(...).get(handler) にカスタム Extractor を入れるだけです。Middleware が先に走り、必要なら Extension で共有状態を注入したうえで、Extractor がリクエストスコープのデータを取得します。

ポイント:Extractor はハンドラ引数として型安全にデータを提供し、Middleware は全体的な前後処理を担う。両者は相補的であり、.layer.route の順序さえ守ればシームレスに併用できます。


FromRequest トレイトの現在のシグネチャと非同期実装

axum 0.7 系(2024 年リリース)以降、公式ドキュメントは次のシグネチャを推奨しています(https://docs.rs/axum/latest/axum/extract/trait.FromRequest.html)。

この形は impl Future<Output = Result<Self, Rejection>> + Send を返す async fn と等価であり、async_trait クレートを使う必要はありません。
その結果、ボックス化された Pin<Box<dyn Future>> が不要になるためコンパイルが速くなり、ランタイムオーバーヘッドも削減できます。

以下に、余計なインポートを除いた最小限の実装例を示します。

  • 必須要件Self::RejectionIntoResponse を実装している必要があります。
  • 非同期化のポイントasync move ブロック内で await があれば自動的に非同期になるので、手書きの Future 型は不要です。

ポイント:最新のシグネチャは async fn from_requestimpl Future を返すだけ。async_trait 不使用でシンプルかつ高速です。


ヘッダー・クエリパラメータ・Body からデータ取得する実装例

カスタム Extractor が実務上必要とする情報は、主に ヘッダークエリ文字列、そして JSON ボディ の3種です。ここではそれぞれを安全かつ idiomatic に取り出す方法を示します。

ヘッダー取得と認証トークンのバリデーション

この実装は MissingHeaderInvalidHeaderInvalidToken といった具体的なエラーに分岐させることで、ハンドラ側が原因を容易に把握できるようにしています。

クエリパラメータの型安全パース

serde_urlencoded は URL エンコードされたクエリ文字列を構造体へ変換でき、型安全かつ エラー情報が明示的 です。

JSON Body の取得 ― 標準 Extractor を活用した例

手動で hyper::body::to_bytes を呼び出す代わりに、axum が提供する標準 Extractor Json<T> を組み合わせるとコードが大幅に簡潔になります。以下は MyExtractor の内部から Json<Value> を取得し、失敗時はカスタムエラーへ変換する例です。

この書き方の利点は axum::extract::Json が自動的に Content-Type: application/json の検証とデシリアライズを行う ことです。手作業で hyper::body::to_bytesserde_json::from_slice と書くより安全かつ保守性が高まります。

ポイント:ヘッダー・クエリ・Body の取得はそれぞれ専用のライブラリ(serde_urlencoded, serde_json, axum::extract::Json)を活用するとコードが簡潔になる。失敗時は統一的なカスタムエラー型にマッピングしてハンドラ側のロジックをすっきりさせます。


DI(依存性注入)パターンと他 Extractor との組み合わせ

DI を実現する代表的な手段は Extension<T>、もしくは axum 0.7 系で新たに追加された .with_state(state) です。どちらも内部的には Extension に変換されるため、同等の使い勝手が得られます。

.with_state(state) のバージョン情報

  • 利用可能バージョン:axum 0.7.0 以降(2024 年リリース)
  • 動作概要Router::with_state(state) を呼び出すと、全てのルートに対して自動的に Extension(state.clone()) が付与されます。従来は明示的に .layer(Extension(state)) と書く必要がありました。

カスタム Extractor 内で Extension と標準 Extractor を同時に利用

  • 循環依存の防止Arc<T> に包むことで Clone が軽量になり、所有権エラーを回避できます。
  • .with_state の活用:上記例では Extension::<Arc<Config>> を手動で取得していますが、Router::with_state で自動的に注入されるためコードがさらにシンプルになります。

ポイントExtension と標準 Extractor は自由に組み合わせられ、共有状態は Arc で包むことで Clone コストを抑えつつ安全に DI が実現できます。axum 0.7 以降は .with_state を使うと記述が一段階簡略化されます。


カスタムエラー型と統一的エラーハンドリング

Extractor が失敗した際に返す型は Rejection と呼ばれ、ハンドラ側へ Result<T, Rejection> の形で伝搬します。ここでは、HTTP ステータスコードと JSON ボディを同時に返すカスタムエラー MyExtractorError を実装し、全ての Extractor が共通して利用できるようにします。

FromRequest::Rejection にこの型を指定すれば、ハンドラは Result<T, MyExtractorError> をそのまま返すだけで統一的なエラー応答が実現します。

ポイント:カスタムエラーに IntoResponse を実装すると、Extractor の失敗が自動的に HTTP レスポンスへ変換され、ハンドラ側のコードはシンプルになるだけでなく、エラーメッセージやステータスコードを一元管理できるようになります。


Router への登録例・テストコード・ベストプラクティスとパフォーマンス考慮点

完全な Router 組み込み例(axum 0.7)

ユニットテスト(tower::Service を利用)

2024‑2026 年版ベストプラクティス(箇条書き)

  • async_trait を使わない
    FromRequestasync fnimpl Future が公式推奨です。ボックス化されたトレイトオブジェクトは不要なので、コンパイル速度と実行時性能が向上します。

  • Pin<Box<dyn Future>> から脱却
    async fn の戻り値型は自動的に匿名 Future になるため、手書きのボックス化は避けます。これによりヒープ割当が減ります。

  • 共有状態は必ず Arc<T> に包む
    Extension.with_state が要求する型は Clone が軽量であることが前提です。Arc を使うことで所有権の問題を回避しつつ、マルチスレッド環境でも安全に共有できます。

  • リクエストスコープキャッシュ
    同一ハンドラ内でヘッダーやパース結果を複数回参照する場合は、一度取得した値をローカル変数に保持して再計算を防ぎます。特に serde_urlencoded::from_str は比較的重いのでキャッシュが有効です。

  • エラーは統一型 Rejection に集約
    カスタムエラーに IntoResponse を実装すれば、全ての Extractor が同じ形で失敗を伝搬でき、ハンドラ側の Result<T, E> パターンマッチがシンプルになります。

  • 標準 Extractor の活用
    Json<T>, Path<T>, Query<T> といった axum が提供する Extractor は内部でエラーハンドリングと型変換を行うため、手動実装より安全です。必要に応じて自前のロジックと組み合わせましょう。

  • with_state の利用
    axum 0.7 以降は Router::with_state(state) が公式 API として提供されています。古いバージョンを対象にする場合は .layer(Extension(state)) を併用してください。

まとめ:上記ベストプラクティスに沿って実装すれば、最新 axum (0.7 系) でも安全・高速なカスタム Extractor が構築でき、DI パターンやエラーハンドリングといった周辺機能ともシームレスに統合できます。


まとめ

  • Extractor と Middleware の役割を正しく認識し、前者はハンドラ引数で型安全にデータ注入、後者は全体的な前処理・後処理を担う点を押さえておくことが重要です。
  • FromRequest の最新シグネチャは async fn from_request -> impl Future であり、async_trait は不要です。この形に統一すればコンパイル速度と実行時性能が向上します。
  • ヘッダー・クエリ・Body の取得は 標準 Extractor (Json, Query) と外部ライブラリ(serde_urlencoded を組み合わせ、失敗時はカスタム Rejection に集約して IntoResponse で統一的に返す設計がベストです。
  • DI は Extension<T> または axum 0.7 の .with_state(state) が推奨され、Arc に包むことで所有権問題とコピーコストを回避できます。
  • テストコードは tower::ServiceExt を用いた oneshot 呼び出しで実装すれば、リクエスト全体の流れをそのまま検証でき、Extractor の正当性も保証されます。

これらの手順とベストプラクティスに沿ってコードを書くことで、2024‑2026 年版 axum において 安全・高速・保守しやすい カスタム Extractor を実装し、実際のサービスへシームレスに組み込むことが可能になります。

スポンサードリンク

-Axum