首页
/ FastAPI 自托管文档静态资源:为 Swagger UI 与 ReDoc 配置自定义 CDN 及完全离线部署指南

FastAPI 自托管文档静态资源:为 Swagger UI 与 ReDoc 配置自定义 CDN 及完全离线部署指南

2026-09-07 20:06:47作者:温艾琴Wonderful

FastAPI 的自动交互式 API 文档(Swagger UI 与 ReDoc)默认依赖公共 CDN 加载 JavaScript/CSS 资源,这在地域性访问受限、离线环境或内网部署时会造成文档无法显示。本文以 docs/fr/docs/how-to/custom-docs-ui-assets.md 为骨架,结合仓库内 fastapi/openapi/docs.py 的实现源码与配套测试,讲解两种完整的自定义方案:将文档静态资源切换到自选 CDN,以及把 JS/CSS 下载到本地并由 FastAPI 自行托管,实现断网也能查看和调试 API 文档。读完本文你将能独立改造任意 FastAPI 项目的文档路由。

背景:FastAPI 文档 UI 的资源加载机制

FastAPI 的 API 文档包含两套界面:Swagger UI(默认挂在 /docs)和 ReDoc(默认挂在 /redoc)。二者本质上都是「一段 HTML 页面 + 若干 JS/CSS 静态资源」的组合:

  • Swagger UI 需要 swagger-ui-bundle.jsswagger-ui.css
  • ReDoc 需要 redoc.standalone.js

默认情况下,这些资源并不随 FastAPI 仓库分发,而是由 FastAPI 生成的 HTML 页面从 CDN(内容分发网络)动态加载。在源码 fastapi/openapi/docs.py 中可以直接看到这些默认 CDN 地址,例如 get_swagger_ui_html()swagger_js_url 参数默认值为 https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.jsdocs.py L79),get_redoc_html()redoc_js_url 默认加载 https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.jsdocs.py L236)。

依赖 CDN 会带来两个现实问题:

  1. 某些网络环境下部分 CDN 域名不可达(例如所在地区屏蔽特定 URL);
  2. 离线环境或内网部署时完全没有公网访问。

好消息是 FastAPI 将生成文档页 HTML 的逻辑封装成了公开函数,并允许在创建 FastAPI() 实例时关闭自动文档路由。下面的两个方案分别解决上述两种场景。

方案一:将文档资源切换到自定义 CDN

