FastAPI 挂载 WSGI 应用:用 a2wsgi 的 WSGIMiddleware 集成 Flask、Django 等框架
本篇技术文章以 FastAPI 官方进阶文档《Including WSGI - Flask, Django, others》为主体,完整讲解如何在 FastAPI(ASGI)应用中挂载一个 WSGI 应用(如 Flask、Django):包括通过 app.mount() 挂载 a2wsgi.WSGIMiddleware 包装的 WSGI 应用、依赖安装、请求路由分发机制、验证方法,以及旧版 fastapi.middleware.wsgi 导入路径的弃用说明。读完后,你可以将一个既有的 WSGI 服务整体接入 FastAPI,使其与 FastAPI 的接口在同一个进程中共存并按路径前缀分流,同时理解这一能力在 FastAPI 源码中的实际落地位置。
背景:为什么需要把 WSGI 应用放进 FastAPI
FastAPI 构建在 Starlette 之上,是一个 ASGI 应用,天然运行在异步(async/await)模型中;而 Flask、Django 等传统框架多数暴露的是 WSGI 接口,是同步调用约定。当你在项目中既有基于 FastAPI 的新 API,又有一段历史悠久的 WSGI 代码需要继续对外提供服务时,最省事的做法就是在一个进程里把两者合并起来:FastAPI 作为“主应用”,WSGI 应用被“挂载”(mount)到某个路径前缀下。
这种“挂载”机制并不是 WSGI 独有的。FastAPI 官方文档 Sub Applications - Mounts 中描述的 app.mount() 用法,可以把任意独立的 ASGI 应用(比如另一个 FastAPI() 实例)挂到指定路径下,每个子应用拥有自己独立的 OpenAPI 和文档 UI。WSGI 应用挂载到 FastAPI 时走的正是同一条 mount 通路,区别只在于挂载的对象是“被 ASGI 化的 WSGI 应用”。
使用 WSGIMiddleware 挂载 WSGI 应用
前置依赖:安装 a2wsgi
官方文档明确要求:使用 WSGIMiddleware 需要先把 a2wsgi 加入项目依赖,例如:
uv add a2wsgi
FastAPI 仓库自身的开发依赖中也可以印证版本要求,pyproject.toml 中声明了:
"a2wsgi >=1.9.0,<=2.0.0",
"flask >=3.0.0,<4.0.0",
也就是说,官方示例所配套的 Flask 为 3.x 版本、a2wsgi 为 1.9.x 以上版本。
完整示例代码
官方示例位于 docs_src/wsgi/tutorial001_py310.py,完整内容如下:
from a2wsgi import WSGIMiddleware
from fastapi import FastAPI
from flask import Flask, request
from markupsafe import escape
flask_app = Flask(__name__)
@flask_app.route("/")
def flask_main():
name = request.args.get("name", "World")
return f"Hello, {escape(name)} from Flask!"
app = FastAPI()
@app.get("/v2")
def read_main():
return {"message": "Hello World"}
app.mount("/v1", WSGIMiddleware(flask_app))
逐段拆解这份示例:
-
从
a2wsgi导入WSGIMiddleware(第 1 行):这是官方文档强调的正确导入路径。WSGIMiddleware是一个 ASGI 中间件类,它把传入的 WSGI 可调用对象包装成符合 ASGI 协议的中间件,从而能被 ASGI 服务器(如 Uvicorn)和 FastAPI 的路由系统接管。 -
构造 WSGI 应用(第 6 行起):这里是一个最简 Flask 应用,根路由
/读取查询参数name(缺省为"World"),并用markupsafe.escape做 HTML 转义后返回纯文本。注意这里的/是相对于挂载前缀的路径,实际对外地址是/v1/。 -
构造 FastAPI 主应用(第 15 行起):声明了一个普通的 FastAPI 路径操作
GET /v2,返回 JSON。 -
挂载 WSGI 应用(第 23 行):
app.mount("/v1", WSGIMiddleware(flask_app))这一行是整个文档的核心操作:用
WSGIMiddleware包一层 Flask 应用,然后mount到路径/v1之下。挂载后,所有落在/v1/前缀下的请求都会被转发进这个 WSGI 应用,其余请求仍由 FastAPI 处理。
验证运行结果
运行示例应用(例如 uv run fastapi dev main.py,其中 main.py 即上述示例文件)后,两条路径分别由两个框架处理:
-
访问
http://localhost:8000/v1/:请求进入 Flask,返回纯文本响应:Hello, World from Flask! -
访问
http://localhost:8000/v2:请求进入 FastAPI,返回 JSON 响应:{ "message": "Hello World" }
此外,由于 Flask 路由读取了 name 查询参数,访问 http://localhost:8000/v1/?name=FastAPI 会得到 Hello, FastAPI from Flask!——这正是查询参数经由 ASGI→WSGI 转换层正常传递的体现。
FastAPI 仓库中也为这个教程提供了自动化测试 tests/test_tutorial/test_wsgi/test_tutorial001.py,它直接用 TestClient 对教程应用发起请求并断言上述两条响应:
def test_flask():
response = client.get("/v1/")
assert response.status_code == 200, response.text
assert response.text == "Hello, World from Flask!"
def test_app():
response = client.get("/v2")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Hello World"}
这组测试从实现层面确认了:挂载后的 WSGI 应用确实能收到请求并返回正确内容,而 FastAPI 侧的接口行为不受影响。
关于已弃用的 fastapi.middleware.wsgi
官方文档特别指出:早期版本中曾推荐使用 fastapi.middleware.wsgi 里的 WSGIMiddleware,该路径现已弃用,建议改用独立的 a2wsgi 包,用法保持不变,只需确保安装了 a2wsgi 并从 a2wsgi 正确导入。
从源码结构看,fastapi/middleware/wsgi.py 的全部内容如今只是一行对 Starlette 实现的转发:
from starlette.middleware.wsgi import (
WSGIMiddleware as WSGIMiddleware,
)
也就是说,fastapi.middleware.wsgi.WSGIMiddleware 与 a2wsgi.WSGIMiddleware 在能力上已趋同,官方统一指向社区独立维护的 a2wsgi 包,避免了在框架内部重复维护一份 WSGI 桥接逻辑。如果你维护的是旧项目,迁移成本很低:把导入语句从 fastapi.middleware.wsgi 换成 a2wsgi 即可,app.mount(path, WSGIMiddleware(wsgi_app)) 的调用方式完全一致。
挂载行为与路径前缀的工作方式
mount 的语义与 Sub Applications - Mounts 中挂载独立 FastAPI 子应用时一致:在某个特定路径下放入一个“完全独立”的应用,由它负责处理该前缀下的一切请求。以本教程为例,可以把路由分发理解为:
| 请求路径 | 处理者 | 说明 |
|---|---|---|
/v1/、/v1/?name=xxx |
Flask(经 WSGIMiddleware 包装) |
前缀 /v1 之后的剩余路径交给 WSGI 应用路由 |
/v2、/docs、/openapi.json 等 |
FastAPI 主应用 | 其余所有未挂载路径由主应用及其文档 UI 处理 |
需要注意的细节:
- 挂载路径带斜杠语义:Flask 路由声明为
/,对外访问的是/v1/,与官方测试用例client.get("/v1/")中的写法一致。 - WSGI 应用是同步执行的:
WSGIMiddleware负责在 ASGI 的异步上下文中桥接 WSGI 的同步调用约定,因此 Flask/Django 这类同步框架不需要改造即可接入,也不会阻塞 FastAPI 中其他异步接口的并发能力。 - 子应用拥有独立路由:被挂载的 WSGI 应用有自己的 URL 规则体系(如 Flask 的
@flask_app.route),FastAPI 的 OpenAPI 文档并不会把这些 WSGI 接口纳入/openapi.json——它们对该主应用的文档 UI 来说是“黑盒”。若需要文档能力,可参考 Sub Applications - Mounts 中挂载独立 FastAPI 子应用的做法,让子应用自行暴露/docs。
与反向代理场景的组合使用
如果你的 FastAPI 主应用本身部署在反向代理(Traefik、Nginx 等)后面,官方文档 Behind a Proxy 介绍了 ASGI 规范中的 root_path 机制:代理剥掉路径前缀(例如 /api/v1)后再转发给 Uvicorn,而通过 --root-path 或 FastAPI(root_path="/api/v1") 告知应用该前缀,使重定向与文档 UI 生成的 URL 正确。该文档还明确说明:在使用 root_path 的同时挂载子应用是常规支持场景,“FastAPI will internally use the root_path smartly, so it will just work”。同理,挂载的 WSGI 应用位于 root_path 之下的某个前缀中,代理只需要把完整请求转发给主应用,mount 与 root_path 的组合即按预期工作。
小结
- 在 FastAPI 中挂载 WSGI 应用的标准做法:
uv add a2wsgi,从a2wsgi导入WSGIMiddleware,然后app.mount("/v1", WSGIMiddleware(flask_app))。 - 挂载前缀(
/v1)之下的所有请求由 WSGI 应用处理,其余请求由 FastAPI 处理;官方测试 tests/test_tutorial/test_wsgi/test_tutorial001.py 验证了这两种响应。 - 旧路径
fastapi.middleware.wsgi已弃用,其实现如今仅是对 StarletteWSGIMiddleware的转发(见 fastapi/middleware/wsgi.py),统一使用a2wsgi包即可平滑迁移。 - 完整可运行示例见 docs_src/wsgi/tutorial001_py310.py,配套文档见 docs/en/docs/advanced/wsgi.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00