中间件

写给初学者:每个概念都从”为什么需要它”开始讲,配合通俗比喻和完整可运行代码。


一、什么是中间件?

中间件是在每个请求到达接口之前、和每个响应返回客户端之前都会执行的代码。

通俗比喻:中间件就像机场安检——每个乘客(请求)登机(进入接口)前都得过一遍,不管你要坐哪个航班。


二、基本用法

 
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 反序行

日志异常兜个底,穿脱对称两相当

相关链接


技术学习路线图 > 工程化篇

相关链接