适用场景:网络可达,但你想使用另一个 CDN(例如 https://unpkg.com/),因为默认 CDN 在你所处的地区不可达,或出于版本与来源管控需要。

第 1 步:关闭自动文档路由

FastAPI() 构造函数的 docs_urlredoc_url 两个参数控制自动文档页的挂载地址,其默认值分别是 /docs/redoc(见 applications.py L421L445)。将它们设为 None 即可关闭自动文档:

from fastapi import FastAPI

app = FastAPI(docs_url=None, redoc_url=None)

关闭后 /docs/redoc 就不会再被 FastAPI 自动注册,这样它也就不会再向页面注入默认 CDN 地址。

第 2 步:手写自定义文档路由

接下来由我们自己注册 /docs/redoc 路由,复用 FastAPI 内部用于生成文档 HTML 的函数,并把 JS/CSS 的 URL 指向新的 CDN。核心函数从 fastapi.openapi.docs 导入:

  • get_swagger_ui_html(...):生成 Swagger UI 页面;
  • get_redoc_html(...):生成 ReDoc 页面;
  • get_swagger_ui_oauth2_redirect_html():生成 OAuth2 回调辅助页。

get_swagger_ui_html() 为例,它接收的关键参数(与文档一一对应,亦可对照 docs.py L40-L111 的签名与默认值):

参数 含义 取值建议
openapi_url 文档页拉取 OpenAPI schema 的 URL 使用 app.openapi_url(默认 /openapi.json
title 显示在浏览器标签页的标题 常用 app.title + " - Swagger UI"
oauth2_redirect_url OAuth2 回调辅助页地址 使用 app.swagger_ui_oauth2_redirect_url(默认 /docs/oauth2-redirect,见 applications.py L458
swagger_js_url 加载 Swagger UI JavaScript 的 URL 这里填入自定义 CDN 地址
swagger_css_url 加载 Swagger UI CSS 的 URL 这里填入自定义 CDN 地址

get_redoc_html() 的参数类似,唯一的资源参数是 redoc_js_url。注意这些参数都可以不传——它们带有默认 CDN 值;正是因为我们要覆盖默认值,才需要显式传入。

第 3 步:处理 OAuth2 重定向

提示swagger_ui_redirect 路径是使用 OAuth2 时的「助手」。如果你的 API 集成了 OAuth2 提供方,用户通过「Authorize」完成认证后,Swagger UI 需要回到一个固定页面来接收携带的授权凭据,再继续与 API 交互——这一过程由 Swagger UI 在后台完成,但前提是存在这个 redirect 回调页。因此在自定义路由时,也需要像下面的代码一样补上 get_swagger_ui_oauth2_redirect_html() 对应的路由。

完整可运行示例

仓库中的完整示例见 docs_src/custom_docs_ui/tutorial001_py310.py

from fastapi import FastAPI
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)

app = FastAPI(docs_url=None, redoc_url=None)


@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js",
        swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css",
    )


@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
    return get_swagger_ui_oauth2_redirect_html()


@app.get("/redoc", include_in_schema=False)
async def redoc_html():
    return get_redoc_html(
        openapi_url=app.openapi_url,
        title=app.title + " - ReDoc",
        redoc_js_url="https://unpkg.com/redoc@2/bundles/redoc.standalone.js",
    )


@app.get("/users/{username}")
async def read_user(username: str):
    return {"message": f"Hello {username}"}

关于上述代码还有两个细节值得说明:

  • include_in_schema=False:FastAPI 在自动注册文档路由时同样使用了该标记(见 applications.py L1137),它保证 /docs/redoc 这些"元页面"不会出现在 OpenAPI schema 自身的 paths 中;
  • 路由路径直接写 app.swagger_ui_oauth2_redirect_url(值为 /docs/oauth2-redirect),与默认行为保持一致。

验证效果

启动应用(例如 uvicorn main:app --reload),访问 http://127.0.0.1:8000/docs 并刷新页面。如果 Swagger UI 正常渲染,说明页面已从新的 unpkg.com CDN 拉取资源。仓库配套测试 tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py 也验证了这一点:它断言 /docs 返回的 HTML 中确实包含 https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js 与对应的 CSS 地址,并检查 /redoc 页包含 unpkg.com 的 ReDoc JS 地址,同时 /docs/oauth2-redirect 与业务接口 /users/john 均返回 200。

方案二:自托管静态资源,实现完全离线可用

适用场景:应用需要在没有公网(离线、断网)或只有内网的环境下持续运行,文档资源必须与应用一起分发、由应用自己提供。

第 1 步:规划项目文件结构

假设项目结构从简如下:

.
├── app
│   ├── __init__.py
│   ├── main.py

先为静态资源创建目录 static/(放在应用目录旁即可):

.
├── app
│   ├── __init__.py
│   ├── main.py
└── static/

第 2 步:下载文档所需的静态文件

把下列文件下载并放入 static/ 目录(在浏览器中右键链接选择"另存为"即可):

Swagger UI 需要:

  • swagger-ui-bundle.js(来源:https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js
  • swagger-ui.css(来源:https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css

ReDoc 需要:

  • redoc.standalone.js(来源:https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js

下载完成后目录结构如下:

.
├── app
│   ├── __init__.py
│   ├── main.py
└── static
    ├── redoc.standalone.js
    ├── swagger-ui-bundle.js
    └── swagger-ui.css

第 3 步:用 StaticFiles 托管静态目录

导入 fastapi.staticfiles.StaticFiles,用 app.mount()static/ 目录挂载到 /static 路径:

from fastapi.staticfiles import StaticFiles

app.mount("/static", StaticFiles(directory="static"), name="static")

name="static" 相当于给这个挂载点起名,便于后续需要反向解析 URL 时引用。

第 4 步:验证静态文件能被访问

启动应用并访问 http://127.0.0.1:8000/static/redoc.standalone.js,浏览器应返回一个很长的 JS 文件,内容大致以类似下面的许可注释开头:

/*! For license information please see redoc.standalone.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
...

看到这段内容,说明你已经成功用 FastAPI 自身提供静态文件,且文件放置位置正确。接下来只需让文档页面指向这些本地 URL。

第 5 步:关闭自动文档并指向本地资源

与方案一相同,先关闭自动文档;区别在于,这次传给 get_swagger_ui_html() / get_redoc_html() 的不再是外部 CDN 地址,而是 FastAPI 自己服务的相对 URL/static/...)。

完整示例见 docs_src/custom_docs_ui/tutorial002_py310.py

from fastapi import FastAPI
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles

app = FastAPI(docs_url=None, redoc_url=None)

app.mount("/static", StaticFiles(directory="static"), name="static")


@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=app.title + " - Swagger UI",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_js_url="/static/swagger-ui-bundle.js",
        swagger_css_url="/static/swagger-ui.css",
    )


@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
    return get_swagger_ui_oauth2_redirect_html()


@app.get("/redoc", include_in_schema=False)
async def redoc_html():
    return get_redoc_html(
        openapi_url=app.openapi_url,
        title=app.title + " - ReDoc",
        redoc_js_url="/static/redoc.standalone.js",
    )


@app.get("/users/{username}")
async def read_user(username: str):
    return {"message": f"Hello {username}"}

需要注意:ReDoc 页面默认还会额外加载 Google Fonts(get_redoc_html()with_google_fonts 参数默认为 True,见 docs.py L245-L252)。如果连字体资源也要完全离线,可在调用 get_redoc_html() 时传入 with_google_fonts=False,让页面使用系统字体。

第 6 步:断网实测

启动应用后访问 http://127.0.0.1:8000/docs。关闭 Wi-Fi 或断开外网后刷新页面,Swagger UI 依旧能完整加载、正常展示 OpenAPI schema,并可直接对接口发起交互调用——因为页面所需的全部 JS/CSS 都由本应用在 /static 下提供。

配套测试 tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py 验证了同样的结论:它断言 /docs 返回的 HTML 中 swagger_js_urlswagger_css_url 已被替换为 /static/swagger-ui-bundle.js/static/swagger-ui.css/redoc 页引用 /static/redoc.standalone.js。需要留意的是,该测试的 fixture 会在当前工作目录创建临时 static/ 目录以兼容示例代码的运行前提,因此运行前示例文件 static/ 中的三个静态文件必须真实存在。

源码剖析:FastAPI 究竟如何生成文档页

理解底层实现有助于你在自定义时准确覆盖需要的参数而不踩坑。

fastapi/openapi/docs.py:三个 HTML 生成函数

  • get_swagger_ui_html()docs.py L40)返回一个完整的 HTMLResponse:它在 <head> 中写入 CSS 链接,在 <body> 末尾用 <script src="..."> 引入 JS,随后内联一段 SwaggerUIBundle({...}) 初始化脚本,把 openapi_url 作为 url 注入。它默认带有一套 swagger_ui_default_parameters(如 deepLinking: Truelayout: "BaseLayout" 等,见 docs.py L22-L37),并支持 init_oauthswagger_ui_parameters 等进阶参数。
  • get_redoc_html()docs.py L197)生成 <redoc spec-url="{openapi_url}"></redoc> 标签加 <script src="{redoc_js_url}"> 的页面。
  • get_swagger_ui_oauth2_redirect_html()docs.py L301)直接内嵌了从 Swagger UI 项目拷贝来的 OAuth2 回调逻辑。

实现中还有一个值得注意的安全细节:内联 JSON 参数在拼进 <script> 之前会经过 _html_safe_json() 处理(docs.py L9-L19),把 <>& 转义为 Unicode 转义序列,防止在 HTML 中注入恶意脚本。

fastapi/applications.py:默认文档路由的注册逻辑

FastAPI 在实例初始化阶段(setup())会按条件注册文档路由(applications.py L1121-L1158):

if self.openapi_url and self.docs_url:
    # 注册 get_swagger_ui_html() 生成的页面到 self.docs_url
    ...
    if self.swagger_ui_oauth2_redirect_url:
        # 同时注册 oauth2-redirect 回调页
        ...
if self.openapi_url and self.redoc_url:
    # 注册 get_redoc_html() 生成的页面到 self.redoc_url
    ...

从这段逻辑可以推断并印证几点:

  1. docs_url=Noneredoc_url=None 传入构造函数,就会跳过上述分支,从而彻底移除自动文档路由——这正是两个方案中"第一步"的原理所在;
  2. 自动注册的内部实现,恰恰就是调用我们手动复用的 get_swagger_ui_html() / get_redoc_html() / get_swagger_ui_oauth2_redirect_html() 三个函数。因此"自定义文档"本质上不是发明新机制,而是接管 FastAPI 的默认注册行为;
  3. 自动注册还额外处理了 root_path(反向代理场景下的路径前缀),会在 openapi_url 前拼接根路径前缀。如果你的应用部署在子路径/代理之后,手写自定义文档页时可参考该逻辑自行处理。

总结与适用建议

场景 推荐方案 关键改动
默认 CDN 不可达/需指定资源来源 自定义 CDN docs_url=None, redoc_url=None + 手写 /docs/redoc 路由指向新 CDN
离线/内网/必须完全自包含 自托管静态资源 额外下载三个静态文件并 app.mount("/static", StaticFiles(...)),路由指向 /static/...
使用 OAuth2 两种方案通用 务必同时注册 swagger_ui_redirect 回调路由
追求完全离线(含字体) 自托管方案 get_redoc_html(..., with_google_fonts=False)

两套方案的可运行代码分别位于仓库的 docs_src/custom_docs_ui/tutorial001_py310.pydocs_src/custom_docs_ui/tutorial002_py310.py,配套测试见 tests/test_tutorial/test_custom_docs_ui/ 目录,底层 HTML 生成逻辑见 fastapi/openapi/docs.py。你可以直接以这两份示例为模板改造自己的项目:只需把示例中的业务接口换成你的真实接口,把 static/ 目录与挂载路径按需调整即可。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388