FastAPI 自定义文档 UI 静态资源:换 CDN 与完全自托管 Swagger UI / ReDoc
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
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.js,swagger_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.py 中setup()方法只在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.js(swagger-ui-dist@5版本)与swagger-ui.css(同版本);ReDoc需要一个文件:redoc.standalone.js(redoc@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@5与redoc@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_url、swagger_css_url、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="/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_id、layout、deepLinking等)以及可选的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_url与oauth2_redirect_url前面(fastapi/applications.py 约第 1121–1158 行),以支持部署在反向代理子路径下。如果你自定义文档路由且应用带有root_path,可参考这一处理方式,为openapi_url手动加上前缀。 - 默认参数方面,
get_swagger_ui_html还支持swagger_favicon_url(默认指向 fastapi.tiangolo.com 的 favicon)与init_oauth,get_redoc_html支持with_google_fonts(默认开启 Google Fonts)。若你的环境同样无法访问这些外部地址,可一并传入本地或内网地址,让文档页彻底去外部化。
小结
- 两种方案共享同一个前置步骤:
FastAPI(docs_url=None, redoc_url=None)关闭自动文档路由,再用get_swagger_ui_html/get_redoc_html/get_swagger_ui_oauth2_redirect_html自建/docs、/redoc与 OAuth2 重定向路径操作(三个示例路由均带include_in_schema=False,避免文档路由本身出现在 Schema 中)。 - 换 CDN 只需把
swagger_js_url、swagger_css_url、redoc_js_url指向新的 CDN 地址;完全自托管则用StaticFiles挂载本地static/目录,并把这些参数改为本地路径,从而让 API 文档在离线、内网环境中照常工作。 - 参考材料:文档 docs_src/custom_docs_ui/tutorial001_py310.py、docs_src/custom_docs_ui/tutorial002_py310.py,实现 fastapi/openapi/docs.py、fastapi/applications.py,测试 tests/test_tutorial/test_custom_docs_ui/。
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考