中间件
写给初学者:每个概念都从”为什么需要它”开始讲,配合通俗比喻和完整可运行代码。
一、什么是中间件?
中间件是在每个请求到达接口之前、和每个响应返回客户端之前都会执行的代码。
通俗比喻:中间件就像机场安检——每个乘客(请求)登机(进入接口)前都得过一遍,不管你要坐哪个航班。
二、基本用法
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import time
app = FastAPI()
# 效果:每个 HTTP 请求都会经过这个函数,不管是 GET、POST 还是其他方法
@app.middleware("http")
async def add_process_time(request: Request, call_next):
"""记录每个请求的处理时间"""
# request 是当前请求对象,包含 URL、方法、请求头等信息
# call_next 是一个函数,用来把请求传给下一个环节
start_time = time.time() # ① 请求进来时,记录开始时间戳
# ② call_next(request) 把请求传给下一个中间件或接口函数
# 必须加 await,因为它是异步操作
# 不调用 call_next 的话,请求就到不了接口,被中间件"吃掉"了
response = await call_next(request)
# ③ 接口处理完后,计算总共花了多少秒
process_time = time.time() - start_time
# ④ 把耗时写进响应头(客户端可以在响应头里看到 X-Process-Time)
response.headers["X-Process-Time"] = str(round(process_time, 4))
return response # ⑤ 返回响应给客户端
call_next 的作用:把请求传递给下一个处理环节(下一个中间件或最终的接口函数),拿到响应后你可以修改它。
三、多个中间件的执行顺序
# 中间件 A:先注册的,所以请求先经过它
@app.middleware("http")
async def middleware_a(request: Request, call_next):
print("A: 请求进来") # ① 请求最先进 A
response = await call_next(request) # ② A 把请求传给下一个(B)
print("A: 响应出去") # ⑤ 响应最后经过 A(收尾)
return response # ⑥ A 把响应返回给客户端
# 中间件 B:后注册的,所以请求第二个经过它
@app.middleware("http")
async def middleware_b(request: Request, call_next):
print("B: 请求进来") # ③ B 收到请求
response = await call_next(request) # ④ B 把请求传给接口函数
print("B: 响应出去") # 接口返回后,B 先处理响应
return response # B 把响应传回给 A
执行顺序总结:请求进来按注册顺序 A→B,响应出去按反序 B→A。就像穿衣服先穿后脱,脱的顺序和穿的顺序相反。
执行顺序(洋葱模型):
graph LR start["请求进来"] --> A_in["A(进)"] A_in --> B_in["B(进)"] B_in --> handler["接口函数"] handler --> B_out["B(出)"] B_out --> A_out["A(出)"] A_out --> end_["响应出去"]
类比:穿衣服——先穿内衣(A),再穿外套(B)。脱的时候反过来——先脱外套(B),再脱内衣(A)。
四、实用中间件示例
4.1 请求日志
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.middleware("http")
async def log_requests(request: Request, call_next):
# request.method → HTTP 方法,如 "GET"、"POST"
# request.url.path → 请求路径,如 "/users"、"/items/1"
logger.info(f"→ {request.method} {request.url.path}")
response = await call_next(request)
# response.status_code → HTTP 状态码,如 200、404、500
logger.info(f"← {request.method} {request.url.path} → {response.status_code}")
return response
4.2 全局异常兜底
@app.middleware("http")
async def catch_exceptions(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
logger.error(f"未捕获的异常: {e}")
return JSONResponse(
status_code=500,
content={"error": "服务器内部错误", "detail": str(e)}
)
速查表
| 概念 | 一句话解释 | 关键代码 |
|---|---|---|
| 中间件 | 每个请求都经过的处理逻辑 | @app.middleware("http") |
call_next | 把请求传给下一个环节 | response = await call_next(request) |
| 洋葱模型 | 请求进来 A→B→接口,响应出去 接口→B→A | 中间件嵌套执行 |
▶ 对应原理:04-中间件Middleware机制
速记卡(面试闪卡)
Q1:一句话讲清「中间件」到底是什么?
A:中间件是请求进接口前、响应返客户端前都会执行的代码,像机场安检每个请求都过一遍。
Q2:一、什么是中间件 —— 怎么理解?
A:中间件是每个请求到达接口之前、响应返回之前都跑的代码。类比机场安检:每个乘客(请求)登机(进接口)前都得过一遍,不管坐哪个航班。用 @app.middleware(“http”) 注册,拿到 request 和 call_next。
Q3:二、基本用法与 call_next —— 怎么理解?
A:call_next(request) 把请求传给下一环节(下一个中间件或接口),拿到响应后可修改再返回。不调 call_next 请求就到不了接口、被”吃掉”。典型:记录处理耗时写进响应头 X-Process-Time。
Q4:三、多个中间件的执行顺序(洋葱模型) —— 怎么理解?
A:请求按注册顺序 A→B 进,响应按反序 B→A 出——像穿衣服先穿后脱、脱的顺序相反。A 先打印”请求进来”、最后打印”响应出去”。这就是洋葱模型:层层嵌套,进和出对称。
Q5:四、实用中间件示例 —— 怎么理解?
A:常见两种:① 请求日志——记 method+path 与 status_code,进前后各打一行;② 全局异常兜底——try 包 call_next,捕获异常返回 JSONResponse 500,避免未捕获异常裸奔。还有 CORS、鉴权、限流等都可放中间件统一处理。
Q6:核心速记主线有哪些?
-
中间件:请求进接口前、响应返客户端前都执行,@app.middleware(“http”)
-
call_next 把请求传下一环,不调则请求被吃掉
-
洋葱模型:请求 A→B 进,响应 B→A 出(穿脱衣服)
-
示例:请求日志、全局异常兜底(JSONResponse 500)
-
统一处理:CORS、鉴权、限流
口诀
A:中间件是安检门,请求进出都过堂
call_next 传下一环,不调就被锁一旁
多个注册像洋葱,进 A 出 B 反序行
日志异常兜个底,穿脱对称两相当
相关链接
-
目录:00-FastAPI
-
上一篇:06-依赖注入
-
下一篇:08-后台任务与CORS