概要(ミドルウェア=「リクエストが通る廊下に置く共通処理」)
ミドルウェアは一言でいうと、
「すべてのリクエスト/レスポンスが必ず通る“共通の廊下”に
共通処理を差し込むための仕組み」
です。
各エンドポイント(@app.get("/...") の関数)の前後で同じ処理をしたいことがよくあります。
認証トークンのチェック
アクセスログの記録
処理時間の計測
共通ヘッダの付与
こういった「どの API でも毎回やりたいこと」を、
ミドルウェアとして一箇所にまとめて書くイメージです。
FastAPI(中身は Starlette の仕組み)を例に、初心者向けに丁寧に見ていきます。
リクエストがアプリ内部を通る流れのイメージ
「入口 → ミドルウェア → エンドポイント → ミドルウェア → 出口」
ざっくりとした流れはこうです。
ブラウザやクライアントから HTTP リクエストが来る
FastAPI アプリの外側から順番にミドルウェアを通る
最終的にルーティングされて、対応するエンドポイント関数が実行される
返されたレスポンスが、もう一度ミドルウェアを逆順で通って外に出ていく
つまり、ミドルウェアは
「エンドポイントの前後で必ず通る場所」
です。
この「前」と「後」にコードを書けるのが、
ミドルウェアの一番のポイントです。
FastAPI のミドルウェアの基本形(@app.middleware(“http”))
とにかく一つ書いてみる
FastAPI で一番シンプルなミドルウェアは、@app.middleware("http") を使うパターンです。
from fastapi import FastAPI, Request
import time
app = FastAPI()
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
process_time = time.perf_counter() - start
response.headers["X-Process-Time"] = f"{process_time:.4f}s"
return response
@app.get("/hello")
def hello():
return {"message": "hello"}
Pythonこのミドルウェアは、
どのエンドポイントにアクセスしても「処理時間」を計測して、
レスポンスヘッダ X-Process-Time に入れるものです。
動き方を分解してみます。
@app.middleware("http")
このデコレータが、「HTTP リクエスト用のミドルウェアですよ」という印です。
async def add_process_time_header(request: Request, call_next):
ミドルウェア関数は、必ず request と call_next の 2 つを引数に取ります。
request
今回の HTTP リクエスト(メソッド、パス、ヘッダなどにアクセスできる)
call_next
「次の処理(次のミドルウェアか、最終的にはエンドポイント)を呼び出す関数」
response = await call_next(request)
ここが一番大事なところです。
この一行の前が「エンドポイントの前にやる処理」、
この一行の後が「エンドポイントの後にやる処理」です。
その後で、response.headers["X-Process-Time"] = ... として
レスポンスにヘッダを追加し、最後に return response で返しています。
これだけで、
あらゆるリクエストについて
「処理開始時間を記録 → エンドポイント実行 → 処理終了時間を記録 → ヘッダ追加」
という共通処理が差し込めます。
ミドルウェアでよくやること(用途のイメージ)
ログ出力(アクセスログ・エラーログ)
一番わかりやすい用途は「アクセスログ」です。
どのエンドポイントが、いつ、誰から、どれくらい時間をかけて呼ばれたか
ステータスコードは何だったか
などを、ミドルウェアで記録しておくと便利です。
import logging
from fastapi import FastAPI, Request
app = FastAPI()
logger = logging.getLogger("access")
@app.middleware("http")
async def access_log_middleware(request: Request, call_next):
client_host = request.client.host if request.client else "unknown"
method = request.method
path = request.url.path
logger.info(f"START {client_host} {method} {path}")
response = await call_next(request)
status = response.status_code
logger.info(f"END {client_host} {method} {path} -> {status}")
return response
Pythonこうすると、どのルートでもミドルウェアが通るので、
全リクエストのログが一箇所で記録されます。
各エンドポイントで logging.info を書きまくるより、
ずっとスッキリします。
共通ヘッダの追加・CORS・セキュリティ関連
レスポンスに「必ず付けたいヘッダ」がある場合も、ミドルウェア向きです。
例えば、簡単な例だと:
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
response = await call_next(request)
response.headers["X-App-Name"] = "MyApp"
return response
PythonCORS(クロスオリジン)やセキュリティヘッダ(X-Frame-Options など)は、
FastAPI の add_middleware で専用ミドルウェアを追加するのが一般的ですが、
「自分のアプリ独自の共通ヘッダ」を付けたいときは、こうした自作ミドルウェアが役立ちます。
自作ミドルウェアで「前」と「後」に何ができるかを整理する
エンドポイントの「前」でできること
response = await call_next(request) の前に書いたコードは、
エンドポイントが走る前に必ず実行されます。
ここでよくやることとしては、
認証情報のチェック(トークンがあるかなど)
リクエストIDの発行(トレース用の ID をヘッダやコンテキストに入れる)
リクエストボディのサイズチェック(あまりに大きい場合は早めに拒否)
などがあります。
たとえば、「簡易的に API キーをヘッダでチェックするミドルウェア」を考えてみます。
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
API_KEY = "SECRET"
@app.middleware("http")
async def api_key_check_middleware(request: Request, call_next):
api_key = request.headers.get("X-API-Key")
if api_key != API_KEY:
return JSONResponse(
status_code=401,
content={"detail": "Invalid API key"},
)
response = await call_next(request)
return response
Pythonここでは、call_next の前で API キーをチェックし、
間違っていたらその場で 401 を返して「エンドポイントまで進ませない」
ようにしています。
逆に、正しい場合だけ await call_next(request) で本処理に進みます。
エンドポイントの「後」でできること
call_next の後に書いたコードは、
エンドポイントが実行されてレスポンスが作られた後に必ず実行されます。
ここでよくやることとしては、
処理時間の計測
レスポンスヘッダの追加・書き換え
レスポンスボディのログ(軽いものに限る)
などがあります。
先ほどの処理時間ミドルウェアが、その典型例です。
start = time.perf_counter()
response = await call_next(request)
process_time = time.perf_counter() - start
response.headers["X-Process-Time"] = f"{process_time:.4f}s"
Pythonこの「前後に挟み込む」構造が、ミドルウェアの本質です。
複数ミドルウェアの順番と注意点
ミドルウェアは「玉ねぎ構造」でネストされる
FastAPI に複数のミドルウェアがあるとき、
リクエストは「外側から内側へ」、レスポンスは「内側から外側へ」流れます。
イメージとしては、
リクエスト
→ ミドルウェアA(前処理)
→ ミドルウェアB(前処理)
→ エンドポイント
→ ミドルウェアB(後処理)
→ ミドルウェアA(後処理)
→ レスポンス
という「玉ねぎ構造」です。
@app.middleware("http") を複数書いた場合、
基本的には定義された順番に適用されます。
ログ → 認証 → 処理時間計測
のような順番を意識して、
「どのミドルウェアがどこを包んでいるか」を考えると分かりやすいです。
call_next を呼ばないと「先に進まない」
重要な注意点として、
await call_next(request) を呼ばなければ、その先には進みません。
認証やレート制限などで
「条件を満たさないときはここで処理を終わらせる」
のは正しい使い方です。
ただし、「本当は先に進めたいのに call_next を呼び忘れた」
というバグをやりがちなので、そこだけ注意が必要です。
まとめ(ミドルウェアは「共通処理を一箇所に集めるためのレール」)
ミドルウェアを、自動化・API 開発の視点で整理すると、こうなります。
- ミドルウェアは、すべてのリクエスト/レスポンスが通る共通の「廊下」で、エンドポイントの前後に共通処理を差し込むための仕組み。
- FastAPI では
@app.middleware("http")でミドルウェアを定義し、call_next(request)の前で「前処理」、後で「後処理」を書く。 - アクセスログ、処理時間計測、共通ヘッダの付与、簡易認証・レート制限など、「どの API でも毎回やりたいこと」はミドルウェアに寄せるとコードがスッキリする。
- 複数ミドルウェアは玉ねぎのようにネストされ、定義順で「外側から内側へ」リクエストを包み込むイメージになるので、順番を意識して設計することが大事。
call_nextの呼び方・呼ぶ場所を意識しながら、「前で何をするか」「後ろで何をするか」を切り分けていくと、ミドルウェア設計の感覚がつかめてくる。
