FastAPI 基于 Starlette,天然继承了 Starlette 强大的中间件 (Middleware)机制。中间件是位于请求进入路由函数之前、响应发出之前的一道”关卡”,可以用来做请求日志记录、跨域处理、认证校验、限流、请求耗时统计等横切关注点。本文系统讲解 FastAPI 中间件的核心用法。
中间件的执行模型 FastAPI 中间件与 Starlette 完全一致:每个请求会按照添加顺序 依次穿过各层中间件,到达路由处理函数后,再按相反顺序 穿过中间件返回。下面这张示意图可以帮你理解:
1 请求 → [中间件A → 中间件B → 路由函数] → 中间件B → 中间件A → 响应
中间件本质上是一个 ASGI 应用,包裹着下一个 ASGI 应用(内层中间件或真正的路由应用)。理解了洋葱模型,中间件的开发就简单了。
内置中间件一览 FastAPI/Starlette 内置了几个常用的中间件,直接拿来用即可:
中间件
用途
CORSMiddleware
处理跨域资源共享
HTTPSRedirectMiddleware
强制 HTTP 跳转 HTTPS
TrustedHostMiddleware
只允许指定的 Host 头
GZipMiddleware
对响应体启用 GZip 压缩
CORS 中间件 前后端分离项目中,CORS 是最常用的中间件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddlewareapp = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["https://example.com" , "http://localhost:3000" ], allow_credentials=True , allow_methods=["GET" , "POST" , "PUT" , "DELETE" ], allow_headers=["*" ], expose_headers=["X-Custom-Header" ], max_age=3600 , )
参数说明:
allow_origins:允许的源站列表,生产环境一定要指定具体域名,不要用 ["*"]
allow_credentials:是否允许携带 Cookie,设为 True 时 allow_origins 不能用 *
allow_methods:允许的 HTTP 方法
allow_headers:允许的请求头
max_age:预检请求缓存时间(秒)
其他内置中间件 1 2 3 4 5 6 7 8 9 10 11 12 from fastapi.middleware.httpsredirect import HTTPSRedirectMiddlewarefrom fastapi.middleware.trustedhost import TrustedHostMiddlewarefrom fastapi.middleware.gzip import GZipMiddlewareapp.add_middleware(HTTPSRedirectMiddleware) app.add_middleware(TrustedHostMiddleware, allowed_hosts=["api.example.com" ]) app.add_middleware(GZipMiddleware, minimum_size=1024 )
自定义中间件 自定义中间件有两种写法:函数式 (使用装饰器 @app.middleware)和类式 (继承 BaseHTTPMiddleware)。
函数式中间件:请求日志 这是最简单的方式,适合轻量级需求:
1 2 3 4 5 6 7 8 9 10 11 12 13 import timefrom fastapi import FastAPI, Requestapp = FastAPI() @app.middleware("http" ) async def log_middleware (request: Request, call_next ): """记录每个请求的方法、路径和耗时""" start = time.time() response = await call_next(request) elapsed = time.time() - start print (f"{request.method} {request.url.path} → {response.status_code} ({elapsed:.4 f} s)" ) return response
核心要点:
装饰器必须指定 "http" 参数
call_next(request) 是调用下一层(内层中间件或路由函数),返回 Response 对象
在 call_next 之前写的代码在请求阶段执行,之后写的代码在响应阶段执行
类式中间件:请求限流器 当中间件逻辑较复杂、需要构造函数参数或状态保持时,推荐用类式写法。下面实现一个基于 IP 的简易令牌桶限流器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import timefrom collections import defaultdictfrom fastapi import FastAPI, Requestfrom starlette.middleware.base import BaseHTTPMiddlewarefrom starlette.responses import JSONResponseclass RateLimitMiddleware (BaseHTTPMiddleware ): """简易令牌桶限流:每个 IP 每秒最多 max_requests 次请求""" def __init__ (self, app, max_requests: int = 10 , window: int = 1 ): super ().__init__(app) self .max_requests = max_requests self .window = window self .buckets: dict [str , list [float ]] = defaultdict(list ) async def dispatch (self, request: Request, call_next ): client_ip = request.client.host now = time.time() bucket = self .buckets[client_ip] self .buckets[client_ip] = [t for t in bucket if now - t < self .window] if len (self .buckets[client_ip]) >= self .max_requests: return JSONResponse( status_code=429 , content={"detail" : "请求过于频繁,请稍后再试" }, headers={"Retry-After" : str (self .window)}, ) self .buckets[client_ip].append(now) return await call_next(request)
注册使用:
1 app.add_middleware(RateLimitMiddleware, max_requests=20 , window=1 )
纯 ASGI 中间件:更高粒度控制 如果需要完全控制 ASGI 事件(如处理 WebSocket),可以直接写纯 ASGI 中间件:
1 2 3 4 5 6 7 8 9 10 11 from starlette.types import ASGIApp, Scope, Receive, Sendclass CustomASGIMiddleware : def __init__ (self, app: ASGIApp ): self .app = app async def __call__ (self, scope: Scope, receive: Receive, send: Send ): if scope["type" ] == "http" : print (f"ASGI 中间件:{scope['method' ]} {scope['path' ]} " ) await self .app(scope, receive, send)
中间件的顺序问题 中间件的执行顺序与 add_middleware 的调用顺序相同 ——先添加的在外层,先拦截请求、最后放行响应。
1 2 3 4 app.add_middleware(MiddlewareA) app.add_middleware(MiddlewareB) app.add_middleware(MiddlewareC)
请求链路:A → B → C → 路由,响应链路:路由 → C → B → A。
最佳实践排序 :
最外层 :日志、耗时统计(尽量覆盖所有请求)
次外层 :CORS、安全头
次内层 :认证鉴权
最内层 :请求数据预处理、响应格式化
实际场景:响应头注入 一个常见的需求是为所有响应统一注入安全头:
1 2 3 4 5 6 7 8 @app.middleware("http" ) async def security_headers_middleware (request: Request, call_next ): response = await call_next(request) response.headers["X-Content-Type-Options" ] = "nosniff" response.headers["X-Frame-Options" ] = "DENY" response.headers["X-XSS-Protection" ] = "1; mode=block" response.headers["Strict-Transport-Security" ] = "max-age=31536000" return response
中间件 vs 依赖注入 很多初学者会困惑:都是请求前执行的逻辑,中间件和 Depends 依赖注入有何区别?
维度
中间件
Depends 依赖注入
作用范围
全局,覆盖所有路由
可按路由、路由分组、全局配置
执行时机
在路由匹配之前
路由匹配之后
请求信息
仅有 Request 对象
有解析后的路径/查询/请求体参数
修改响应
可以直接修改 Response
不能直接修改 Response
适用场景
日志、CORS、安全头、限流
鉴权、数据库会话、参数校验
一句话总结:影响”所有请求”用中间件,影响”特定业务逻辑”用 Depends 。
小结 FastAPI 的中间件系统继承自 Starlette,简洁而强大。日常开发中:
优先级最高 的中间件是 CORS,前后端分离几乎必配
函数式 @app.middleware("http") 覆盖 80% 的场景
需要参数化或复杂逻辑时,使用类式 BaseHTTPMiddleware
牢记洋葱模型,合理安排中间件添加顺序
掌握中间件,你就掌握了 FastAPI 请求生命周期的”总开关”。