news 2026/9/8 18:11:55

FastAPI 中间件(Middleware)完全指南:请求拦截、响应处理与执行顺序深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastAPI 中间件(Middleware)完全指南:请求拦截、响应处理与执行顺序深度解析

FastAPI 中间件(Middleware)完全指南:请求拦截、响应处理与执行顺序深度解析

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

本文基于 FastAPI 官方教程文档(docs/hi/docs/tutorial/middleware.md,与英文版 docs/en/docs/tutorial/middleware.md 同源)整理。核心主题是在FastAPIapplication 中添加自定义 HTTP 中间件:拦截进入应用的每一个请求、在交给具体path operation之前做处理,并在响应返回客户端之前做二次加工。读完本文你将掌握@app.middleware("http")装饰器的完整用法、如何在响应中注入自定义 Header、yield依赖与 Background Tasks 的执行时序,以及多个中间件的栈式执行顺序,并了解其底层在 Starlette 基础之上是如何实现的。

什么是中间件

在 FastAPI 中,"中间件(middleware)"是一个函数,它会与**每一个请求(request)打交道——在请求被任何一个具体的path operation处理之前;同时也会与每一个响应(response)**打交道——在响应被返回给客户端之前。

一个中间件的完整工作流程包含以下 6 个步骤:

  1. 它接住发往你 application 的每一个请求
  2. 它可以对该request做一些处理,或执行任何需要的代码;
  3. 然后它把request交给 application 的其余部分去处理(由某个path operation完成);
  4. 它随后拿到由 application(即某个path operation)生成的response
  5. 它可以对该response做一些处理,或执行任何需要的代码;
  6. 最后它把response返回出去。

换句话说,中间件就像包在路由处理逻辑外面的一层"洋葱皮":请求进来先经过它,响应出去也先经过它。

yield依赖、后台任务的技术时序

官方文档在"技术细节(Technical Details)"中特别澄清了两个容易混淆的执行时序问题:

  • 如果你使用带yield的依赖,那么该依赖的exit code(yield之后的收尾代码)会在中间件之后执行
  • 如果存在后台任务(background tasks,见 Background Tasks 一节),那么它们会在所有中间件都执行完之后才运行。

也就是说,从响应生命周期看,顺序大致是:path operation→ 带yield依赖的退出代码 → 中间件 → 后台任务。理解这一点,有助于避免在中间件里"提前"做那些本该由依赖清理或后台任务完成的工作。

创建第一个中间件:@app.middleware("http")

创建自定义 HTTP 中间件最简单的方式,是在一个函数上方使用装饰器@app.middleware("http")。这个中间件函数会收到两个关键对象:

  • request:当前进入的请求;
  • call_next:一个接收request作为参数的函数——
    • 它会把request传递给对应的path operation
    • 然后返回该 *path operation 生成的response

拿到call_next(request)返回的response之后,你可以在最终返回它之前对它做任意修改。

下面是一份完整、可直接运行的示例(来自 docs_src/middleware/tutorial001_py310.py,需 Python 3.10+):

import time from fastapi import FastAPI, Request app = FastAPI() @app.middleware("http") async def add_process_time_header(request: Request, call_next): start_time = time.perf_counter() response = await call_next(request) process_time = time.perf_counter() - start_time response.headers["X-Process-Time"] = str(process_time) return response

Request从哪来

注意上例中的request: Request,其类型来自fastapi.Request。官方技术细节指出:你也可以直接写from starlette.requests import RequestFastAPI之所以提供fastapi.Request,纯粹是出于开发者便利,它本质上直接来自 Starlette。同理,call_next也是一个典型的 StarletteBaseHTTPMiddlewaredispatch 接口。

源码中的落地方式:它其实是add_middleware(BaseHTTPMiddleware, ...)

查看 applications.py 中FastAPI.middleware()的实现(约第 4683 行起),可以看到装饰器的本质:

