!文章内容如有错误或排版问题,请提交反馈,非常感谢!
在 FastAPI 中接入中间件(Middleware)非常简单,因为 FastAPI 基于 Starlette 框架,可以直接使用 Starlette 的中间件系统。
中间件基础概念
中间件是 FastAPI 中用于拦截HTTP 请求和响应的组件,它在请求到达路由处理函数之前和响应返回客户端之前执行。
- 类比:类似于”洋葱模型”,中间件层层包裹路由处理函数,可修改请求或响应。
- 核心作用:处理全局性或跨路由的通用逻辑(如日志、鉴权、限流)。
中间件的执行流程
中间件按添加顺序 逆序执行(类似洋葱模型):
app.add_middleware(MiddlewareA) # 外层 app.add_middleware(MiddlewareB) # 内层 # 执行顺序: # MiddlewareA 请求前 → MiddlewareB 请求前 → 路由处理 # MiddlewareB 响应后 → MiddlewareA 响应后
关键特性:
- 中间件按添加顺序反向嵌套 执行(先添加的中间件在外层)。
- 可修改请求对象(如添加 Headers)或响应对象(如修改状态码)。
中间件的核心用途
场景 | 示例 |
日志记录 | 记录请求的 URL、方法、耗时,响应的状态码。 |
身份验证 | 验证 API 密钥或 JWT Token,拦截非法请求。 |
跨域资源共享(CORS) | 添加 Access-Control-Allow-Origin 头,支持前端跨域请求。 |
请求限流 | 限制单个 IP 的请求频率,防止滥用。 |
异常处理 | 捕获全局异常,返回统一格式的错误响应。 |
数据预处理 | 修改请求 Body(如解密数据)或添加全局请求头。 |
常用内置中间件及使用
CORS 跨域中间件
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], # 允许的源列表 allow_credentials=True, allow_methods=["*"], # 允许的 HTTP 方法 allow_headers=["*"], # 允许的请求头 )
GZip压缩中间件
from fastapi.middleware.gzip import GZipMiddleware app.add_middleware( GZipMiddleware, minimum_size=1000 # 仅压缩大于 1000 字节的响应 )
HTTPS重定向中间件
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware app.add_middleware(HTTPSRedirectMiddleware) # 强制所有请求使用 HTTPS
信任代理头部中间件
from fastapi.middleware.trustedhost import TrustedHostMiddleware app.add_middleware( TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"] # 允许的 Host 头 )
自定义中间件开发
基础模板
from fastapi import Request from fastapi.responses import Response async def custom_middleware(request: Request, call_next): # 请求处理前逻辑 print(f"Request started: {request.url}") response = await call_next(request) # 响应返回前逻辑 response.headers["X-Custom-Header"] = "FastAPI" return response app.middleware("http")(custom_middleware)
示例:请求耗时统计
import time async def timing_middleware(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) return response app.middleware("http")(timing_middleware)
示例:API密钥认证
```html from fastapi import HTTPException, status @app.middleware("http") async def verify_api_key(request: Request, call_next): api_key = request.headers.get("X-API-Key") if api_key != "SECRET_KEY": raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Invalid API Key" ) return await call_next(request)
第三方中间件集成
Prometheus监控中间件
pip install starlette-prometheus from starlette_prometheus import PrometheusMiddleware app.add_middleware(PrometheusMiddleware)
Sentry错误监控
pip install sentry-sdk import sentry_sdk from sentry_sdk.integrations.asgi import SentryAsgiMiddleware sentry_sdk.init(dsn="your-sentry-dsn") app.add_middleware(SentryAsgiMiddleware)
同步 vs 异步中间件
核心对比速览
特性 | 同步中间件 | 异步中间件 |
定义方式 | 普通函数(def) | 协程函数(async def) |
I/O操作兼容性 | 阻塞性操作(如同步数据库查询) | 非阻塞异步操作(如 asyncpg、httpx) |
执行线程 | 在主线程中运行,可能阻塞事件循环 | 在事件循环中异步执行,不阻塞其他任务 |
性能影响 | 高并发场景下性能较差 | 高并发下性能更优 |
适用场景 | 简单逻辑、无I/O或CPU密集型任务 | 涉及网络请求、数据库访问等异步操作 |
代码定义对比
同步中间件
from fastapi import Request @app.middleware("http") def sync_middleware(request: Request, call_next): # 同步操作(可能阻塞事件循环) print("Sync Middleware: Request received") response = call_next(request) return response
异步中间件
from fastapi import Request @app.middleware("http") async def async_middleware(request: Request, call_next): # 异步操作(非阻塞) print("Async Middleware: Request received") response = await call_next(request) return response
执行流程差异
同步中间件
┌───────────────────────┐ │ 同步中间件 │→阻塞事件循环,直到处理完成 └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 路由处理函数 │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 同步中间件 │ └───────────────────────┘
异步中间件
┌───────────────────────┐ │ 异步中间件 │→挂起协程,释放事件循环处理其他请求 └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 路由处理函数 │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 异步中间件 │ └───────────────────────┘
性能关键点
场景 | 同步中间件 | 异步中间件 |
无I/O操作 | 无显著差异 | 无显著差异 |
涉及网络/数据库请求 | 阻塞事件循环,导致请求堆积,延迟增加 | 非阻塞,高并发下吞吐量更高 |
CPU密集型任务 | 可能拖慢整体性能(如图像处理) | 同样会阻塞事件循环,需用多进程优化 |
使用场景示例
适合同步中间件的场景
简单日志记录:
@app.middleware("http") def log_request(request: Request, call_next): print(f"Request: {request.method} {request.url}") response = call_next(request) return response
同步数据处理(如CPU计算):
def heavy_calculation(): # 模拟CPU密集型任务 return sum(i*i for i in range(10**6)) @app.middleware("http") def cpu_middleware(request: Request, call_next): result = heavy_calculation() # 同步阻塞 response = call_next(request) response.headers["X-Calculation"] = str(result) return response
适合异步中间件的场景
异步数据库查询
“`
@app.middleware("http") async def db_middleware(request: Request, call_next): async with AsyncSession() as session: request.state.db = session response = await call_next(request) return response
调用外部API:
import httpx @app.middleware("http") async def external_api_middleware(request: Request, call_next): async with httpx.AsyncClient() as client: # 非阻塞请求外部服务 resp = await client.get("https://api.example.com/data") request.state.external_data = resp.json() response = await call_next(request) return response
混合使用的注意事项
若同时存在同步和异步中间件,需注意执行顺序对性能的影响:
# 添加顺序决定执行顺序(外层到内层)
app.add_middleware(sync_middleware) # 外层同步中间件
app.add_middleware(async_middleware) # 内层异步中间件
- 潜在问题:外层的同步中间件若包含阻塞操作,会降低内层异步中间件的性能优势。
- 最佳实践:尽量统一中间件类型(全同步或全异步),或确保外层中间件无阻塞。
异常处理差异
异常类型 | 同步中间件 | 异步中间件 |
同步异常 | 直接抛出,中断请求流程 | 需用 try/except 捕获 |
异步异常 | 无法正确处理(需手动处理) | 通过 await 自动传播异常 |
总结:如何选择?
决策因素 | 选择同步中间件 | 选择异步中间件 |
是否涉及I/O操作 | ❌避免使用(除非逻辑极简单) | ✅优先使用 |
代码库整体异步程度 | 项目以同步代码为主 | 项目主要使用异步库(如 asyncpg) |
高并发需求 | ❌不适用 | ✅必要选择 |
维护成本 | 简单易读 | 需熟悉异步编程概念 |
最终建议:
- 默认使用异步中间件,尤其是在涉及网络、数据库等I/O操作时。
- 仅在无I/O且逻辑简单时使用同步中间件,避免阻塞事件循环。
- 通过性能测试(如locust)验证中间件对吞吐量的影响。
- 避免在中间件中进行耗时同步操作
- 使用BackgroundTasks 处理非即时需求(如日志写入)
中间件与依赖注入
核心概念对比
特性 | 中间件(Middleware) | 依赖注入(Dependency Injection) |
定位 | 全局或路由级别的请求/响应拦截器 | 面向路径操作函数的参数注入机制 |
执行时机 | 在请求到达路由前和响应返回前执行(包裹整个处理流程) | 在路径操作函数执行前解析依赖,并注入到函数参数中 |
主要用途 | 处理与HTTP协议相关的横切关注点(如日志、认证头) | 解耦业务逻辑、复用代码(如获取数据库连接、用户鉴权) |
作用范围 | 全局或路由组级别 | 精确到单个路径操作函数或子路由 |
数据传递 | 通过请求状态(request.state)传递数据 | 直接通过函数参数传递数据 |
修改请求/响应能力 | ✅可修改请求或响应对象(如添加Headers) | ❌仅提供数据,不直接操作请求/响应对象 |
使用场景对比
中间件适用场景
全局性处理:
from fastapi import Request @app.middleware("http") async def log_requests(request: Request, call_next): print(f"Request: {request.method} {request.url}") response = await call_next(request) print(f"Response: {response.status_code}") return response
- 日志记录、异常捕获、跨域(CORS)、限流、IP白名单等。
- 修改请求/响应头(如添加X-Request-ID)。
路由组中间件:
sub_app = FastAPI() sub_app.middleware("http")(auth_middleware)
依赖注入适用场景
复用逻辑:
from fastapi import Depends def get_db(): db = SessionLocal() try: yield db finally: db.close() @app.get("/items/") async def read_items(db: Session = Depends(get_db)): return db.query(Item).all()
- 数据库连接、用户身份验证(如get_current_user)。
- 请求参数预处理(如分页参数解析)。
分层依赖
def get_query_params(q: str = None, page: int = 1): return {"q": q, "page": page} @app.get("/search/") async def search(params: dict = Depends(get_query_params)): return params
执行顺序与数据流
中间件与依赖注入的协作流程
┌───────────────────────┐ │ 中间件1 │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 中间件2 │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 依赖注入解析(如数据库) │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 路径操作函数(路由) │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 中间件2 │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ 中间件1 │ └───────────────────────┘
关键点:
- 中间件在依赖注入之前执行(如认证中间件先验证Token,依赖注入再获取用户信息)。
- 依赖注入的参数在路由函数执行前解析完成。
代码示例对比
用户认证实现对比
中间件方式(处理全局认证):
@app.middleware("http") async def auth_middleware(request: Request, call_next): token = request.headers.get("Authorization") if not validate_token(token): return JSONResponse(status_code=403, content={"error": "Forbidden"}) request.state.user = get_user_from_token(token) return await call_next(request)
依赖注入方式(精确到路由):
def get_current_user(token: str = Header(...)): if not validate_token(token): raise HTTPException(status_code=403, detail="Forbidden") return get_user_from_token(token) @app.get("/profile/") async def profile(user: User = Depends(get_current_user)): return {"user": user.name}
数据传递对比
中间件传递数据:
request.state.user_id = 123 # 中间件中设置
依赖注入传递数据:
user_id: int = Depends(get_user_id) # 直接作为参数注入
性能与灵活性
维度 | 中间件 | 依赖注入 |
性能影响 | 全局生效,可能影响所有请求 | 按需注入,仅影响使用该依赖的路由 |
可测试性 | 需模拟请求对象 | 直接调用依赖函数,易于单元测试 |
代码耦合度 | 与HTTP层耦合度高 | 业务逻辑解耦,依赖项可独立替换 |
复用性 | 适用于横跨多个路由的通用逻辑 | 支持细粒度复用(如单个参数解析) |
总结:何时使用哪种?
场景 | 选择中间件 | 选择依赖注入 |
需要修改请求/响应头或Body | ✅ | ❌ |
全局性日志或异常处理 | ✅ | ❌ |
路由级别的用户鉴权 | ❌(除非路由组统一处理) | ✅ |
数据库连接管理 | ❌ | ✅ |
复杂业务逻辑解耦 | ❌ | ✅ |
跨域(CORS)配置 | ✅(使用 CORSMiddleware) | ❌ |
最终结论:
- 中间件:处理HTTP协议层面、全局性的横切关注点。
- 依赖注入:管理业务逻辑的依赖关系,实现代码解耦和复用。
两者可结合使用(如中间件处理Token验证,依赖注入获取用户对象),但需明确职责边界。
参考链接: