FastAPI 集成 WSGI 应用实战:借助 WSGIMiddleware 将 Flask、Django 等挂载进同一 ASGI 服务
导读:本文围绕 FastAPI 官方文档中的"挂载 WSGI 应用(Flask、Django 等)"主题展开,讲解如何在同一个 FastAPI 应用中通过 a2wsgi 的 WSGIMiddleware 将传统的 WSGI 框架应用"嵌入"进来,实现新旧技术栈共存与平滑过渡。读完你将掌握安装依赖、编写示例、挂载路径以及验证请求分发归属的完整方法,并能理解 fastapi.middleware.wsgi 被弃用背后的工程缘由。
背景:为什么需要把 WSGI 应用挂进 FastAPI
FastAPI 基于 ASGI(异步服务器网关接口)规范构建,天然支持异步编程与 WebSocket 等能力。而 Flask、Django 等大量历史项目遵循的是 WSGI(同步的 Web 服务器网关接口)规范。两者协议不同,无法直接相互调用。
但在实际迁移场景中,团队往往希望:
- 新接口用 FastAPI 开发,享受自动 OpenAPI 文档、数据校验与异步性能;
- 存量业务仍运行在成熟的 Flask / Django 应用里,不必一次性全部重写;
- 两者运行在同一个进程、同一个端口之下,便于统一部署、统一入口。
FastAPI 官方给出的答案与"子应用挂载(Mounts)"思路一致——把 WSGI 应用当作一个挂在指定路径前缀下的"独立子应用"。这一点正是 Sous-applications - Montages(子应用与挂载) 和 Être derrière un proxy(反向代理部署) 两篇文档所铺垫的挂载与 root_path 机制的延续(中文版见 docs/zh/docs/advanced/sub-applications.md 与 docs/zh/docs/advanced/behind-a-proxy.md)。
核心概念:WSGIMiddleware 充当 ASGI↔WSGI 的翻译层
要让 FastAPI(ASGI 侧)能把请求交给 Flask(WSGI 侧),需要一层适配中间件来做协议转换:把 ASGI 的 scope / receive / send 调用翻译为 WSGI 的 environ / start_response,再把 WSGI 应用产生的响应翻译回 ASGI。这就是 WSGIMiddleware 的职责。
在当前的 FastAPI 仓库中,挂载 WSGI 应用的推荐做法是引入第三方包 a2wsgi,并从其中导入 WSGIMiddleware:
from a2wsgi import WSGIMiddleware
官方文档(本仓库 docs/fr/docs/advanced/wsgi.md,英文源版 docs/en/docs/advanced/wsgi.md)给出的使用路径非常简洁,只有三步:
- 用
WSGIMiddleware包装你的 WSGI 应用(例如 Flask app); - 通过
app.mount(path, ...)把它挂载到某个路径前缀之下; - 之后该路径前缀下的所有请求都由被挂载的 WSGI 应用处理,其余请求仍由 FastAPI 处理。
第一步:添加依赖
由于 WSGIMiddleware 现在来自第三方包 a2wsgi,使用前需要先安装。官方文档推荐使用 uv:
uv add a2wsgi
如果你还需要运行本文的完整示例(它包含一个最小 Flask 应用),同时要安装 Flask:
uv add flask
在仓库自身的工程配置中也能看到这一事实:pyproject.toml 的 tests 依赖组同时纳入了 a2wsgi 与 flask,其中 a2wsgi 版本约束为 >=1.9.0,<=2.0.0、flask 版本约束为 >=3.0.0,<4.0.0(参见 pyproject.toml 与 pyproject.toml),这说明仓库自己的教程测试正是围绕这两个包运行的。
第二步:编写完整示例
仓库在 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))
逐段拆解这段代码:
- 第 1~4 行:导入三组能力——
a2wsgi提供的WSGIMiddleware(协议桥接)、FastAPI 的FastAPI类(ASGI 主应用),以及 Flask 与其request/escape(被挂载的 WSGI 应用)。 - 第 6~12 行:创建并配置 Flask 应用。它的根路由
/会读取查询参数name(缺省为"World"),并用markupsafe.escape转义后拼接进响应文本,防止 XSS。这个 Flask 应用完全是一个标准的、独立可运行的 WSGI 应用。 - 第 15 行:创建 FastAPI 主应用
app。 - 第 18~20 行:在主应用里定义一条路径操作
GET /v2,作为"FastAPI 侧"的对照接口,返回 JSON{"message": "Hello World"}。 - 第 23 行:关键的挂载语句
app.mount("/v1", WSGIMiddleware(flask_app))——先把 Flask 应用用WSGIMiddleware包起来,再挂到/v1前缀下。
注意:Flask 侧的 flask_main 路由是 /,但因为它整体被挂载在 /v1 之下,所以实际对外生效的完整路径是 /v1/。也就是说,子应用的内部路由始终是相对于挂载点解析的,这与挂载另一个 FastAPI 子应用时的规则一致。
第三步:理解挂载点的请求分发规则
挂载完成后,路由归属非常简单清晰:
- 凡是以
/v1/开头的请求,全部交给 Flask 应用处理; - 其余请求继续由 FastAPI 主应用的路由表处理(本例即
/v2)。
这种"路径前缀 + 独立子应用"的架构,本质上复用了 FastAPI 文档中"子应用挂载"的同一套机制:主应用只负责把前缀匹配到的请求"转发"出去,子应用内部再按自己的路由规则继续分发。如果你之前已经掌握 Sous-applications - Montages(中文版见 docs/zh/docs/advanced/sub-applications.md),那么 WSGI 挂载的唯一新增点就是多套了一层 WSGIMiddleware 协议适配,其余心智模型完全一致。
需要留意的工程细节是:挂载点本身(/v1)并不参与子应用内部的 URL 构造。若 WSGI 应用内部需要知道自己的对外前缀(例如生成链接、处理重定向),通常依赖部署层传递的环境信息;当 FastAPI 自身又位于 Nginx / Traefik 等反向代理之后时,则要与 Derrière un proxy(代理部署) 一文中的 root_path、转发请求头等机制配合处理(中文版见 docs/zh/docs/advanced/behind-a-proxy.md)。
弃用说明:从 fastapi.middleware.wsgi 迁移到 a2wsgi
官方文档特别给出了一条迁移提示:早期版本推荐从 fastapi.middleware.wsgi 导入 WSGIMiddleware,但现在该模块已被弃用,建议改用独立的 a2wsgi 包,使用方式完全一致。
从当前仓库源码可以印证这一点:仓库内的 fastapi/middleware/wsgi.py 只剩下一行再导出代码,即把 Starlette 的 WSGIMiddleware 原样转出(标注了 pragma: no cover),本身不再承载任何 FastAPI 特有的实现逻辑:
from starlette.middleware.wsgi import (
WSGIMiddleware as WSGIMiddleware,
) # pragma: no cover # noqa
因此官方将协议适配的实现职责整体移交给了第三方 a2wsgi 维护。从源码结构可以推断,这一调整是为了让 FastAPI 自身的 middleware 包更聚焦于 ASGI 世界,而 WSGI 桥接这类兼容需求交给专门维护的独立库持续跟进。
迁移时你只需要做两件事:
-
确保
a2wsgi已安装(uv add a2wsgi); -
把导入语句从:
from fastapi.middleware.wsgi import WSGIMiddleware改为:
from a2wsgi import WSGIMiddleware
其余代码(包装、挂载、请求分发)一概不变。
第四步:运行并验证
启动服务(可通过 FastAPI CLI 或任意 ASGI 服务器运行上面的示例):
uv run fastapi dev docs_src/wsgi/tutorial001_py310.py
然后分别验证两个端点,请求会按预期被不同的框架处理:
访问 http://localhost:8000/v1/ —— 请求前缀命中 /v1/,由 Flask 应用响应:
Hello, World from Flask!
(若携带查询参数,例如 /v1/?name=FastAPI,则会得到经过转义的 Hello, FastAPI from Flask!。)
访问 http://localhost:8000/v2 —— 未命中挂载前缀,交给 FastAPI 主应用响应:
{
"message": "Hello World"
}
仓库通过测试用例固定了上述行为,见 tests/test_tutorial/test_wsgi/test_tutorial001.py:
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"}
这个测试同时是很好的"冒烟验证"手段:即便没有真实启动服务器,也可以用 FastAPI 自带的 TestClient 断言 WSGI 子应用与 ASGI 主应用各自响应正确、互不干扰。
扩展到 Django 及其他 WSGI 框架
官方文档明确指出该方案不仅限于 Flask,同样适用于 Django 及其他任何遵循 WSGI 规范的框架。模式是通用的:
from a2wsgi import WSGIMiddleware
from fastapi import FastAPI
# django_app 例如来自 django.core.wsgi 的 get_wsgi_application()
app = FastAPI()
app.mount("/django", WSGIMiddleware(django_app))
不过需要说明:Django 这类"全家桶"框架本身包含自己的 URL 路由、ORM 中间件、静态文件与模板体系,挂载后它将作为一个整体服务 /django/ 前缀下的请求,框架内部机制不会与 FastAPI 的依赖注入、请求/响应对象打通——两者只是在传输层共享同一个进程与端口。对于更细粒度的"让 Flask 的某个蓝图或 Django 的某个 app 暴露成 FastAPI 路由"这类诉求,则属于另一套话题,不能与"整体挂载"混为一谈。
小结
- WSGI(同步,Flask/Django)与 ASGI(异步,FastAPI)通过
WSGIMiddleware桥接,可以把历史 WSGI 应用整体挂载到 FastAPI 的路径前缀下,实现同一进程、同一端口的混合部署。 - 推荐从第三方包
a2wsgi导入WSGIMiddleware,安装命令为uv add a2wsgi;旧入口fastapi.middleware.wsgi已弃用(fastapi/middleware/wsgi.py 仅作再导出)。 - 完整可运行示例见 docs_src/wsgi/tutorial001_py310.py,官方行为测试见 tests/test_tutorial/test_wsgi/test_tutorial001.py。
- 挂载机制与 FastAPI 子应用挂载、反向代理部署共用同一套路径前缀与
root_path心智模型,建议联动阅读 docs/fr/docs/advanced/sub-applications.md 与 docs/fr/docs/advanced/behind-a-proxy.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 StartedRust0624
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