FastAPI 文档静态资源自定义指南:切换自定义 CDN 与自托管 Swagger UI / ReDoc 资产
本文基于 FastAPI 官方文档 Custom Docs UI Static Assets (Self-Hosting) 编写,讲解如何摆脱 API 文档页面对公共 CDN 的默认依赖:将 Swagger UI 与 ReDoc 所需的 JavaScript/CSS 文件指向自定义 CDN,或完全自托管(self-hosting)到本应用内,使文档在离线环境、内网环境中依然可用。读完本文,你将掌握 get_swagger_ui_html()、get_redoc_html() 等 FastAPI 内置 HTML 生成函数的完整参数用法,以及如何用 StaticFiles 挂载静态资源的完整可运行方案。
1. 背景:默认文档为什么依赖 CDN
FastAPI 的 API 文档使用 Swagger UI(/docs)和 ReDoc(/redoc)两套前端,它们各自需要若干 JavaScript 与 CSS 文件。默认情况下,这些文件通过 CDN 加载——FastAPI 生成的文档 HTML 中只包含指向 CDN 的 <script> 与 <link> 标签,浏览器直接从公共 CDN 拉取资源。
从源码 fastapi/openapi/docs.py 可以确认默认值:
get_swagger_ui_html()的swagger_js_url默认为https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js,swagger_css_url默认为https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css;get_redoc_html()的redoc_js_url默认为https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js,并且默认通过 Google Fonts 加载字体(with_google_fonts=True)。
这意味着默认文档页的运行依赖外网可达。如果所在网络环境限制某些 URL(例如公司内网、特定地区的网络限制),就需要自定义文档静态资源的来源。官方文档给出了两种方案:
- 自定义 CDN:把资源 URL 换成你信任/可达的另一个 CDN(例如
https://unpkg.com/); - 自托管:把资源文件下载到本地,由同一个 FastAPI 应用自己提供静态文件服务,实现完全离线可用。
两种方案的前置步骤相同:先禁用 FastAPI 的自动文档路由,再手动创建文档的 path operation。
2. 方案一:使用自定义 CDN
对应示例代码见 docs_src/custom_docs_ui/tutorial001_py310.py。
2.1 禁用自动文档
FastAPI 创建时会自动注册 /docs、/redoc 等文档路由,它们使用的是默认 CDN。要替换资源来源,需要把这些 URL 置为 None 来关闭自动文档:
from fastapi import FastAPI
app = FastAPI(docs_url=None, redoc_url=None)
从源码 fastapi/applications.py 的 setup() 方法可以印证这一机制:只有当 self.openapi_url and self.docs_url 同时为真时才会注册 Swagger UI 路由,self.openapi_url and self.redoc_url 为真时才会注册 ReDoc 路由。因此 docs_url=None, redoc_url=None 会让这两组自动路由完全不注册,而 /openapi.json(openapi_url)保持不变,这正是后续自定义文档页需要的 OpenAPI schema 来源。
2.2 创建自定义文档 path operation
FastAPI 把生成文档 HTML 的能力封装为可复用函数,定义在 fastapi/openapi/docs.py:
get_swagger_ui_html():生成 Swagger UI 的 HTML 页面;get_redoc_html():生成 ReDoc 的 HTML 页面;get_swagger_ui_oauth2_redirect_html():生成 OAuth2 重定向辅助页。
关键参数说明(均取自源码签名):
| 参数 | 所属函数 | 含义 |
|---|---|---|
openapi_url |
两者 | 文档页 HTML 加载 OpenAPI schema 的地址,直接使用 app.openapi_url(即默认的 /openapi.json) |
title |
两者 | HTML <title> 内容,通常显示在浏览器标签页 |
swagger_js_url |
Swagger UI | 文档页加载 JavaScript 文件的 URL,此处传入自定义 CDN 地址 |
swagger_css_url |
Swagger UI | 文档页加载 CSS 文件的 URL,此处传入自定义 CDN 地址 |
redoc_js_url |
ReDoc | 文档页加载 ReDoc JavaScript 文件的 URL |
oauth2_redirect_url |
Swagger UI | OAuth2 重定向地址,使用 app.swagger_ui_oauth2_redirect_url 即可获得默认值 |
swagger_favicon_url / redoc_favicon_url |
两者 | 浏览器标签页的 favicon,默认指向 FastAPI 官方图片,可按需替换 |
以换用 https://unpkg.com/ 为例,完整代码如下:
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",
)
关于 swagger_ui_redirect 这个 path operation:它是 OAuth2 场景的辅助路由。当 API 集成了 OAuth2 提供商后,Swagger UI 会把浏览器弹到授权页,授权完成后需要一个本地页面把凭据带回文档页,Swagger UI 在幕后处理整个流程,但必须有这个"redirect"辅助页配合。如果你的 API 完全不涉及 OAuth2,可以省略该路由,oauth2_redirect_url 传 None 即可。
2.3 添加测试接口并验证
为方便确认功能正常,添加一个普通 path operation:
@app.get("/users/{username}")
async def read_user(username: str):
return {"message": f"Hello {username}"}
启动应用后访问 http://127.0.0.1:8000/docs 并刷新页面,文档页的 JS/CSS 就会从你指定的新 CDN 加载,OpenAPI schema 仍来自本应用的 /openapi.json。
3. 方案二:自托管 JavaScript 与 CSS
对应示例代码见 docs_src/custom_docs_ui/tutorial002_py310.py。
自托管适合需要离线运行的场景:应用部署在没有开放外网访问的本地网络或内网时,文档仍然可用、可交互。
3.1 项目文件结构
假设项目初始结构为:
.
├── app
│ ├── __init__.py
│ ├── main.py
先新建一个 static/ 目录存放静态文件:
.
├── app
│ ├── __init__.py
│ ├── main.py
└── static/
3.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.3 由 FastAPI 提供静态文件
只需两步:导入 StaticFiles,再把一个 StaticFiles() 实例"挂载"(mount)到指定路径:
from fastapi.staticfiles import StaticFiles
app = FastAPI(docs_url=None, redoc_url=None)
app.mount("/static", StaticFiles(directory="static"), name="static")
这里同样先通过 docs_url=None, redoc_url=None 禁用自动文档,因为自动文档默认引用 CDN。
启动应用后访问 http://127.0.0.1:8000/static/redoc.standalone.js,应当能看到一段很长的 ReDoc JavaScript 源码,可能类似:
/*! For license information please see redoc.standalone.js.LICENSE.txt */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):
...
看到文件内容即说明:静态文件服务正常、文件放置位置正确。
3.4 让文档页引用本地资源
与自定义 CDN 的方式完全相同,只是 URL 从外部 CDN 换成本应用自己的静态路径:
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}"}
参数要点与 2.2 节相同,区别在于 swagger_js_url、swagger_css_url、redoc_js_url 现在指向你自己应用正在提供的 /static/ 路径。OAuth2 redirect 辅助路由的作用同上,按需保留。
3.5 验证离线可用
启动应用,断开 WiFi,访问 http://127.0.0.1:8000/docs 并刷新。即使没有任何互联网连接,文档页依然完整加载并可交互——因为 JS、CSS、OpenAPI schema 全部来自本地应用本身。
4. 源码层面的原理佐证
- HTML 模板生成:
get_swagger_ui_html()在 fastapi/openapi/docs.py 中用 f-string 拼装完整 HTML,<link rel="stylesheet" href="{swagger_css_url}">与<script src="{swagger_js_url}">直接采用你传入的 URL;get_redoc_html()则生成<redoc spec-url="...">自定义元素加上<script src="{redoc_js_url}">。因此"换 CDN/自托管"在机制上等价于替换这两个 URL 字符串。 - 参数注入的转义处理:源码中的
_html_safe_json()会把参数序列化为 JSON 并转义<、>、&,防止内嵌<script>标签时的注入问题;swagger_ui_parameters会与swagger_ui_default_parameters(包含dom_id、layout、deepLinking等默认配置)合并后注入页面。 - 自动文档的注册逻辑:fastapi/applications.py 的
setup()中,Swagger UI、OAuth2 redirect、ReDoc 三类路由分别在docs_url、swagger_ui_oauth2_redirect_url、redoc_url非空时注册,且生成的页面会拼接root_path前缀以适配子路径部署——手动创建自定义文档时,若应用带root_path,可参考此逻辑自行处理。 - 测试用例:tests/test_local_docs.py 用
inspect.signature取出各 URL 参数的默认值,断言默认 CDN 地址确实出现在生成的 HTML 中,同时用自定义 URL 验证替换生效;此外还断言 ReDoc HTML 默认包含 Google Fonts、with_google_fonts=False时不含——后者对自托管离线场景是一个值得注意的细节:ReDoc 默认仍会尝试加载 Google Fonts,追求完全离线时可以显式传with_google_fonts=False。
5. 小结
| 目标 | 关键操作 |
|---|---|
| 换用其他 CDN | FastAPI(docs_url=None, redoc_url=None) 禁用自动文档;用 get_swagger_ui_html() / get_redoc_html() 自建文档路由,传入自定义 swagger_js_url / swagger_css_url / redoc_js_url |
| 自托管离线资源 | 额外 app.mount("/static", StaticFiles(directory="static")),把三个前端文件放入 static/,URL 改为 /static/... |
两条路线复用同一套内置函数,仅资源 URL 不同;/openapi.json 保持由本应用提供,因此文档的数据源始终来自自己的 API,可复制可运行的完整示例分别位于 docs_src/custom_docs_ui/tutorial001_py310.py 与 docs_src/custom_docs_ui/tutorial002_py310.py。
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