首页
/ FastAPI 文档静态资源自定义指南:切换自定义 CDN 与自托管 Swagger UI / ReDoc 资产

FastAPI 文档静态资源自定义指南:切换自定义 CDN 与自托管 Swagger UI / ReDoc 资产

2026-09-06 16:28:00作者:殷蕙予

本文基于 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.jsswagger_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(例如公司内网、特定地区的网络限制),就需要自定义文档静态资源的来源。官方文档给出了两种方案:

  1. 自定义 CDN:把资源 URL 换成你信任/可达的另一个 CDN(例如 https://unpkg.com/);
  2. 自托管:把资源文件下载到本地,由同一个 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.pysetup() 方法可以印证这一机制:只有当 self.openapi_url and self.docs_url 同时为真时才会注册 Swagger UI 路由,self.openapi_url and self.redoc_url 为真时才会注册 ReDoc 路由。因此 docs_url=None, redoc_url=None 会让这两组自动路由完全不注册,而 /openapi.jsonopenapi_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_urlNone 即可。

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_urlswagger_css_urlredoc_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_idlayoutdeepLinking 等默认配置)合并后注入页面。
  • 自动文档的注册逻辑fastapi/applications.pysetup() 中,Swagger UI、OAuth2 redirect、ReDoc 三类路由分别在 docs_urlswagger_ui_oauth2_redirect_urlredoc_url 非空时注册,且生成的页面会拼接 root_path 前缀以适配子路径部署——手动创建自定义文档时,若应用带 root_path,可参考此逻辑自行处理。
  • 测试用例tests/test_local_docs.pyinspect.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.pydocs_src/custom_docs_ui/tutorial002_py310.py

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