FastAPI 高级中间件(Advanced Middleware)完全指南:集成任意 ASGI 中间件与内置中间件实战
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
本篇技术指南以 FastAPI 高级用户指南中的 Middleware Avanzado(高级中间件)章节(英文原版)为核心,系统讲解 FastAPI 项目中中间件的进阶用法:如何把任意符合 ASGI 规范的第三方中间件接入应用,以及仓库内置/内置集成的HTTPSRedirectMiddleware、TrustedHostMiddleware、GZipMiddleware等中间件的正确配置方式与参数语义。读完本文,你将掌握app.add_middleware()的接入机制、各内置中间件的安全与性能价值,并能在自己的 FastAPI 应用中独立完成 HTTPS 强制跳转、Host 校验和 GZip 压缩等生产级配置。
在正文开始之前,建议先回顾两篇前置章节:自定义中间件的创建方式见 Middleware 教程章节,跨域场景的CORSMiddleware用法见 CORS 章节。本文讨论的是这两者之外的“其他中间件”用法。
FastAPI 为什么可以接入任意 ASGI 中间件
FastAPI是基于Starlette构建的,而 Starlette 完整实现了 ASGI 规范(异步服务器网关接口)。这意味着:
- 只要一个组件遵循 ASGI 规范,它就不需要专门为 FastAPI 或 Starlette 定制,也能无缝接入应用;
- 一般而言,ASGI 中间件就是一类“期望把某个 ASGI 应用作为第一个构造参数接收”的类,通过“包裹”下一层应用实现对请求/响应的拦截与加工。
在第三方 ASGI 中间件的文档中,你常见到的是类似下面的用法——直接手工构建新的应用对象:
from unicorn import UnicornMiddleware app = SomeASGIApp() new_app = UnicornMiddleware(app, some_config="rainbow")用app.add_middleware()接入第三方 ASGI 中间件
直接手工包裹的问题是:这样生成的new_app脱离了 FastAPI 内部的中件夹栈管理,无法保证服务端错误处理(ServerErrorMiddleware)与自定义异常处理器(ExceptionMiddleware)正常工作。因此 FastAPI(更准确说是 Starlette)提供了更简单也更稳妥的方式——app.add_middleware():
from fastapi import FastAPI from unicorn import UnicornMiddleware app = FastAPI() app.add_middleware(UnicornMiddleware, some_config="rainbow")app.add_middleware()第一个参数接收中间件类,其后紧跟任意需要传递给该中间件的附加参数(构造参数),如这里的some_config="rainbow"。
从源码看 add_middleware 的中间件栈机制
为什么用add_middleware就能保证异常处理正常?可以从仓库的 FastAPI 实现找到依据:
FastAPI类本身继承自 Starlette(见 fastapi/applications.py),因此add_middleware()方法来自 Starlette 基类;- FastAPI 在
__init__中维护self.user_middleware列表(fastapi/applications.py),每次调用add_middleware()就是把中间件类追加进该列表,随后触发setup()重建中间件栈; - FastAPI 重写了
setup()(fastapi/applications.py)与build_middleware_stack()(fastapi/applications.py),其核心组装逻辑是:
[ServerErrorMiddleware] + self.user_middleware + [ExceptionMiddleware, AsyncExitStackMiddleware]也就是说,你通过add_middleware()注册的中间件会落在ServerErrorMiddleware内部、ExceptionMiddleware外部。这样 500 级服务器错误仍会被最外层兜底处理,路由抛出的异常也仍会经过你注册的中间件链最终交给自定义异常处理器——这正是文档强调“内部中间件处理服务器错误、自定义异常处理器正常工作”的源码级原因。FastAPI 在构建栈时额外加入了AsyncExitStackMiddleware(位于最内层),用于关闭依赖与上传文件等资源。
关于顺序:后添加的中间件更“靠外”
如果在构造阶段传入middleware参数,或在运行期多次调用add_middleware(),每个新中间件都会包裹已存在的应用形成栈。请求进入时最外层先执行,响应返回时最后执行;FastAPI会把栈重建为上述“ServerError → 用户中间件 → Exception → AsyncExitStack → 路由”的结构,具体顺序语义可参见 Middleware 教程中的“多中间件执行顺序”小节。除运行期add_middleware()外,还可用 Starlette 的Middleware类在构造FastAPI(middleware=[...])时声明式传入(Middleware已由 fastapi/middleware/init.py 从 Starlette 再导出)。
仓库自带的中间件模块一览(fastapi.middleware)
文档有一处专门的“细节说明”:下文示例中你其实也可以直接写from starlette.middleware.something import SomethingMiddleware。FastAPI 在fastapi.middleware下提供这些中间件,纯粹是为了开发者便利——它们绝大多数直接来自 Starlette。这一点在源码中得到印证:fastapi/middleware/下的模块基本都是对 Starlette 同名类的极薄再导出:
| fastapi.middleware 模块 | 实际来源 | 用途 |
|---|---|---|
| httpsredirect.py | starlette.middleware.httpsredirect.HTTPSRedirectMiddleware | 强制 HTTPS/WSS |
| trustedhost.py | starlette.middleware.trustedhost.TrustedHostMiddleware | Host 头白名单校验 |
| gzip.py | starlette.middleware.gzip.GZipMiddleware | GZip 响应压缩 |
| cors.py | starlette.middleware.cors.CORSMiddleware | 跨域资源共享 |
| asyncexitstack.py | starlette.middleware.asyncexitstack.AsyncExitStackMiddleware | 栈内资源清理 |
| wsgi.py | starlette.middleware.wsgi.WSGIMiddleware | 挂载 WSGI 应用 |
| init.py | starlette.middleware.Middleware | 声明式中间件组合类 |
CORSMiddleware的具体用法在 CORS 教程 已有专门讲解,本节不再重复。下面按文档顺序深入三个最常用的内置中间件。
HTTPSRedirectMiddleware:强制 HTTPS/WSS 跳转
该中间件强制所有入站请求必须为https或wss:任何以http或ws到达的请求都会被 307 重定向到对应的安全协议。适合部署在 HTTPS 终结(如反向代理之后)场景下进一步兜底,防止明文协议被直接访问。
完整可运行示例见 docs_src/advanced_middleware/tutorial001_py310.py:
from fastapi import FastAPI from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware app = FastAPI() app.add_middleware(HTTPSRedirectMiddleware) @app.get("/") async def main(): return {"message": "Hello World"}仓库测试如何验证它
仓库测试 tests/test_tutorial/test_advanced_middleware/test_tutorial001.py 从两个方向验证了该中间件:
- 当
TestClient使用base_url="https://testserver"发起请求时,直接返回200; - 当使用默认的
http协议请求且关闭自动跟随重定向(follow_redirects=False)时,返回状态码307,且响应头location为https://testserver/。
这组用例也提醒你:该中间件生效的前提是上游真的终结了 TLS——它只负责把明文流量“导流”到安全入口,并不自己做加解密。
TrustedHostMiddleware:防御 HTTP Host 头攻击
TrustedHostMiddleware强制所有入站请求携带正确的Host头,从而防御 HTTP Host Header 攻击(如密码重置钓鱼、缓存投毒等依赖篡改Host头的攻击手段)。若入站请求校验不通过,中间件会直接返回400响应。
完整示例见 docs_src/advanced_middleware/tutorial002_py310.py:
from fastapi import FastAPI from fastapi.middleware.trustedhost import TrustedHostMiddleware app = FastAPI() app.add_middleware( TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"] ) @app.get("/") async def main(): return {"message": "Hello World"}支持的构造参数
allowed_hosts:一个允许作为主机名的域名列表。支持通配符域名,例如*.example.com会匹配其所有子域名。若想允许任意主机名,要么显式传allowed_hosts=["*"],要么干脆不挂载该中间件(因为"*"相当于关闭校验)。www_redirect:设为True时,对允许主机列表中“非 www”版本域名的请求会被 307 重定向到带www的对应地址;默认值为True。
文档明确说明:一旦入站请求未通过校验,会收到400响应。
仓库测试如何验证它
test_tutorial002.py 用三种base_url演示了判定规则:
| base_url | 结果 |
|---|---|
http://example.com | 200(命中白名单) |
http://subdomain.example.com | 200(被*.example.com通配匹配) |
http://invalidhost | 400(不在白名单) |
实际接入时需要把你所有的对外域名(含可能用到的子域名)都放进allowed_hosts,并留意www_redirect=True的默认重定向行为是否符合你的域名规划。
GZipMiddleware:为响应启用 GZip 压缩
GZipMiddleware会对在Accept-Encoding请求头中包含"gzip"的请求返回 GZip 压缩后的响应,从而减小传输体积、降低带宽消耗。值得注意的细节是:它既能处理标准响应,也能处理流式(streaming)响应。
完整示例见 docs_src/advanced_middleware/tutorial003_py310.py:
from fastapi import FastAPI from fastapi.middleware.gzip import GZipMiddleware app = FastAPI() app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5) @app.get("/") async def main(): return "somebigcontent"支持的构造参数
minimum_size:小于该字节数的响应不做 GZip 压缩。默认值为500(字节)。这是为了“小响应不值得压缩”的工程权衡——压缩小响应反而可能因头部开销得不偿失。compresslevel:GZip 压缩过程中使用的压缩级别,取值为1到9的整数,默认值为9。级别越低压缩越快但产物越大,级别越高压缩越慢但产物越小。在 CPU 敏感或流量峰值场景可调低以换取吞吐。
文档示例即为“不是默认参数”的实践:minimum_size=1000(小于 1KB 不压缩)配合compresslevel=5(速度与体积的折中)。
仓库测试如何验证它
test_tutorial003.py 为同一个app额外注册了一条返回4000个x字符的路由/large,然后断言:
- 请求携带
accept-encoding: gzip时响应状态为200; - 响应头
Content-Encoding等于gzip; - 压缩后的
Content-Length数值小于原始的4000; - 请求不带 gzip 编码的根路径
/时行为正常。
如果你想亲手验证,可把任一示例保存为main.py后用uvicorn main:app启动,再配合浏览器开发者工具或curl --compressed观察响应头中的Content-Encoding: gzip。
其他 ASGI 中间件生态与延伸阅读
FastAPI 的中间件体系并不局限于上述三者。因为 ASGI 规范天然具备互操作性,生态里还活跃着大量同类中间件,例如:
- Uvicorn 的
ProxyHeadersMiddleware:在反向代理(如 Nginx)之后,用于根据代理头还原客户端真实 IP 与协议信息,对日志审计、限流、HTTPS 判定等场景很重要; - MessagePack 等序列化类 ASGI 中间件:为需要紧凑二进制载荷的接口提供备选编解码路径;
- Starlette 官方维护的其他中间件:如会话管理
SessionMiddleware、认证AuthenticationMiddleware、自定义异常响应ExceptionMiddleware等,均在 Starlette 中间件文档中有完整说明,通常只需from starlette.middleware.xxx import XxxMiddleware引入后按前文方式注册即可。
需要再次强调的是:接入任何第三方 ASGI 中间件时,优先使用app.add_middleware()而不是手工包裹应用对象,这样你得到的中间件栈仍处于 FastAPI/Starlette 的管理之下,服务器错误兜底与自定义异常处理器都不会被绕过。这与本仓库源码中build_middleware_stack()的实现(fastapi/applications.py)保持一致。
小结
本文围绕 Advanced Middleware 文档 完整覆盖了三个层面的知识:
- 接入机制:FastAPI 基于 Starlette/ASGI,可用
app.add_middleware()注册任意 ASGI 中间件;源码证实用户中间件位于ServerErrorMiddleware之内、ExceptionMiddleware之外,因此错误处理不被绕过; - 内置中间件:
HTTPSRedirectMiddleware(强制 HTTPS/WSS)、TrustedHostMiddleware(allowed_hosts+www_redirect,非法 Host 返回 400)、GZipMiddleware(minimum_size默认 500、compresslevel默认 9、范围 1–9),每个均有文档示例与仓库测试用例双保险; - 生态延展:
fastapi.middleware各模块实为 Starlette 同名类的再导出,此外 Uvicorn、Starlette 与 ASGI 社区还有更丰富的中间件可选用。
掌握了这些中间件与注册顺序规则,你就可以在 FastAPI 应用中可靠地叠加 HTTPS 强制、Host 校验、GZip 压缩等生产级能力,而无需担心它们破坏框架自身的异常处理链路。
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考