首页
/ FastAPI 自定义文档 UI 静态资源:换 CDN 与完全自托管 Swagger UI / ReDoc

FastAPI 自定义文档 UI 静态资源:换 CDN 与完全自托管 Swagger UI / ReDoc

2026-09-04 19:48:44作者:秋泉律Samson

FastAPI 自动生成的 API 文档(Swagger UI 与 ReDoc)默认从公共 CDN 加载 JavaScript 和 CSS 文件,这意味着文档页面在离线环境、内网环境或 CDN 被限制的部署场景下可能无法使用。本文基于官方文档 docs/de/docs/how-to/custom-docs-ui-assets.md(对应 示例源码 1示例源码 2),完整讲解两种替代方案:替换为自定义 CDN 的静态资源地址,以及把 JS/CSS 文件下载到本地、由 FastAPI 应用自身托管。读完本篇,你将能够离线运行 FastAPI 的交互式文档,并理解 get_swagger_ui_html 等内部函数是如何拼装文档页面的。

默认行为:文档资源来自 CDN

FastAPI 的 /docs(Swagger UI)与 /redoc(ReDoc)页面本身是服务端动态生成的 HTML,但页面里引用的浏览器端资源(Swagger UI 的 JS 与 CSS、ReDoc 的 JS)默认由 CDN 提供。从源码 fastapi/openapi/docs.py 可以看到默认值:

  • get_swagger_ui_html() 的默认参数:swagger_js_url 为 jsDelivr CDN 上的 swagger-ui-dist@5/swagger-ui-bundle.jsswagger_css_url 为同版本的 swagger-ui.css
  • get_redoc_html() 的默认参数:redoc_js_url 为 jsDelivr CDN 上的 redoc@2/bundles/redoc.standalone.js

因此只要应用能访问外网 CDN,文档页面开箱即用;而本文要解决的正是“不能用默认 CDN”的场景。

方案一:使用自定义 CDN

假设你想改用另一个 CDN(例如 unpkg.com)。这在某些公共 CDN 域名被限制或不可达的网络环境下特别有用。完整示例见 docs_src/custom_docs_ui/tutorial001_py310.py

第一步:禁用自动文档

自动生成的 /docs/redoc 路由默认使用默认 CDN,所以第一步是创建 FastAPI 应用时把它们的 URL 设为 None

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)

从源码结构看,fastapi/applications.pysetup() 方法只在 self.openapi_url and self.docs_url 同时成立时才注册 swagger_ui_html 路由(约第 1121 行),redoc_url 同理(约第 1149 行)。把 docs_url / redoc_url 置为 None 后,这两条自动路由就不会被添加,而 openapi_url 保持默认 /openapi.json,OpenAPI Schema 依旧对外提供。

第二步:创建自定义文档的路径操作

可以复用 FastAPI 的内部函数来生成文档 HTML 页面,并传入自己需要的参数:

  • openapi_url:文档 HTML 页面获取 API OpenAPI Schema 的 URL,可直接用应用属性 app.openapi_url
  • title:API 标题(显示在浏览器标签页);
  • oauth2_redirect_url:OAuth2 重定向地址,可用 app.swagger_ui_oauth2_redirect_url 使用默认值;
  • swagger_js_url:Swagger UI 页面加载 JavaScript 文件的 URL,这里填自定义 CDN 地址;
  • swagger_css_url:Swagger UI 页面加载 CSS 文件的 URL,这里填自定义 CDN 地址。

ReDoc 的用法类似,只是换用 get_redoc_html 并传 redoc_js_url

@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 这个路径操作是配合 OAuth2 使用的辅助页。如果你的 API 集成了 OAuth2 提供商,文档页面可以发起认证并带着凭据返回,Swagger UI 在幕后完成授权流程,但它需要这个“重定向”辅助页面来接收回调参数。这也是为什么自定义文档路由时通常要一并注册它。

第三步:加一个测试用路径操作

为了验证一切正常,可以加一个最简单的业务接口:

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

第四步:测试

