1. 为什么你的Swagger UI在内网“裸奔”了?
最近在做一个企业内部的自动化平台,用的是FastAPI,开发体验那叫一个丝滑。但项目部署到客户的内网环境后,问题来了:我们团队习惯用的API文档页面 http://localhost:8000/docs 打开后,样式全无,只剩下光秃秃的HTML和一堆功能失效的按钮,活像被扒了衣服。一开始还以为是服务没跑起来,检查了半天端口和日志,一切正常。后来才恍然大悟——客户的生产环境是物理隔离的,服务器压根连不上互联网。
这其实就是FastAPI默认Swagger UI的一个“特性”。为了方便开发者,FastAPI在生成那个漂亮的文档页面时,默认会从公共CDN(内容分发网络)引用两个关键资源:swagger-ui-bundle.js 和 swagger-ui.css。你可以在浏览器里按F12打开开发者工具,在“网络”选项卡里刷新docs页面,十有八九能看到它正在尝试从 cdn.jsdelivr.net 这样的域名加载文件。在能上网的环境下,这当然没问题,速度快又省事。可一旦机器处于无外网环境、企业严格内网或者某些安全保密要求极高的项目中,这些外部请求就会全部失败,导致Swagger UI的样式和交互脚本加载不出来,文档页面也就“瘫痪”了。
这种场景其实比你想象中更常见。除了军工、金融这些对网络安全有极端要求的行业,很多传统企业的内部生产网、研发测试环境,甚至是一些物联网设备的封闭网络中,都会遇到类似问题。难道就因为没网,我们就要放弃这个强大的API文档和调试工具吗?当然不。解决思路很直接:把Swagger UI需要的外部资源,提前“搬”到我们的项目里,让FastAPI从本地加载它们。这个过程,我们称之为Swagger UI的本地化部署或离线化改造。接下来,我就带你一步步实操,把这个“裸奔”的文档页面重新打扮得漂漂亮亮。
2. 实战第一步:获取Swagger UI的“离线安装包”
要让Swagger UI离线工作,我们首先得拿到它的“源代码”或者说“资源包”。最官方、最可靠的来源就是它的GitHub仓库。别担心,这个过程不需要你在离线环境里操作,你可以在任何能上网的电脑上完成下载,然后再把文件拷贝进去。
打开浏览器,访问 Swagger UI 的 GitHub 发布页面:https://github.com/swagger-api/swagger-ui/releases。你会看到很多版本标签,建议选择最新的稳定版(通常排在最上面)。找到 Assets 折叠区域,点击展开,你会看到几个压缩包。我们通常选择 swagger-ui.zip 这个文件进行下载。这个ZIP包里面包含了Swagger UI完整的前端资源,正是我们需要的。
下载完成后,解压这个ZIP文件。解压后的目录结构里,你会找到一个名为 dist 的文件夹。这个 dist 文件夹,就是我们本次任务的核心目标。它里面存放的是已经构建好、可以直接在浏览器中运行的静态文件。用前端的话说,这就是“构建产物”。我们主要关心其中的三个文件:
swagger-ui-bundle.js: 这是所有功能的打包文件,包含了Swagger UI的核心逻辑、渲染引擎等。swagger-ui.css: 这是样式表,决定了文档页面的布局、颜色、字体等所有视觉效果。favicon-32x32.png: 这个是浏览器标签页上显示的小图标,可选,但有了它会更完整。
现在,在你的FastAPI项目根目录下,创建一个用于存放静态资源的文件夹,比如叫做 static。然后在 static 文件夹里再创建一个子文件夹,专门用来放Swagger UI的资源,例如就叫 swagger。最后,把刚才从 dist 目录里找到的那三个文件,复制到你项目的 your_project/static/swagger/ 路径下。至此,资源准备阶段就完成了。你可以把这个 static 文件夹看作是你的本地“CDN”,FastAPI服务将从这里读取文件,而不是去遥远的互联网上请求。
3. 核心改造:告诉FastAPI“请用本地资源”
资源已经备好,接下来就要修改FastAPI的配置,让它“改邪归正”,从本地加载资源。默认情况下,FastAPI使用内置的 get_swagger_ui_html 函数快速生成文档页面,但这个函数内部写死了CDN的URL。我们需要“拦截”这个过程,提供一个自定义的版本。
首先,在你的主应用文件(比如 main.py)中,我们需要进行几处关键修改。第一步是禁用FastAPI自动生成的默认docs路由,因为我们打算自己来定义它。在创建FastAPI应用实例时,设置 docs_url=None。
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.staticfiles import StaticFiles
# 创建应用实例,并禁用自动生成的/docs路由
app = FastAPI(docs_url=None, redoc_url=None) # 也一并禁用redoc
接着,我们需要把刚才存放资源的 static 目录挂载为静态文件路由。这样,当浏览器请求 /static/swagger/swagger-ui-bundle.js 时,FastAPI才知道去哪个文件夹里找这个文件。
# 挂载静态文件目录,将`./static`目录下的文件通过`/static`路径提供访问
app.mount("/static", StaticFiles(directory="static"), name="static")
最关键的一步来了:我们需要自己编写一个处理函数,来响应 /docs 路径的访问。这个函数将调用 get_swagger_ui_html,但通过参数告诉它使用我们本地的资源路径。
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title="My API Docs - Local", # 可以自定义标题
swagger_js_url="/static/swagger/swagger-ui-bundle.js",
swagger_css_url="/static/swagger/swagger-ui.css",
swagger_favicon_url="/static/swagger/favicon-32x32.png", # 可选,添加本地图标
)
让我解释一下这几个参数:
openapi_url=app.openapi_url: 这个指向你的OpenAPI规范JSON文件的URL,通常是/openapi.json,保持默认即可。title: 你可以在这里设置文档页面的标题。swagger_js_url和swagger_css_url: 这是最核心的两个参数。我们把它们从默认的CDN地址,改成了我们本地挂载的静态资源路径/static/swagger/...。swagger_favicon_url: 如果你也拷贝了图标文件,可以在这里指定,让标签页图标也正常显示。include_in_schema=False: 这个参数很巧妙,它意味着我们自定义的这个/docs路由本身不会出现在它生成的API文档里,避免了循环引用和干扰。
完成这些修改后,你的项目目录结构应该看起来是这样的:
your_project/
├── main.py
└── static/
└── swagger/
├── swagger-ui-bundle.js
├── swagger-ui.css
└── favicon-32x32.png
4. 验证与调试:让本地文档“跑起来”
代码写好了,资源也放对了地方,是时候验收成果了。启动你的FastAPI应用(例如使用 uvicorn main:app --reload),然后在浏览器中访问 http://localhost:8000/docs。
如果一切配置正确,你应该能看到样式完整、功能正常的Swagger UI界面。为了确保资源确实是从本地加载的,我们还需要做一次“体检”。打开浏览器的开发者工具(F12),切换到“网络”(Network)选项卡,然后刷新docs页面。在加载的资源列表里,你应该清晰地看到 swagger-ui-bundle.js 和 swagger-ui.css 这两个文件的来源(Source)是你的本地服务器地址(如 localhost:8000),并且状态码是200(成功)。绝对不应该再看到任何对 cdn.jsdelivr.net 或其他外部域名的请求。这是验证本地化是否成功的黄金标准。
在这个过程中,你可能会遇到一些常见的“坑”。我把自己踩过的几个分享一下:
- 404错误(文件找不到): 这是最常见的问题。首先,反复检查文件路径。确保
static/swagger/目录就在你的项目根目录下,并且文件名完全一致,包括大小写(在Linux服务器上尤其要注意)。其次,确认app.mount中directory参数的值是"static",它表示当前目录下的static文件夹。 - 样式还是加载失败: 如果页面有基本布局但样式很奇怪,可能是CSS文件路径不对,或者浏览器缓存了旧的、失败的CDN请求。尝试在开发者工具的网络面板中勾选“禁用缓存”(Disable cache),然后强制刷新(Ctrl+F5)。
- 功能按钮无效: 如果点击“Try it out”等按钮没反应,通常是JS文件没有正确加载。同样检查JS文件路径,并在浏览器控制台(Console)查看是否有JavaScript错误。
- 关于图标(favicon): 如果你没有配置
swagger_favicon_url或者图标文件缺失,浏览器可能会发起一个对/favicon.ico的请求并报404。这通常不影响核心功能,但如果你追求完美,可以放一个图标文件在static根目录,或者就使用我们准备好的Swagger UI自带图标。
完成验证后,你就可以将整个项目打包,部署到那个没有外网的环境中了。只要保证 static 文件夹随着你的代码一起被拷贝过去,Swagger UI就能独立、正常地工作。
5. 进阶优化与生产环境考量
基础的本地化部署完成后,我们可以根据实际项目需求,做一些优化和增强,让它更贴合生产环境。
首先,考虑资源版本管理。 直接从GitHub下载的 dist 文件,其内部可能包含哈希值作为版本标识。但在实际开发中,你可能希望锁定一个特定的Swagger UI版本,以避免未来升级带来的意外变化。一个实用的做法是,在项目的 static/swagger/ 目录下,创建一个 version.txt 文件,里面记录你下载的Swagger UI版本号(例如 v5.9.0)。这样,半年后当你需要更新时,能清楚地知道当前基底是什么。
其次,自定义UI主题和品牌。 Swagger UI的CSS是完全可以自定义的。你可以在 static/swagger/ 目录下创建自己的CSS文件,比如 custom-theme.css,在里面覆盖默认的样式变量,比如修改主题色、字体,甚至隐藏一些你不希望用户看到的UI组件。然后,在你的 custom_swagger_ui_html 函数中,通过 swagger_css_url 参数引入多个CSS文件(虽然函数参数只接受一个,但你可以通过修改HTML模板或引入额外link标签的方式实现),让你的API文档更贴合企业品牌规范。
第三,关于OpenAPI规范文件(openapi.json)本身。 我们的改造只解决了UI资源的加载问题。请注意,Swagger UI页面本身还会向 openapi_url(默认为 /openapi.json)发起请求,以获取API的元数据。这个请求是发向你自己的服务器的,所以不受外网限制。但是,如果你的FastAPI应用在生成OpenAPI规范时,依赖了某些需要网络访问的配置(极其罕见),也需检查。99%的情况下,这都不是问题。
最后,安全与权限思考。 在内网环境部署,虽然解决了网络可达性问题,但有时也需要对API文档的访问加以限制。FastAPI本身不提供文档页面的认证,但你可以轻松地给你的 /docs 路由加上依赖项。例如,你可以写一个简单的验证函数,检查请求头中的某个令牌(Token),或者结合你项目现有的登录认证体系。
from fastapi import Depends, HTTPException, Header
async def verify_internal_token(x_internal_token: str = Header(None)):
if x_internal_token != "your_secret_token_here":
raise HTTPException(status_code=403, detail="Forbidden")
@app.get("/docs", include_in_schema=False, dependencies=[Depends(verify_internal_token)])
async def custom_swagger_ui_html():
# ... 函数体保持不变
这样,只有携带了正确令牌的请求才能看到API文档页面,为你的内网API增加了一层简单的访问控制。
354



被折叠的 条评论
为什么被折叠?



