FastAPI中间件
一、中间件核心概念
在 FastAPI 中,中间件是介于「客户端请求」和「应用程序核心路由逻辑」之间的轻量级软件层,本质是一个异步函数,能够拦截所有请求和响应,实现统一的全局处理逻辑。
中间件的核心作用
中间件主要用于处理所有请求/响应的公共逻辑,无需在每个路由中重复编写,常见用途包括:
- 拦截客户端传入的请求,进行前置处理(如验证请求头、记录请求日志、解析Token);
- 调用应用程序核心路由逻辑,获取响应结果;
- 拦截路由返回的响应,进行后置处理(如添加响应头、统一响应格式、记录响应耗时);
- 全局异常捕获、跨域设置、请求限流等全局统一操作。
中间件的执行流程
- 客户端发送请求到 FastAPI 应用;
- 请求先经过所有注册的中间件(按注册顺序执行前置处理);
- 中间件通过
call_next(request)调用下一个中间件,最终调用对应的路由处理函数; - 路由处理函数返回响应,响应再按「反向顺序」经过所有中间件(执行后置处理);
- 最终处理后的响应返回给客户端。
二、中间件基本结构
FastAPI 提供 @app.middleware("http") 装饰器来定义中间件,中间件函数必须遵循固定的参数和返回值规范,以下是最基础的中间件模板,可直接复用:
from fastapi import FastAPI, Request, Response
import time
# 1. 创建 FastAPI 应用实例
app = FastAPI()
# 2. 定义中间件:使用 @app.middleware("http") 装饰器
@app.middleware("http")
async def custom_middleware(request: Request, call_next):
"""
自定义基础中间件模板
Args:
request: 请求对象,包含客户端请求的所有信息(方法、URL、请求头、请求体等)
call_next: 可调用对象,用于调用下一个中间件或路由处理函数,必须传入 request
Returns:
Response: 处理后的响应对象,必须返回 Response 或其子类(如 JSONResponse)
"""
# ------------------- 1. 请求前处理(前置逻辑)-------------------
# 示例:记录请求开始时间(用于计算接口耗时)
start_time = time.time()
# 可添加其他前置操作:验证请求头、打印请求信息、解析Token等
# ------------------- 2. 调用后续逻辑(核心)-------------------
# 调用下一个中间件或路由函数,获取响应对象
# 注意:必须使用 await 调用 call_next,且传入 request 参数
response = await call_next(request)
# ------------------- 3. 响应后处理(后置逻辑)-------------------
# 示例:计算接口耗时,添加到响应头中
process_time = time.time() - start_time
# 给响应添加自定义响应头(X-开头为自定义头,避免与标准头冲突)
response.headers["X-Process-Time"] = str(process_time)
# 可添加其他后置操作:记录响应日志、统一响应格式、处理异常等
# ------------------- 4. 返回响应-------------------
return response
# 测试路由:用于验证中间件是否生效
@app.get("/test")
async def test_route():
return {"message": "测试中间件生效"}
三、中间件关键组件详解
中间件的核心是 request、call_next 和 response 三个组件,掌握其用法是编写中间件的关键,以下是详细说明:
3.1 request: Request(请求对象)
由 FastAPI 自动传入,包含客户端请求的所有信息,常用属性和方法:
request.method:请求方法(GET、POST、PUT、DELETE 等);request.url:请求完整 URL(如 http://127.0.0.1:8000/test);request.headers:请求头(字典格式,可获取 Token、Content-Type 等);request.query_params:查询参数(如 /test?id=123 中的 id);await request.body():获取请求体(异步方法,需用 await 调用);request.client:客户端信息(IP 和端口,如 ('127.0.0.1', 54321))。
3.2 call_next(调用链函数)
一个可调用的异步函数,核心作用是「传递请求到下一个环节」,必须满足两个要求:
- 调用时必须传入
request对象(不可省略,否则后续环节无法获取请求信息); - 必须使用
await调用(因为 call_next 是异步函数); - 返回值是
Response对象(即路由或下一个中间件返回的响应)。
注意:如果在中间件中不调用 call_next(request),请求将被拦截,无法到达路由处理函数,客户端会一直等待响应(超时)。
3.3 response: Response(响应对象)
由 call_next(request) 返回,包含路由处理后的响应信息,常用操作:
response.status_code:响应状态码(200、404、500 等);response.headers:响应头(可添加、修改、删除响应头信息);response.body:响应体(bytes 格式,可修改响应内容)。
四、实战案例:常用中间件实现
以下是两个最常用的中间件实战案例,可直接复制到项目中使用,覆盖「请求日志记录」和「全局响应头设置」两个高频场景。
案例1:请求日志记录中间件
功能:记录所有请求的「请求方法、请求URL、客户端IP」,以及响应的「状态码、处理耗时」,便于调试和问题排查(结合 logging 模块)。
from fastapi import FastAPI, Request
import time
import logging
# 1. 配置日志(格式化输出,便于查看)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# 2. 创建 FastAPI 应用
app = FastAPI()
# 3. 定义日志记录中间件
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""
全局请求日志记录中间件
记录请求信息、响应状态码、处理耗时
"""
# 前置处理:记录请求信息
client_ip = request.client.host # 客户端IP
method = request.method # 请求方法
url = str(request.url) # 请求URL
logger.info(f"收到请求 | IP: {client_ip}, 方法: {method}, URL: {url}")
# 记录请求开始时间(用于计算耗时)
start_time = time.time()
# 调用下一个中间件/路由
response = await call_next(request)
# 后置处理:记录响应信息和耗时
process_time = round(time.time() - start_time, 4) # 保留4位小数
status_code = response.status_code # 响应状态码
logger.info(f"返回响应 | 状态码: {status_code}, 耗时: {process_time}s")
return response
# 测试路由
@app.get("/user/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "message": "查询用户成功"}
@app.post("/user")
async def create_user():
return {"message": "创建用户成功"}, 201
案例2:全局响应头中间件
功能:给所有响应统一添加自定义响应头,如接口版本、服务器标识、跨域相关头,无需在每个路由中单独设置。
from fastapi import FastAPI, Request, Response
app = FastAPI()
# 定义全局响应头中间件
@app.middleware("http")
async def add_global_headers(request: Request, call_next):
"""
全局响应头中间件
给所有响应添加统一的自定义响应头
"""
# 调用下一个中间件/路由,获取响应
response = await call_next(request)
# 后置处理:添加自定义响应头
response.headers["X-API-Version"] = "v1.0.0" # 接口版本
response.headers["X-Server"] = "FastAPI-Server" # 服务器标识
response.headers["Access-Control-Allow-Origin"] = "*" # 跨域允许(开发环境)
return response
# 测试路由:响应会自动带上上面的3个响应头
@app.get("/demo")
async def demo():
return {"message": "全局响应头测试成功"}
五、多个中间件的注册与执行顺序
FastAPI 支持注册多个中间件,执行顺序遵循「注册顺序=前置处理顺序,反向顺序=后置处理顺序」,以下是示例说明:
from fastapi import FastAPI, Request
app = FastAPI()
# 中间件1:第一个注册,最先执行前置处理,最后执行后置处理
@app.middleware("http")
async def middleware1(request: Request, call_next):
print("中间件1:前置处理")
response = await call_next(request)
print("中间件1:后置处理")
return response
# 中间件2:第二个注册,第二个执行前置处理,第二个执行后置处理
@app.middleware("http")
async def middleware2(request: Request, call_next):
print("中间件2:前置处理")
response = await call_next(request)
print("中间件2:后置处理")
return response
# 测试路由
@app.get("/test")
async def test():
print("路由函数执行")
return {"message": "测试多个中间件"}
执行顺序输出
中间件1:前置处理
中间件2:前置处理
路由函数执行
中间件2:后置处理
中间件1:后置处理
六、关键注意事项
- 异步要求:中间件函数必须是异步函数(
async def),call_next(request)必须用await调用,否则会报错。 - 返回值要求:中间件必须返回
Response对象或其子类(如JSONResponse、HTMLResponse),不能返回普通字典或字符串。 - 请求体读取:如果在中间件中读取了请求体(
await request.body()),后续路由中再读取请求体会为空,需使用request.body()的缓存机制(如body = await request.body(),再通过Request(scope=request.scope, receive=lambda: {"type": "http.request", "body": body})重新构造请求)。 - 开发与生产环境区分:跨域相关头(如
Access-Control-Allow-Origin: *)仅适合开发环境,生产环境需指定具体的客户端域名,避免安全风险。 - 中间件粒度:中间件用于全局公共逻辑,单个中间件职责尽量单一(如只做日志记录、只加响应头),避免一个中间件包含过多逻辑,便于维护。
七、常见问题排查
- 问题1:中间件不生效:检查是否使用
@app.middleware("http")装饰器,是否在路由注册前定义中间件(FastAPI 会按代码顺序注册中间件,路由需在中间件之后定义)。 - 问题2:调用 call_next 时报错:确认 call_next 传入的是
request对象,且中间件是异步函数、call_next 用 await 调用。 - 问题3:路由无法读取请求体:检查中间件是否读取了请求体且未重新构造请求,需缓存请求体后重新传入后续环节。
- 问题4:响应头添加失败:确认 response 是
Response对象,而非普通字典;部分响应头(如Content-Length)是自动生成的,手动修改可能不生效。

浙公网安备 33010602011771号