启动应用后访问 http://127.0.0.1:8000/docs 并刷新页面,此时 Swagger UI 的 JS/CSS 已从新的 CDN 加载。可以用浏览器开发者工具的网络面板确认资源请求发往的是你配置的 CDN 域名。

方案二:完全自托管文档的 JavaScript 和 CSS

如果应用需要离线运行(无外网、纯内网、本地网络),最彻底的办法是把文档所需的全部 JS/CSS 下载下来,在同一个 FastAPI 应用里自己托管。完整示例见 docs_src/custom_docs_ui/tutorial002_py310.py

项目文件结构

假设项目结构如下:

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

新建一个存放静态文件的目录 static/

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

下载所需文件

把文档需要的静态文件下载并放入 static/ 目录(浏览器中对资源链接“另存为”即可)。Swagger UI 需要两个文件:swagger-ui-bundle.jsswagger-ui-dist@5 版本)与 swagger-ui.css(同版本);ReDoc 需要一个文件:redoc.standalone.jsredoc@2 的 bundles 版本)。下载后结构如下:

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

文件版本应与 fastapi/openapi/docs.py 中默认 CDN 参数一致(swagger-ui-dist@5redoc@2),以保证行为与官方默认体验相同。

托管静态文件

  • 导入 StaticFiles(FastAPI 直接从 Starlette 转出,见 fastapi/staticfiles.py);
  • StaticFiles() 实例 “mount” 到指定路径:
from fastapi.staticfiles import StaticFiles

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

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

先验证静态文件可访问

启动应用后访问 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")):
...

这就说明应用能正确对外提供静态文件,且文件放对了位置。

禁用自动文档并接入本地资源

与方案一相同,创建应用时设置 docs_url=None, redoc_url=None。然后创建自定义文档路径操作,区别在于:swagger_js_urlswagger_css_urlredoc_js_url 现在指向自己应用托管的本地路径(相对根路径):

@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}"}

同样保留 swagger_ui_redirect 路径操作,OAuth2 流程才能完整工作。

离线验证 UI

此时可以断开 Wi-Fi,访问 http://127.0.0.1:8000/docs 并刷新页面——即使完全没有互联网,文档页面依旧可见、可交互。测试用例 tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py 验证了自托管示例中 /docs/redoc 与静态文件路径都能被正确访问。

源码级细节:这些内部函数做了什么

理解两个生成函数后,自定义 URL 的原理一目了然(见 fastapi/openapi/docs.py):

  • get_swagger_ui_html()(约第 40 行起)返回一段 HTML 字符串:<link rel="stylesheet" href="{swagger_css_url}"><script src="{swagger_js_url}"> 就是你在自定义路由里传入的 URL;页面内嵌的 SwaggerUIBundle({ url: '{openapi_url}', ... }) 负责在浏览器端拉取 OpenAPI Schema 并渲染。此外它还会注入 swagger_ui_parameters(默认含 dom_idlayoutdeepLinking 等)以及可选的 init_oauth
  • get_redoc_html()(约第 197 行起)生成的页面更简单:一个 <redoc spec-url="{openapi_url}"> 自定义元素加一个 <script src="{redoc_js_url}">
  • 注意源码中内嵌 JSON 会经过 _html_safe_json() 转义 <>&(约第 9 行),防止把动态内容注入 <script> 时产生 HTML 注入问题——这也是官方模板可放心拼入 openapi_url 等动态值的原因。
  • 从源码结构看,自动文档路由(setup() 中)会把请求上下文里的 root_path 拼到 openapi_urloauth2_redirect_url 前面(fastapi/applications.py 约第 1121–1158 行),以支持部署在反向代理子路径下。如果你自定义文档路由且应用带有 root_path,可参考这一处理方式,为 openapi_url 手动加上前缀。
  • 默认参数方面,get_swagger_ui_html 还支持 swagger_favicon_url(默认指向 fastapi.tiangolo.com 的 favicon)与 init_oauthget_redoc_html 支持 with_google_fonts(默认开启 Google Fonts)。若你的环境同样无法访问这些外部地址,可一并传入本地或内网地址,让文档页彻底去外部化。

小结

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341