ミドルウェア¶
Note
ミドルウェアの仕組みは Starlette のもの。 FastAPI クラス は Starlette クラス を継承していて、 @app.middleware("http") も add_middleware も Starlette 側で定義されたものを受け継いでるだけ。下のコードにもある @app.middleware("http") は、内部で Starlette の BaseHTTPMiddleware を使って、関数を包んでるだけ。
add_middleware で追加する定番のミドルウェアも Starlette の再エクスポート。例えば FastAPI の CORSMiddleware は Starlette の CORSMiddleware と全く同じもの。
ミドルウェアは、リクエストがエンドポイントに届く前と、レスポンスがクライアントに返る前に処理を挟み込める仕組み。
認証チェックや CORS、 CSRF 対策 のように、複数のエンドポイントに共通でかけたい処理を書くのに向いてる。
cf. https://fastapi.tiangolo.com/tutorial/middleware/
どういう流れで動くのか¶
ミドルウェアは「リクエストを受け取る → 次の処理に渡す → レスポンスを受け取る」という順番で動く。
call_next を呼ぶと、その先のミドルウェアやエンドポイントに処理が渡って、戻ってきたレスポンスを受け取れる。
下の例は、リクエストの処理にかかった時間を測ってレスポンスヘッダーに入れるミドルウェア(FastAPI チュートリアルと同じもの)。
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request) # 先の処理にリクエストを渡す
elapsed = time.perf_counter() - start
response.headers["X-Process-Time"] = str(elapsed) # 返ってきたレスポンスに手を加える
return response
@app.middleware("http") を付けると、全リクエストがこの関数を通る。引数の request が届いたリクエストで、 call_next が「この先の処理を実行して」という関数。
ポイントは call_next を境に処理が「行き」と「帰り」に分かれること。
- 行き(
call_nextの前 ): まだエンドポイントには渡してない段階。ここでは開始時刻を記録してる。 await call_next(request): ここで初めてエンドポイントに処理が渡る。awaitで処理が終わるのを待って、結果のレスポンスを受け取る。- 帰り(
call_nextの後 ): 返ってきたレスポンスに手を加えられる。ここではかかった時間を計算して、X-Process-Timeヘッダーに入れてる。
認証チェックみたいに「エンドポイントに入る前に弾きたい」処理は行きに、レスポンスの加工は帰りに書く、というイメージ。
複数のミドルウェアを登録すると、玉ねぎのように外側から内側へ入って、内側から外側へ戻ってくる。
FastAPI の内部の仕組み¶
ミドルウェアが内部でどう動いてるかは、 Marcelo Trylesinski さんの PyCon Italia 2024 のトークで説明されてる。
トークでは、リクエストは Server から Application へ進んで、レスポンスは Application から Server へ戻ってくるって説明されてる。
下記のコードを例にすると、下の図のような流れでミドルウェアが効いてくるみたい。
from fastapi import FastAPI
from src.middleware import CustomMiddleware, AnotherCustomMiddleware
app = FastAPI()
app.add_middleware(CustomMiddleware)
app.add_middleware(AnotherCustomMiddleware)
