装饰器 · middleware

0. 一句话定位

维度 内容
作用对象 函数(ASGI 中间件 callable)
使用场景 中间件
来源 fastapi / starlette
语法形式 @app.middleware("http")app.add_middleware(...)

1. 做什么

在路由匹配之前/之后包裹每个 HTTP 请求:可记录日志、加响应头、鉴权、计时;通过 call_next(request) 把请求交给内层(最终到端点),再处理返回的 Response

2. 重点参数

@app.middleware("http")

参数 类型 作用 配置建议
被装饰函数 async fn 签名为 (request, call_next) 必须 async
call_next callable 调用下一层,返回 Response 不 await 则响应断链

app.add_middleware(cls, **options)

参数 类型 作用 配置建议
中间件类 ASGI Middleware CORSMiddleware 后添加的通常更靠外
选项 kwargs allow_origins 见各类文档

3. 最小可运行示例

装饰器式 HTTP 中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.get("/")
async def root():
return {"ok": True}

类中间件(CORS)

1
2
3
4
5
6
7
8
9
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

4. 常见变体

请求 ID 日志

1
2
3
4
5
6
7
8
import uuid

@app.middleware("http")
async def request_id_middleware(request: Request, call_next):
rid = request.headers.get("X-Request-ID", str(uuid.uuid4()))
response = await call_next(request)
response.headers["X-Request-ID"] = rid
return response

短路(不进入路由)

1
2
3
4
5
6
7
from fastapi.responses import JSONResponse

@app.middleware("http")
async def maintenance_mode(request: Request, call_next):
if request.url.path != "/health":
return JSONResponse({"detail": "maintenance"}, status_code=503)
return await call_next(request)

5. 适用 / 不适用

适用

  • 全路径日志、计时、CORS、GZip、统一错误包装
  • 与具体业务参数无关的横切逻辑

不适用

  • 需要注入 DB/用户到端点 → Depends
  • 仅个别路由鉴权 → 路由级 dependencies=[Depends(...)]
  • 进程启动级初始化 → lifespan 上下文

6. 易踩坑

  • 中间件必须 return Response;忘记 await call_next 导致挂起
  • 洋葱模型:后 add_middleware 的请求阶段更靠外;调试顺序时画层图
  • 中间件内读 request.body() 会消耗 body,影响后续端点,需缓存技巧
  • 同步阻塞操作会拖慢所有请求

7. 近邻替代

替代 何时用
Depends 按路由注入依赖
@app.exception_handler 异常转响应
反向代理(nginx) TLS、限流、静态文件

8. 参考

-------------本文结束感谢您的阅读-------------