前言

在 Python Web 开发中,中间件(Middleware)是一个非常关键但常被初学者忽视的概念。很多人能写接口,却对请求是如何被一步步处理的缺乏清晰认识,而中间件正是理解这一过程的核心。
本文将围绕“中间件是什么、为什么存在、如何工作、如何在 FastAPI 中使用以及实际开发中的设计思路”进行系统讲解,帮助你建立完整认知。


一、中间件到底是什么

从本质上讲,中间件是一段位于请求与响应之间的处理逻辑。

当客户端发起请求时,这个请求并不会直接进入你的接口函数,而是会先经过一层或多层中间件处理;同样,接口返回的响应也会经过这些中间件再返回给客户端。

可以用一个流程来理解:

请求 → 中间件A → 中间件B → 路由函数 → 中间件B → 中间件A → 响应

这说明两件事情:

  • 请求阶段:从外到内逐层进入
  • 响应阶段:从内到外逐层返回

二、为什么需要中间件

在实际开发中,有很多“横向功能”是所有接口都需要的,例如:

  • 记录请求日志
  • 校验用户身份
  • 控制访问权限
  • 统计接口耗时
  • 处理异常
  • 统一响应格式
  • 处理跨域请求

这些逻辑如果写在每一个接口里,会带来几个问题:

  • 代码重复
  • 难以维护
  • 修改成本高

中间件的作用就是把这些“通用逻辑”统一抽离出来,在请求链路中集中处理。

可以理解为:中间件是为整个应用服务的,而不是某一个接口。


三、中间件的执行机制(重点理解)

理解执行顺序是掌握中间件的关键。

假设有两个中间件:

  • middleware1
  • middleware2

执行流程如下:

  1. 请求进入 middleware1(前置逻辑)
  2. 进入 middleware2(前置逻辑)
  3. 进入路由函数(业务逻辑)
  4. 返回 middleware2(后置逻辑)
  5. 返回 middleware1(后置逻辑)
  6. 响应返回客户端

这种结构本质上是一个“嵌套调用”或“栈结构”。


四、用纯 Python 模拟中间件原理

在学习框架之前,先用最简单的代码理解本质。

def middleware1(next_func):
    def wrapper():
        print("middleware1 before")
        next_func()
        print("middleware1 after")
    return wrapper


def middleware2(next_func):
    def wrapper():
        print("middleware2 before")
        next_func()
        print("middleware2 after")
    return wrapper


def view():
    print("执行核心业务逻辑")


app = middleware1(middleware2(view))

app()

执行结果:

middleware1 before
middleware2 before
执行核心业务逻辑
middleware2 after
middleware1 after

这里体现了两个关键点:

  • 中间件本质是函数嵌套
  • 执行顺序符合先进后出

五、FastAPI 中的中间件实现

在 FastAPI 中,中间件的实现非常简洁,主要通过装饰器完成。

1. 基础示例:统计接口耗时

from fastapi import FastAPI, Request
import time

app = FastAPI()

@app.middleware("http")
async def process_time_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.get("/")
async def root():
    return {"message": "Hello World"}

2. 核心参数解析

函数定义:

async def process_time_middleware(request, call_next):

其中:

  • request:当前请求对象
  • call_next:一个函数,用于调用下一个处理环节

最关键的是这一行:

response = await call_next(request)

它的作用是:

  • 将请求继续向下传递
  • 最终进入路由函数
  • 获取返回的响应对象

如果没有这一步,请求会被中断。


六、多个中间件的执行顺序

定义多个中间件时,顺序很容易搞错。

示例:

# 创建中间件1
@app.middleware('http')
async def m1(request: Request, call_next):
    # 将请求向下传递之前进行的一个操作
    print('中间件1执行之前的操作')
    # 将请求向下传递,等待下一个中间件或路由处理完成
    response = await call_next(request)
    print('中间件1执行之后的操作')
    return response

# 创建中间件2
@app.middleware('http')
async def m2(request: Request, call_next):
    # 将请求向下传递之前进行的一个操作
    print('中间件2执行之前的操作')
    # 将请求向下传递,等待下一个中间件或路由处理完成
    response = await call_next(request)
    print('中间件2执行之后的操作')
    return response

执行顺序:

中间件2执行之前的操作
中间件1执行之前的操作
测试中渐渐执行了
中间件1执行之后的操作
中间件2执行之后的操作

结论:

  • 先定义的中间件在最外层
  • 后定义的更靠近业务逻辑

七、类方式实现中间件(进阶)

当逻辑复杂时,可以使用类来实现中间件。

from starlette.middleware.base import BaseHTTPMiddleware

class CustomMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        print("请求进入")

        response = await call_next(request)

        print("响应返回")
        return response


app.add_middleware(CustomMiddleware)

这种方式更适合:

  • 需要配置参数
  • 需要复用
  • 逻辑较复杂的场景

八、常见中间件实战案例

1. 日志中间件

@app.middleware("http")
async def log_middleware(request, call_next):
    print(f"请求路径: {request.url}")

    response = await call_next(request)

    print(f"响应状态码: {response.status_code}")
    return response

2. 简单鉴权中间件

@app.middleware("http")
async def auth_middleware(request, call_next):
    token = request.headers.get("Authorization")

    if token != "secret-token":
        from fastapi.responses import JSONResponse
        return JSONResponse({"error": "未授权"}, status_code=401)

    return await call_next(request)

3. 统一异常处理

@app.middleware("http")
async def error_middleware(request, call_next):
    try:
        return await call_next(request)
    except Exception as e:
        from fastapi.responses import JSONResponse
        return JSONResponse({"error": str(e)}, status_code=500)

九、中间件设计原则

在实际项目中,建议遵循以下原则:

1. 保持轻量

中间件应该只做必要的事情,不要:

  • 执行复杂数据库操作
  • 写大量业务逻辑

2. 单一职责

一个中间件只做一件事,例如:

  • 只做日志
  • 只做认证

不要混合多个功能。


3. 顺序清晰

中间件顺序非常重要,例如:

  • 日志:最外层
  • 认证:靠前
  • 业务相关处理:靠内

4. 避免副作用

中间件不应该随意修改:

  • 请求内容(除非必要)
  • 响应结构(除非统一格式)

十、中间件与装饰器的区别

很多初学者容易混淆两者。

核心区别如下:

  • 中间件作用于整个应用
  • 装饰器作用于单个函数

举例:

  • 中间件适合做登录校验(全局)
  • 装饰器适合做接口限流(单个接口)

十一、总结

中间件可以看作是 Web 框架中的“控制层”,它连接了请求与业务逻辑。

掌握中间件,你需要建立这几个核心认知:

  • 它是请求处理链的一部分
  • 执行顺序是先进后出
  • call_next 是核心控制点
  • 适用于通用逻辑,而不是业务逻辑

如果你能独立写出以下三种中间件,就已经算入门:

  • 请求日志中间件
  • 接口耗时统计
  • 简单认证拦截

进一步提升,可以尝试:

  • 自定义中间件类
  • 结合数据库实现用户鉴权
  • 设计完整的请求处理链

当你真正理解中间件之后,再去阅读框架源码(如 FastAPI 或 Starlette),会更加容易,也更有收获。

更多推荐