How-To: ミドルウェアスタックの正しい設定
推奨: setup_middlewares() を使う
手動で add_middleware を並べる代わりに、フレームワーク提供のヘルパーを使うと LIFO 順序ミス(500 に X-Request-Id が付かない等)を防げる。
from nene2.middleware import setup_middlewares
setup_middlewares(
app,
debug=cfg.app_debug,
domain_handlers=[NoteNotFoundExceptionHandler()],
throttle_limit=cfg.throttle_limit if cfg.throttle_enabled else None,
max_request_bytes=cfg.max_body_size,
cors_allowed_origins=cfg.cors_origins if cfg.cors_enabled else None,
)有効なスタック(外側 → 内側):
CORS → RequestId → SecurityHeaders → SizeLimit → Throttle → RequestLogging → ErrorHandlerカスタムミドルウェアが必要な場合のみ、以下の手動登録パターンを使う。
手動登録(TL;DR)
# この順序で add_middleware を呼ぶ
app.add_middleware(ErrorHandlerMiddleware) # 最内側
app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(ThrottleMiddleware, limit=100, window=60)
app.add_middleware(RequestSizeLimitMiddleware, max_bytes=1_048_576)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(RequestIdMiddleware) # 最外側なぜこの順序なのか
Starlette の LIFO ルール
app.add_middleware() は 後から追加したものが外側(LIFO)になる。
add_middleware(A) → B(A(Router))
add_middleware(B)リクエストは外側から内側へ(B → A → Router)、 レスポンスは内側から外側へ(Router → A → B)流れる。
ErrorHandler を最外側にすると何が壊れるか
# ❌ 間違い
app.add_middleware(RequestIdMiddleware)
app.add_middleware(ErrorHandlerMiddleware) # 最外側
# スタック: ErrorHandler(RequestId(Router))ハンドラーが例外を raise したとき:
ErrorHandlerMiddleware.dispatchが例外を捕捉problem_details_response(...)で 新しい Response を直接 return- この Response は内側の
RequestIdミドルウェアを 通過しない - 結果: 500 エラーに
X-Request-Idが付かない
同じ理由で SecurityHeadersMiddleware が内側にあると、 エラーレスポンスにセキュリティヘッダーが付かない。
正しい順序のスタック図
RequestIdMiddleware ← 全レスポンス(200〜5xx)に X-Request-Id を付与
└─ SecurityHeadersMiddleware ← 全レスポンスにセキュリティヘッダーを付与
└─ RequestSizeLimitMiddleware ← 413 を直接返す(ErrorHandler 不要)
└─ ThrottleMiddleware ← 429 を直接返す(ErrorHandler 不要)
└─ RequestLoggingMiddleware
└─ ErrorHandlerMiddleware ← ハンドラー例外を 500 に変換
└─ Router (FastAPI handlers)RequestSizeLimitMiddleware と ThrottleMiddleware は自身で problem_details_response() を 返すため、ErrorHandler の内外に置いても 413/429 の形式は変わらない。 ただし X-Request-Id が付くかどうかは RequestIdMiddleware の位置次第。
使用しないミドルウェアがある場合
一部のミドルウェアを省略しても、残りの順序は同じルールに従う:
# ThrottleMiddleware と RequestLoggingMiddleware を省略した場合
app.add_middleware(ErrorHandlerMiddleware)
app.add_middleware(RequestSizeLimitMiddleware, max_bytes=1_048_576)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(RequestIdMiddleware)ErrorHandlerMiddleware.install() を使う場合
install() は add_middleware と add_exception_handler をまとめて行うが、 他のミドルウェアとの順序設定は手動で行う必要がある:
# install() は最初に呼ぶ(最内側になる)
ErrorHandlerMiddleware.install(app) # 内側
# その後に他のミドルウェアを追加
app.add_middleware(RequestSizeLimitMiddleware, max_bytes=1_048_576)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(RequestIdMiddleware) # 外側よくある質問
Q: RequestSizeLimitMiddleware は ErrorHandler の内外どちらに置くべきか?
A: 内外どちらでも機能するが、RequestIdMiddleware より内側にすることで 413 レスポンスにも X-Request-Id が付く。上記の推奨順序に従えばよい。
Q: カスタムミドルウェアはどこに追加するか?
A: そのミドルウェアの性質による:
- 全レスポンスに何かを追加したい →
RequestIdMiddlewareの直前(外側) - ハンドラー例外をキャッチしたい →
ErrorHandlerMiddlewareの直後(内側) - リクエストを早期拒否したい →
RequestSizeLimitMiddlewareやThrottleMiddlewareの近く