FastAPI 文档 UI 静态资源自定义:切换 CDN 与完全离线自托管
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
FastAPI 的交互式 API 文档由Swagger UI(默认位于/docs)与ReDoc(默认位于/redoc)渲染,二者都需要加载若干 JavaScript 与 CSS 文件,默认从公共 CDN 拉取。当你的部署环境无法访问默认 CDN,或需要在内网、离线环境中使用文档时,你可以利用 docs/ja/docs/how-to/custom-docs-ui-assets.md 介绍的两种方案——切换自定义 CDN与本地静态文件自托管——来完全掌控这些资源。读完本文,你将掌握如何关闭 FastAPI 内置的文档路由、用内部 HTML 生成函数重建文档页面,以及如何在同一个 FastAPI 应用中挂载StaticFiles实现"断网也能看文档"。
一、背景:文档页面为什么要"加载资源"
Swagger UI 与 ReDoc 本质上是运行在浏览器里的前端单页应用,它们需要一个渲染"外壳"(一段 HTML),HTML 里再通过<script>、<link>引用一套 JS/CSS 来获得交互能力。
打开 fastapi/applications.py 可以看到 FastAPI 应用在构造时的相关默认值:
| 参数 | 默认值 | 说明 |
|---|---|---|
docs_url | "/docs" | Swagger UI 文档路由,设为None可关闭 |
redoc_url | "/redoc" | ReDoc 文档路由,设为None可关闭 |
openapi_url | "/openapi.json" | OpenAPI schema 路由,设为None会连带自动禁用文档 |
swagger_ui_oauth2_redirect_url | "/docs/oauth2-redirect" | Swagger UI 的 OAuth2 回调辅助页 |
而 JS/CSS 的具体来源,定义在 fastapi/openapi/docs.py 的函数默认参数中,例如:
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.cssredoc_js_url默认https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js
也就是说,只要替换这几个 URL,就能把文档资源"搬"到你指定的任何位置。
二、方案一:让文档加载自定义 CDN
适用场景:默认cdn.jsdelivr.net在你的网络环境下不可达(例如某些 URL 受限的地区),或你希望统一走公司内部 CDN 以提升加载速度。改用https://unpkg.com/是最常见的例子。
2.1 关闭自动文档路由
FastAPI 创建应用时会自动注册/docs、/redoc路由(见 fastapi/applications.py 的setup()方法),并把这些路由的 HTML 生成逻辑连同默认 CDN URL 一起固定下来。因此第一步是把它们关掉,改成手动接管:
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)注意:docs_url=None与redoc_url=None分别关掉两个文档入口,openapi_url(即/openapi.json)仍然保留。从源码看,文档路由的注册条件是if self.openapi_url and self.docs_url/if self.openapi_url and self.redoc_url,所以只要你保留openapi_url,后续手写的文档页面依然能通过app.openapi_url拿到 schema 地址。
2.2 用内部 HTML 生成函数重建文档页
关闭自动文档后,需要自己注册/docs、/redoc两个path operation。FastAPI 在fastapi.openapi.docs模块里暴露了三个可复用的内部函数(定义见 fastapi/openapi/docs.py):
| 函数 | 作用 |
|---|---|
get_swagger_ui_html(...) | 返回加载 Swagger UI 的HTMLResponse |
get_swagger_ui_oauth2_redirect_html() | 返回 Swagger UI 的 OAuth2 跳转辅助页HTMLResponse |
get_redoc_html(...) | 返回加载 ReDoc 的HTMLResponse |
get_swagger_ui_html的关键参数如下(Swagger UI 侧):
openapi_url:文档 HTML 去拉取 OpenAPI schema 的 URL,这里直接复用app.openapi_url;title:页面标题,通常用app.title拼接后缀;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 侧大同小异,唯一的资源参数是redoc_js_url。完整实现(对应 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", )几点实现细节:
include_in_schema=False:文档路由本身不应出现在生成的 OpenAPI schema / API 文档列表里;- OAuth2 回调路由的路径直接取
app.swagger_ui_oauth2_redirect_url(默认/docs/oauth2-redirect),因此只要使用docs_url=None时也会一并关闭、需要手工补回; @app.get(app.swagger_ui_oauth2_redirect_url, ...)意味着注册路径来自 FastAPI 实例属性,而不是写死字符串,便于后续统一修改。
2.3 为什么要保留 OAuth2 跳转页
get_swagger_ui_html生成的 HTML 里,当传入oauth2_redirect_url时会拼接出oauth2RedirectUrl: window.location.origin + '...'配置。当你的 API 对接了 OAuth2 提供方后,文档页上的Authorize授权流程需要先把浏览器带到认证服务器,拿到凭据后再跳回文档页继续交互——Swagger UI 在后台完成这一过程,而"跳回"这一步依赖的就是/docs/oauth2-redirect这个辅助页。因此,只要 Swagger UI 需要支持 OAuth2 授权,就必须保留这个路由。
2.4 加一个普通接口用于验证
为了确认改动没有破坏应用本身,再注册一个普通的path operation:
@app.get("/users/{username}") async def read_user(username: str): return {"message": f"Hello {username}"}2.5 验证效果
启动应用后访问 http://127.0.0.1:8000/docs,在开发者工具中观察 Network 面板,可以看到swagger-ui-bundle.js等资源是从unpkg.com加载的。仓库自带的测试也验证了这一行为,见 tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py:测试通过断言响应文本中包含https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js、https://unpkg.com/swagger-ui-dist@5/swagger-ui.css等 URL 来确认自定义 CDN 已生效,同时验证/docs/oauth2-redirect返回的是 Swagger UI 的 OAuth2 跳转脚本。
三、方案二:静态文件完全本地自托管(离线可用)
适用场景:应用运行在无法访问互联网的离线环境或隔离内网,连 CDN 也够不到,却仍需要完整的交互式 API 文档。
思路很简单:把文档需要的 JS/CSS 文件下载到项目本地,用 FastAPI 自己的StaticFiles把它当成普通静态资源对外提供,再让文档 HTML 的swagger_js_url等参数指向本站地址。对应示例见 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 | 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说明:这三个文件即上文中
get_swagger_ui_html/get_redoc_html默认参数的本地版本,下载版本号对齐源码中的默认值(swagger-ui-dist@5、redoc@2)即可保证功能一致。
3.3 用 StaticFiles 挂载静态目录
在main.py中导入并挂载StaticFiles:
from fastapi.staticfiles import StaticFiles app = FastAPI(docs_url=None, redoc_url=None) app.mount("/static", StaticFiles(directory="static"), name="static")app.mount("/static", ...)会把应用内static目录下的所有文件映射到 URL 前缀/static/下,name="static"用于在模板或反向解析中引用这个挂载点。
3.4 先验证静态文件能否访问
启动应用,访问 http://127.0.0.1:8000/static/redoc.standalone.js。浏览器应返回一段很长的压缩 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.5 关闭自动文档,把 URL 指向本站
同样先把自动文档关掉(docs_url=None, redoc_url=None),然后像方案一那样重建文档路由,区别在于这次资源 URL 写的是本站路径:
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}"}仓库测试 tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py 同样验证了这一行为:它断言/docs响应中包含/static/swagger-ui-bundle.js、/static/swagger-ui.css,/redoc响应中包含/static/redoc.standalone.js,并验证/users/john业务接口照常工作。
3.6 离线验证
关闭本机网络(如断开 WiFi),再次访问 http://127.0.0.1:8000/docs 并刷新页面——只要浏览器没把资源缓存掉、也确实断网,页面仍能正常渲染出可交互的 API 文档。至此,文档的 JS/CSS 已完全不依赖外部网络。
3.7 追求"零外网请求"时的补充细节
如果对完全离线要求严格,从 fastapi/openapi/docs.py 的默认参数可以看出还有两处"漏网"的外部引用值得一并处理:
get_redoc_html的with_google_fonts参数默认是True,生成的 ReDoc HTML 会额外引入https://fonts.googleapis.com/...的字体样式表。完全离线时,可在调用处显式传with_google_fonts=False关闭,或接受"文档可用、字体回退为默认"的效果;get_swagger_ui_html的swagger_favicon_url默认指向 FastAPI 官网的 favicon 图片,get_redoc_html的redoc_favicon_url同理。介意的话可把 favicon 一并放进static/并传本地路径,实现真正全站零外链。
这些选项在你不干预时都不影响本教程的离线渲染结果,但它们决定了是否仍存在个别"发往公网"的请求。
四、底层原理:路由注册与 HTML 生成
理解"为什么这样改能生效",能帮你应对更复杂的定制需求。
路由注册时机。FastAPI 的setup()方法(fastapi/applications.py)在初始化阶段按条件注册内置路由:
- 若
openapi_url存在,注册/openapi.json路由; - 若
openapi_url与docs_url同时存在,调用get_swagger_ui_html(并带上init_oauth、swagger_ui_parameters等配置)注册 Swagger UI,同时注册 OAuth2 跳转路由; - 若
openapi_url与redoc_url同时存在,调用get_redoc_html注册 ReDoc。
因此把docs_url、redoc_url设为None等于取消了这些自动路由,而你自己写的装饰器路由随后接管同名路径——由于include_in_schema=False,也不会污染 schema。
HTML 的生成方式。get_swagger_ui_html(fastapi/openapi/docs.py)本质上是按模板拼接字符串,把openapi_url塞进url: '...'、把swagger_css_url/swagger_js_url塞进<link>与<script src>、把oauth2_redirect_url拼成oauth2RedirectUrl,最终返回HTMLResponse;get_redoc_html则生成带<redoc spec-url="...">标签与redoc.standalone.js引用的页面。这也是为什么"换一个 URL 参数"就能让整个文档资源源切换——它们只是生成 HTML 时的字符串插值。
可进一步定制的参数。在源码中,get_swagger_ui_html还支持swagger_ui_parameters(默认合并dom_id、layout、deepLinking等一组swagger_ui_default_parameters)与init_oauth;这些与本文主题相关的扩展能力在 fastapi/openapi/docs.py 中都有完整定义,如需调整 Swagger UI 行为(例如默认展开状态、OAuth 初始化选项)可直接查阅。
五、运行与回归验证
本地运行两个示例(示例代码位于 docs_src/custom_docs_ui/ 目录,分别对应两个tutorial*_py310.py):
fastapi dev docs_src/custom_docs_ui/tutorial001_py310.py或使用 uvicorn:
uvicorn docs_src.custom_docs_ui.tutorial002_py310:app --reload若想跑仓库自带的回归测试,可执行:
pytest tests/test_tutorial/test_custom_docs_ui/两个测试文件分别覆盖了"自定义 CDN"与"本地静态文件"两条链路,是验证改动正确性的快捷参考。
小结
- 想换文档资源源:
docs_url=None, redoc_url=None关闭自动文档 → 用get_swagger_ui_html/get_redoc_html重建路由 → 把swagger_js_url、swagger_css_url、redoc_js_url指向目标 CDN; - 想彻底离线:把上述三个文件下载进
static/→app.mount("/static", StaticFiles(directory="static"), name="static")→ 文档路由的 URL 参数改成/static/...; - 别忘了保留
/docs/oauth2-redirect路由以支持 Swagger UI 的 OAuth2 授权流程; - 追求"零外网请求"时,可顺带处理
with_google_fonts与 favicon 默认指向的外部资源。
两种方案的完整可运行示例与配套测试都在本仓库内(docs_src/custom_docs_ui/ 与 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),仅供参考