FastAPI 中间件完全指南:从原理到实战,掌控请求响应的全局钩子

【学习记录】FastAPI 中间件完全指南:从原理到实战,掌控请求响应的全局钩子

在 Web 开发中,有很多需求需要全局统一处理 ,比如记录每个请求的日志、验证用户身份、添加跨域响应头、监控接口耗时等。FastAPI 提供了中间件(Middleware) 机制,允许我们在请求到达路由函数之前和响应返回客户端之前插入自定义逻辑。本文从原理、执行顺序到 5 种实战场景,全面解析 FastAPI 中间件的使用方法,并提供完整代码和面试问答。


📌 目录

  1. 什么是中间件?
  2. 中间件的执行时机与洋葱模型
  3. 典型使用场景与完整代码示例
    • 3.1 日志记录中间件
    • 3.2 身份认证中间件
    • 3.3 跨域处理(CORS)
    • 3.4 自定义响应头中间件
    • 3.5 性能监控中间件
  4. 测试与启动
  5. [面试官可能会问 & 推荐回答](#面试官可能会问 & 推荐回答)
  6. 总结与最佳实践

一、什么是中间件?

中间件(Middleware) 是一个在请求到达路径操作函数之前、响应返回给客户端之前执行的函数。它可以对请求和响应进行全局的预处理或后处理,比如:

  • 日志记录
  • 身份验证
  • 添加响应头
  • 性能监控
  • 跨域处理(CORS)
  • 请求限流

在 FastAPI 中,中间件是一个接收 requestcall_next 参数的异步函数

  • request:传入的请求对象(Request 类型)。
  • call_next:将请求传递给下一个中间件或最终的路由处理函数。

中间件必须返回响应 (通常是通过 await call_next(request) 获得响应后,再修改或直接返回)。

基本结构

python 复制代码
@app.middleware("http")
async def my_middleware(request: Request, call_next):
    # 请求前处理(例如:记录开始时间)
    response = await call_next(request)   # 传递给下一个中间件或路由
    # 响应后处理(例如:添加响应头)
    return response

二、中间件的执行时机与洋葱模型

2.1 洋葱模型

中间件的执行顺序遵循经典的洋葱模型:请求从最外层进入,逐层向内,直到路由函数;响应从最内层逐层向外返回。每个中间件都有机会在请求前和响应后执行代码。

2.2 添加顺序与执行顺序的关系

假设我们按顺序添加两个中间件 A 和 B(先 add_middleware(A),再 add_middleware(B)),实际上 B 会包裹 A。执行流程:

  1. 请求先进入 B 的请求前代码
  2. B 调用 call_next,进入 A 的请求前代码
  3. A 调用 call_next,进入路由函数
  4. 路由函数返回响应后,先回到 A 的响应后代码
  5. 再回到 B 的响应后代码

结论:后添加的中间件先执行请求前代码,后执行响应后代码。

2.3 图解

路由函数 中间件A (先添加) 中间件B (后添加) Client 路由函数 中间件A (先添加) 中间件B (后添加) Client #mermaid-svg-PtV170KaWfbgZWLo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PtV170KaWfbgZWLo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PtV170KaWfbgZWLo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PtV170KaWfbgZWLo .error-icon{fill:#552222;}#mermaid-svg-PtV170KaWfbgZWLo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PtV170KaWfbgZWLo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PtV170KaWfbgZWLo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PtV170KaWfbgZWLo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PtV170KaWfbgZWLo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PtV170KaWfbgZWLo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PtV170KaWfbgZWLo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PtV170KaWfbgZWLo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PtV170KaWfbgZWLo .marker.cross{stroke:#333333;}#mermaid-svg-PtV170KaWfbgZWLo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PtV170KaWfbgZWLo p{margin:0;}#mermaid-svg-PtV170KaWfbgZWLo .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PtV170KaWfbgZWLo text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-PtV170KaWfbgZWLo .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PtV170KaWfbgZWLo .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-PtV170KaWfbgZWLo .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-PtV170KaWfbgZWLo .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-PtV170KaWfbgZWLo #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-PtV170KaWfbgZWLo .sequenceNumber{fill:white;}#mermaid-svg-PtV170KaWfbgZWLo #sequencenumber{fill:#333;}#mermaid-svg-PtV170KaWfbgZWLo #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-PtV170KaWfbgZWLo .messageText{fill:#333;stroke:none;}#mermaid-svg-PtV170KaWfbgZWLo .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PtV170KaWfbgZWLo .labelText,#mermaid-svg-PtV170KaWfbgZWLo .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-PtV170KaWfbgZWLo .loopText,#mermaid-svg-PtV170KaWfbgZWLo .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-PtV170KaWfbgZWLo .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PtV170KaWfbgZWLo .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-PtV170KaWfbgZWLo .noteText,#mermaid-svg-PtV170KaWfbgZWLo .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-PtV170KaWfbgZWLo .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PtV170KaWfbgZWLo .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PtV170KaWfbgZWLo .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PtV170KaWfbgZWLo .actorPopupMenu{position:absolute;}#mermaid-svg-PtV170KaWfbgZWLo .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-PtV170KaWfbgZWLo .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PtV170KaWfbgZWLo .actor-man circle,#mermaid-svg-PtV170KaWfbgZWLo line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-PtV170KaWfbgZWLo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求 call_next() call_next() 响应 返回响应 最终响应


三、典型使用场景与完整代码示例

以下是一个集成了 5 种常用中间件 的完整 FastAPI 应用,涵盖日志、认证、CORS、响应头、性能监控。

3.1 日志记录中间件

记录每个请求的方法、路径、状态码和耗时。

python 复制代码
import time
import logging
from fastapi import FastAPI, Request

app = FastAPI()

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
logger = logging.getLogger(__name__)

@app.middleware("http")
async def log_requests(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    duration = time.perf_counter() - start
    logger.info(f"{request.method} {request.url.path} - Status {response.status_code} - {duration:.4f}s")
    return response

3.2 身份认证中间件

检查请求头中的 Authorization: Bearer <token>,对受保护路径进行验证。

python 复制代码
VALID_TOKEN = "secret-token-123"

@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    # 公开路径跳过验证
    if request.url.path in ["/docs", "/openapi.json", "/", "/login"]:
        return await call_next(request)
    
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        return JSONResponse(status_code=401, content={"detail": "Missing or invalid token"})
    
    token = auth_header.split(" ")[1]
    if token != VALID_TOKEN:
        return JSONResponse(status_code=403, content={"detail": "Invalid token"})
    
    # 将用户信息存入 request.state,供路由使用
    request.state.user = "authenticated_user"
    return await call_next(request)

3.3 跨域处理(CORS)

FastAPI 内置了 CORSMiddleware,直接添加即可。

python 复制代码
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],          # 生产环境应限制具体域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

3.4 自定义响应头中间件

为所有响应添加自定义头,如 X-Processed-By: FastAPI

python 复制代码
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Processed-By"] = "FastAPI"
    response.headers["X-API-Version"] = "1.0"
    return response

3.5 性能监控中间件

记录耗时超过阈值的慢请求,可上报监控系统。

python 复制代码
@app.middleware("http")
async def performance_monitor(request: Request, call_next):
    start = time.monotonic()
    response = await call_next(request)
    elapsed = time.monotonic() - start
    if elapsed > 1.0:   # 超过1秒记录警告
        logger.warning(f"Slow request: {request.method} {request.url.path} took {elapsed:.2f}s")
    return response

3.6 整合所有中间件的完整代码

python 复制代码
import time
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 日志配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
logger = logging.getLogger(__name__)

# ---------- 1. 日志中间件 ----------
@app.middleware("http")
async def log_requests(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    duration = time.perf_counter() - start
    logger.info(f"{request.method} {request.url.path} - Status {response.status_code} - {duration:.4f}s")
    return response

# ---------- 2. 认证中间件 ----------
VALID_TOKEN = "secret-token-123"

@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    if request.url.path in ["/docs", "/openapi.json", "/", "/login"]:
        return await call_next(request)
    
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        return JSONResponse(status_code=401, content={"detail": "Missing or invalid token"})
    
    token = auth_header.split(" ")[1]
    if token != VALID_TOKEN:
        return JSONResponse(status_code=403, content={"detail": "Invalid token"})
    
    request.state.user = "authenticated_user"
    return await call_next(request)

# ---------- 3. CORS 中间件 ----------
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ---------- 4. 自定义响应头中间件 ----------
@app.middleware("http")
async def add_custom_header(request: Request, call_next):
    response = await call_next(request)
    response.headers["X-Processed-By"] = "FastAPI"
    response.headers["X-API-Version"] = "1.0"
    return response

# ---------- 5. 性能监控中间件 ----------
@app.middleware("http")
async def performance_monitor(request: Request, call_next):
    start = time.monotonic()
    response = await call_next(request)
    elapsed = time.monotonic() - start
    if elapsed > 1.0:
        logger.warning(f"Slow request: {request.method} {request.url.path} took {elapsed:.2f}s")
    return response

# ---------- 测试路由 ----------
@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.get("/protected")
async def protected(request: Request):
    return {"message": f"Welcome, {request.state.user}"}

四、测试与启动

4.1 启动服务

bash 复制代码
uvicorn main:app --reload

4.2 测试端点

bash 复制代码
# 根路径(公开,无需认证)
curl http://localhost:8000/

# 受保护路径(不带 Token → 401)
curl http://localhost:8000/protected

# 受保护路径(带正确 Token → 200)
curl -H "Authorization: Bearer secret-token-123" http://localhost:8000/protected

# 查看 Swagger 文档(公开)
http://localhost:8000/docs

4.3 查看日志输出

控制台会显示类似:

复制代码
2025-06-04 10:00:01,123 - GET / - Status 200 - 0.0023s
2025-06-04 10:00:05,456 - GET /protected - Status 401 - 0.0012s

五、面试官可能会问 & 推荐回答

Q1:什么是中间件?在 FastAPI 中如何实现?

:中间件是在请求处理链中插入的钩子函数,可以全局地修改请求或响应。FastAPI 中使用 @app.middleware("http") 装饰器定义异步函数,接收 requestcall_next,最后必须调用 call_next(request) 并返回响应。


Q2:中间件的执行顺序是怎样的?为什么后添加的中间件先执行请求前代码?

:执行顺序遵循"洋葱模型"。后添加的中间件包裹先添加的,因此请求进入时先执行后添加中间件的请求前代码,然后层层向内,最终到达路由;响应时逆向返回。这样可以保证每个中间件都能访问到最原始的请求,也能处理最终响应。


Q3:如何实现一个只对特定路径生效的中间件?

:在中间件内部根据 request.url.path 判断,对公开路径直接 return await call_next(request) 跳过处理。例如认证中间件中排除 /docs/ 等路径。


Q4:中间件和依赖项(Dependency)有什么区别?什么时候用中间件,什么时候用依赖?

  • 中间件:全局、对所有请求生效,适合日志、跨域、认证等横切关注点。
  • 依赖项 :更细粒度,可以在具体路由或路由组中复用,适合数据库会话、当前用户对象等需要注入到路径函数的场景。
    选择:需要全局拦截 → 中间件;需要部分路由使用或注入参数 → 依赖项。

Q5:如何捕获中间件中的异常并返回自定义错误响应?

:在中间件内使用 try...except 包裹 await call_next(request),捕获异常后返回自定义的 JSONResponsePlainTextResponse。也可以结合全局异常处理器统一处理。


Q6:FastAPI 的 CORSMiddleware 是如何工作的?为什么需要它?

:浏览器同源策略限制跨域请求,CORSMiddleware 通过在响应头中添加 Access-Control-Allow-Origin 等字段,告诉浏览器允许跨域访问。FastAPI 提供了现成的 CORSMiddleware,只需添加并配置允许的源、方法、头即可。


Q7:中间件的性能影响如何?如何优化?

:中间件会增加每个请求的微小开销。应避免在中间件中执行耗时操作(如同步阻塞 I/O)。可以使用异步库、缓存,或根据路径过滤(只对需要的路径执行)。对于性能监控,可采样或异步上报。


Q8:如何编写测试来验证中间件行为?

:使用 from fastapi.testclient import TestClient 发送请求,检查响应状态码、响应头、日志输出等。例如测试认证中间件:不传 Token 应返回 401,传正确 Token 返回 200。

python 复制代码
from fastapi.testclient import TestClient

client = TestClient(app)

def test_auth():
    response = client.get("/protected")
    assert response.status_code == 401
    
    response = client.get("/protected", headers={"Authorization": "Bearer secret-token-123"})
    assert response.status_code == 200

六、总结与最佳实践

中间件类型 核心作用 注意事项
日志记录 记录请求信息、耗时 避免记录敏感数据
身份认证 验证 Token,拦截未授权请求 公开路径需跳过
CORS 解决跨域问题 生产环境限制 allow_origins
自定义响应头 添加统一标识 避免覆盖重要头
性能监控 发现慢请求 阈值需根据业务调整

最佳实践建议

  • ✅ 中间件应保持轻量,避免阻塞。
  • ✅ 使用 request.state 在中间件与路由间传递数据。
  • ✅ 注意中间件的添加顺序,尤其是认证和日志的顺序(通常认证在前)。
  • ✅ 对静态资源或健康检查路径,可以提前跳过中间件以提升性能。
  • ✅ 为中间件编写单元测试,确保逻辑正确。

掌握中间件,你就能以极低的成本为 FastAPI 应用添加强大的全局能力。希望本文能帮助你写出更健壮、更可维护的 Web 服务。

相关推荐
花酒锄作田14 小时前
Repository 模式在 FastAPI 中的应用
python·fastapi
喜欢的名字被抢了1 天前
FastAPI 接口安全与工程化 JWT、测试和配置管理
安全·fastapi
Maiko Star2 天前
从零落地「AI掘金头条」新闻模块:FastAPI + SQLAlchemy 异步实战
python·fastapi
喜欢的名字被抢了2 天前
FastAPI 接入异步 PostgreSQL 完成任务 CRUD 与数据库迁移
数据库·postgresql·fastapi
Esaka_Forever2 天前
uvicorn main.py 命令说明
fastapi
巴啦啦小魔仙变身3 天前
FastAPI 01 Create Demo
python·fastapi
李昊哲小课4 天前
fastapi sse websocket 奶茶店实时订单看板
人工智能·python·websocket·网络协议·fastapi·sse
李昊哲小课5 天前
fastapi sse websocket 智能家居实时控制台
python·websocket·智能家居·fastapi·sse
赵广陆5 天前
SQLAlchemy ORM 超全教程
python·fastapi
ThsPool5 天前
【ENVI二次开发学习整理 02】IDL语言基础与程序设计
学习·中间件