def middleware( self, middleware_type: Annotated[str, Doc("The type of middleware. Currently only supports `http`.")], ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_middleware(BaseHTTPMiddleware, dispatch=func) return func return decorator

也就是说,@app.middleware("http")会把你写的函数作为dispatch参数,注册为一个 Starlette 的BaseHTTPMiddleware。当前类型参数仅支持字符串"http"。这解释了为什么中间件函数必须写成async def、为什么签名固定为(request, call_next)——它们完全对应BaseHTTPMiddleware.dispatch的约定。

call_next之前与之后:请求/响应两侧的代码

利用中间件函数天然的分段结构,你可以同时在请求侧和响应侧挂代码:

  • call_next(request)之前的代码:在path operation收到请求前运行;
  • call_next(request)之后、return response之前的代码:在响应已生成但尚未返回给客户端时运行。

上面示例正是这一模式的典型应用:用time.perf_counter()记录请求开始时间,await call_next(request)拿到响应后立即再次计时求差,并把耗时(秒)写入自定义响应头X-Process-Time

为什么用time.perf_counter()而不是time.time()

官方建议在此类性能计时场景使用 Python 标准库的time.perf_counter(),因为它提供更高精度的单调时钟,适合测量极短的代码执行间隔,不受系统时钟调整的影响。time.time()更适合表示墙上时钟(wall-clock)时间,不适合做高精度差分计时。

自定义 Header 与 CORS 的联动注意事项

中间件里设置的"自定义专有 Header"(custom proprietary headers)可以遵循惯例加上X-前缀(例如上例的X-Process-Time)。

但有一个非常实际的坑:如果你希望浏览器中的客户端(前端 JS)能够读到这些自定义响应头,仅仅在中间件里设置是不够的——你还必须在 CORS 配置中通过expose_headers参数把它们显式暴露出去。相关配置见 CORS(跨域资源共享) 一节,以及在 docs/en/docs/advanced/middleware.md 中引用的 Starlette CORS 文档说明。

换句话说:中间件负责"写入"自定义响应头,CORS 的expose_headers负责让浏览器"允许读取",两者缺一不可。

多个中间件的执行顺序:栈式叠加

当你通过@app.middleware()装饰器或app.add_middleware()方法添加多个中间件时,每一个新中间件都会把 application 再"包一层",从而形成一个栈(stack)

  • 最后添加的中间件位于最外层(outermost);
  • 最先添加的中间件位于最内层(innermost)。

执行规则非常明确:

  • 请求路径(request path)上,最外层的中间件最先运行;
  • 响应路径(response path)上,最外层的中间件最后运行。

官方文档给出了直观示例:

app.add_middleware(MiddlewareA) app.add_middleware(MiddlewareB)

得到的执行顺序是:

  • 请求方向MiddlewareB → MiddlewareA → route
  • 响应方向route → MiddlewareA → MiddlewareB

这种 stacking 行为保证了中间件总在一个可预测、可控的顺序中执行——这正是"洋葱模型"的典型体现。

源码层面的顺序佐证

在 applications.py 的build_middleware_stack()方法(约第 1020 行起)中可以看到,FastAPI 会这样组装整个中间件链:

middleware = ( [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] + self.user_middleware + [ Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug), # FastAPI-specific AsyncExitStackMiddleware ... ] )

其中self.user_middleware正是通过add_middleware/@app.middleware收集到的、按注册先后顺序排列的用户中间件列表(定义见该文件约第 1014 行:self.user_middleware = [] if middleware is None else list(middleware))。Starlette 的build_middleware_stack会从列表尾部开始逐层 wrap,从而实现了"后注册者在外层、先注册者在内层"的栈式效果。此外,FastAPI 特意在ExceptionMiddleware之后、用户中间件之内插入AsyncExitStackMiddleware,以保证流式响应和文件关闭等清理逻辑能正常工作——这属于框架内部实现细节,但解释了为什么官方推荐始终通过app.add_middleware()添加中间件,而不是手工把 app 包一层(那样会破坏异常处理器与内部中间件的协同)。

用测试验证中间件行为

仓库自带的测试用例(tests/test_tutorial/test_middleware/test_tutorial001.py)验证了上述示例的行为:

from fastapi.testclient import TestClient from docs_src.middleware.tutorial001_py310 import app client = TestClient(app) def test_response_headers(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert "X-Process-Time" in response.headers

它通过TestClient发起一次/openapi.json请求,断言响应头中确实包含X-Process-Time。这从测试角度证明了:中间件会作用于每一个经过 application 的请求——即使该请求最终落到的是 FastAPI 内置的 OpenAPI schema 路由,而非开发者自定义的path operation

如果你想亲自运行这段示例,可把上面示例保存为本地文件并启动服务,然后对任意路径(例如/openapi.json)发起请求,用浏览器开发者工具或curl -i观察响应头中新增的X-Process-Time。需要注意运行前提:示例使用 Python 3.10+ 语法,且本地需已安装 fastapi 与任一 ASGI 服务器(如 uvicorn)。

更多的内置与第三方中间件

本文介绍的是自定义HTTP 中间件的写法与执行模型。FastAPI 自身还捆绑了一批开箱即用的中间件,位于 fastapi/middleware 目录下,例如:

  • CORSMiddleware(跨域处理,见 CORS);
  • HTTPSRedirectMiddleware(强制 HTTP/WS 跳转到 HTTPS/WSS);
  • TrustedHostMiddleware(校验Host请求头,防御 HTTP Host Header 攻击,支持allowed_hostswww_redirect参数,校验失败返回400);
  • GZipMiddleware(对Accept-Encodinggzip的请求做 GZip 压缩响应,支持minimum_size(默认 500 字节)与compresslevel(1–9,默认 9)参数);
  • WSGIMiddlewareAsyncExitStackMiddleware等。

这些中间件大多在fastapi/middleware中做了再导出(re-export),例如 fastapi/middleware/cors.py 里的from starlette.middleware.cors import CORSMiddleware,因此你可以直接from fastapi.middleware.cors import CORSMiddleware

由于 FastAPI 基于 Starlette 并完整实现 ASGI 规范,任何遵循 ASGI 规范的第三方中间件都可以通过app.add_middleware(SomeMiddleware, some_config=...)的方式接入——add_middleware的第一个参数是中间件类,后续参数会传给该类的构造器。官方建议始终使用app.add_middleware()而非手动SomeMiddleware(app)包裹,因为前者能确保服务器错误处理和自定义异常处理器正常工作。

更多中间件的逐一讲解与参数细节,可继续阅读同一仓库中的进阶文档 Advanced User Guide: Advanced Middleware(英文版见 docs/en/docs/advanced/middleware.md);而紧随本教程之后的下一节,则是用CORSMiddleware处理跨域问题的完整实战。

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 18:09:14

基于SpringBoot的留学信息管理系统设计与实现毕业设计项目源码

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/8 18:08:16

裸机工程快速接入FreeRTOS:任务拆分与移植避坑指南

前阵子有个朋友把跑了一年多的裸机工程发给我,问能不能不推倒重写就把RTOS加进去。他的状态我记得很清楚:main 函数的 while(1) 里塞了五六个模块,按键扫描、传感器读取、OLED刷新、蜂鸣器控制全挤在一起,某个外设偶尔卡一下&…

作者头像 李华
网站建设 2026/9/8 18:08:12

opencode 实战指南:终端 AI 编码代理的安装配置与核心玩法

如果你最近在逛技术社区,大概率刷到过 opencode 这个名字。它是一款开源的终端 AI 编码代理,简单说就是让你在命令行里像和同事聊天一样,把“写代码、改 bug、跑测试”这些活交给 AI 去执行。和 Claude Code 这类绑定单一模型的工具不同&…

作者头像 李华