中间件与异常处理
中间件管横切逻辑(日志、CORS、压缩),异常处理器管错误出口。面试主线:中间件怎么写、异常怎么统一处理。
中间件
@app.middleware("http")
async def timing_middleware(request: Request, call_next):
start = time.perf_counter()
response = await call_next(request) # 进入内层
response.headers["X-Process-Time"] = str(time.perf_counter() - start)
return response- 洋葱模型:请求逐层进入,响应逐层返回(详见设计模式篇)
call_next是进入下一层的开关,不调用就拦截请求- 注册顺序决定执行顺序,先注册的在外层
- 横切所有请求的逻辑(日志、限流、统一响应头)放中间件
异常处理器
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
class BizError(Exception):
def __init__(self, code: int, message: str):
self.code, self.message = code, message
@app.exception_handler(BizError)
async def biz_error_handler(request: Request, exc: BizError):
return JSONResponse(status_code=400,
content={"code": exc.code, "message": exc.message})| 异常类型 | 处理 |
|---|---|
HTTPException |
内置,raise HTTPException(404, "用户不存在") |
| 自定义业务异常 | @app.exception_handler(MyError) 统一出口 |
| 未知异常 | 兜底 handler(500,记日志防泄漏细节) |
工程模式:业务代码抛自定义异常(BizError),异常处理器统一转 JSON 响应。错误码、错误消息、日志在处理器里集中管理,路由函数不写 try/except。
CORS
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://front.example.com"], # 精确域名, 别用 *
allow_methods=["*"],
allow_headers=["*"],
)前后端分离必配:浏览器跨域请求先发 OPTIONS 预检,中间件响应 CORS 头。生产 allow_origins 要精确配置,* 加 allow_credentials=True 会失效(浏览器规范)。
GZip 与静态资源
from starlette.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000) # 响应压缩- GZip:大响应压缩,节省带宽,CPU 换带宽
- StaticFiles:静态资源挂载(前端构建产物)
- 中间件顺序:CORS 在最外层,压缩在内层
面试追问
- 中间件怎么写? @app.middleware(“http”),call_next 进入内层,响应返回再加工。不调 call_next 即拦截
- HTTPException 和自定义异常? HTTPException 内置快捷抛错;自定义异常配 exception_handler 统一转 JSON,业务代码不写 try/except
- CORS 怎么配? CORSMiddleware,allow_origins 精确配置。预检请求 OPTIONS 由中间件处理
- 异常处理器的优先级? 先匹配具体异常类型,再匹配父类,再兜底 Exception
- 中间件和依赖的执行顺序? 中间件先于路由和依赖执行(外层)。全局横切用中间件,接口逻辑用依赖