中间件与责任链
责任链的本意:多个处理器依次尝试,直到有人处理或链条走完。Web 框架的中间件是责任链的变体:每个环节都处理,请求穿过整条链。FastAPI/Starlette 的中间件基于 ASGI 洋葱模型。
责任链模式
class Handler:
def __init__(self, next_handler=None):
self._next = next_handler
def handle(self, request):
result = self._process(request) # 本环节处理
if result is None and self._next: # 处理不了就传递
return self._next.handle(request)
return result两种形态:
- 传递型:处理不了才传给下一个(日志级别、审批流)
- 全链型:每环都处理并传给下一个(Web 中间件)
ASGI 洋葱模型
洋葱模型: 请求逐层进入, 响应逐层返回
- 请求从外到内穿过所有中间件到达应用,响应从内到外穿回
- 每层可决定:放行(调
call_next)、拦截(直接返回,不进内层)、增强(响应回来再加工)
FastAPI 中间件
from starlette.middleware.base import BaseHTTPMiddleware
@app.middleware("http")async def timing_middleware(request, call_next):
start = time.perf_counter()
response = await call_next(request) # 进入下一层
response.headers["X-Time"] = str(time.perf_counter() - start)
return response要点:
call_next是“进入内层”的开关,不调它请求就被拦截- 注册顺序决定执行顺序(先注册的在外层)
- 纯 ASGI 中间件(类实现
__call__)性能更好,BaseHTTPMiddleware 有额外开销;生产级中间件(如 GZip)用纯 ASGI 形态
常用中间件场景:请求日志、鉴权、CORS、限流、响应压缩、统一错误处理。
与 Java 的对比
| 维度 | FastAPI/Starlette | Spring |
|---|---|---|
| 模型 | ASGI 洋葱(中间件包应用) | Filter 链 + Interceptor |
| 写法 | 装饰器或 ASGI 类 | Filter 类 + 配置 |
| 顺序控制 | 注册顺序 | @Order 注解 |
| 终止链路 | 不调 call_next | 不调 chain.doFilter |
FastAPI 的“依赖”也承担部分中间件职责(鉴权、会话),选择标准:横切所有请求用中间件,单接口逻辑用依赖。
面试追问
- 洋葱模型是什么? 请求逐层进入、响应逐层返回。每层进出都有机会处理
- call_next 不调会怎样? 请求被拦截在本层,不进应用。返回自定义响应(鉴权失败、限流)
- 中间件和依赖怎么分工? 全局横切(日志、CORS)用中间件;接口级逻辑(鉴权、会话)用依赖。中间件先于依赖执行
- 注册顺序影响什么? 执行顺序。先注册的在外层,先处理请求、后处理响应
- BaseHTTPMiddleware 和纯 ASGI 的区别? 前者有额外开销,后者性能好。生产中间件用纯 ASGI 形态