FastAPI 挂载 WSGI 应用实战:用 a2wsgi 的 WSGIMiddleware 整合 Flask 与 Django
本文围绕 FastAPI 官方文档「Including WSGI(整合 WSGI)」一节展开,介绍如何在同一个 FastAPI 应用中挂载 Flask、Django 等传统 WSGI 应用:使用 a2wsgi 包提供的 WSGIMiddleware 将 WSGI 应用包装为 ASGI 兼容的中间件,再通过 app.mount() 挂载到指定路径,实现新旧框架在同一进程中共存与按路径分流。读完本文,你可以掌握 WSGI/ASGI 桥接的具体配置方式、依赖安装方法,以及该功能在 FastAPI 源码中的实现与弃用迁移细节。
背景:为什么要在 FastAPI 中挂载 WSGI 应用
FastAPI 是一个基于 ASGI(Asynchronous Server Gateway Interface)的异步 Web 框架,而 Flask、Django 等传统框架大多基于 WSGI(Web Server Gateway Interface,同步协议)构建。当项目中既有现代 FastAPI 服务、又有一段无法(或不便)迁移的历史 Flask/Django 代码时,可以直接把 WSGI 应用挂载到 FastAPI 的某个路径下,就像官方文档 子应用 – Mounts 与 反向代理后部署 中演示的「挂载」机制一样:
- 「挂载(mount)」意味着在一个特定路径下加入一个完全独立的应用,该路径下的所有请求都交由这个子应用处理;
- 对 WSGI 应用而言,还需要一步「桥接」:WSGI 应用是同步的、以
environ字典 + 可调用对象为接口,而 ASGI 服务端发送的是scope/receive/send。WSGIMiddleware的作用就是在两者之间做转换,让 WSGI 应用能够被 ASGI 服务器(如 Uvicorn)驱动。
FastAPI 本身不内置这个桥接器,而是依赖第三方包 a2wsgi(a2wsgi 即 "WSGI to ASGI" 的缩写),这也是本文的核心配置点。
安装依赖:a2wsgi 包
使用 WSGIMiddleware 需要先将 a2wsgi 添加到项目依赖中,例如使用 uv:
$ uv add a2wsgi
在当前仓库中可以直接验证这一依赖的声明:pyproject.toml 的可选依赖组中包含 "a2wsgi >=1.9.0,<=2.0.0",说明官方教程与测试运行所需的版本区间在 1.9.0 到 2.0.0 之间。
使用 WSGIMiddleware 的核心步骤
操作步骤可以概括为三步:
- 从
a2wsgi导入WSGIMiddleware; - 用它包裹(wrap)你的 WSGI 应用(如 Flask 应用实例);
- 将包裹后的中间件挂载(mount)到 FastAPI 应用的一个路径上。
下面是一个可完整运行的示例(对应仓库中的 示例文件):
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))
代码要点说明:
flask_app是一个完全独立的 Flask 应用,其路由/会在被挂载后对外表现为/v1/(注意尾部斜杠由 Flask 的路由规则决定);WSGIMiddleware(flask_app)将 Flask 应用实例包装成符合 ASGI 接口约定的对象;app.mount("/v1", WSGIMiddleware(flask_app))是 Starlette/FastAPI 应用对象提供的挂载方法:所有以/v1为前缀的请求都会被转发给被包装的 WSGI 应用,其余路径仍由 FastAPI 自身的 path operations 处理;markupsafe.escape用于对用户输入的name查询参数做 HTML 转义,属于 Flask 侧的常规安全处理。
重要弃用提示:不要再从 fastapi.middleware.wsgi 导入
早期版本中官方推荐从 fastapi.middleware.wsgi 导入 WSGIMiddleware,该入口现已弃用(deprecated),推荐统一改用 a2wsgi 包,使用方式保持不变。你只需要确保已安装 a2wsgi,并正确地从 a2wsgi 导入 WSGIMiddleware。
这一点可以从仓库源码中得到印证:fastapi/middleware/wsgi.py 的整个文件只剩 3 行,仅仅是从 Starlette 的 starlette.middleware.wsgi 中转发导出同名类,且带有 # pragma: no cover 标记:
from starlette.middleware.wsgi import (
WSGIMiddleware as WSGIMiddleware,
) # pragma: no cover # noqa
也就是说,FastAPI 不再自行维护 WSGI 桥接实现,旧的导入路径只保留用于兼容旧代码;官方发布说明 release-notes.md 中同样记录了迁移决定:改用 a2wsgi 的 WSGIMiddleware,弃用 fastapi.middleware.wsgi.WSGIMiddleware。如果你维护着旧项目,迁移方式就是把 from fastapi.middleware.wsgi import WSGIMiddleware 一行替换为 from a2wsgi import WSGIMiddleware,其余代码无需改动。
运行并验证效果
运行服务后,/v1/ 路径下的所有请求由 Flask 应用处理,其余请求由 FastAPI 处理。两种启动方式都可以:
$ uv run uvicorn main:app
# 或使用带热重载的开发模式
$ uv run fastapi dev main.py
访问各路径可以看到两个框架各自的响应:
- 打开
http://localhost:8000/v1/,得到 Flask 的响应(纯文本):
Hello, World from Flask!
- 打开
http://localhost:8000/v2,得到 FastAPI 的响应(JSON):
{
"message": "Hello World"
}
仓库中配套的自动化测试 test_tutorial001.py 正是对上述两个行为断言的,它直接导入示例应用 docs_src.wsgi.tutorial001_py310.app 并用 TestClient 发起请求:
from fastapi.testclient import TestClient
from docs_src.wsgi.tutorial001_py310 import app
client = TestClient(app)
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"}
这组测试同时验证了:挂载路径 /v1/ 正确路由到 Flask(文本响应)、非挂载路径 /v2 正确路由到 FastAPI(JSON 响应),以及两条链路的响应内容均符合预期。
实践要点小结
- 依赖:必须安装
a2wsgi(当前仓库测试环境约束为>=1.9.0,<=2.0.0,见 pyproject.toml),并用uv add a2wsgi之类的命令加入项目; - 导入:一律使用
from a2wsgi import WSGIMiddleware;旧的fastapi.middleware.wsgi入口已弃用,仅作向后兼容转发(见 fastapi/middleware/wsgi.py); - 挂载:
app.mount("/v1", WSGIMiddleware(wsgi_app))是标准写法,wsgi_app可以是任意符合 WSGI 规范的可调用对象,Flask、Django(其 WSGI 入口)均适用; - 路由边界:只有挂载路径前缀内的请求进入 WSGI 应用,其余请求(包括 FastAPI 的
/docs、/openapi.json等)不受影响,两个应用可互不干扰地共用同一端口; - 适用前提:WSGI 应用内部是同步执行的,
WSGIMiddleware会将其桥接到 ASGI 运行时;对于需要独立 OpenAPI 文档的 FastAPI 子应用,仍应参考 子应用 – Mounts 一节的做法。
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 StartedRust0622
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