一个面向子路径代理环境的端口访问与导航修复工具,自动探测平台代理模板、生成端口代理链接,并提供统一的 Service Worker(子路径修复或 HTTP 隧道)以提升在 JupyterLab、Code Server、AI Studio 等环境下的可用性。
- 自动识别运行环境:JupyterLab、Code Server、AI Studio
- 代理URL模板生成:返回最短子路径模板(如
/proxy/{{port}}/或平台变量拼接) - nginx解码深度辅助检测:前端探测代理层对参数的解码深度(用于子路径策略编码补偿)
- 实时检测端口监听状态(TCP connect_ex)
- 展示进程信息(PID、完整命令行)
- 智能增量更新:只更新变化的端口,保护用户交互
- 数据源:SW registrations(浏览器持久化)
- 后端 LRU 缓存:自动淘汰不常用端口
- 动态策略切换:通过
postMessage实时配置策略 - 四种模式:
- None:不处理任何请求(默认)
- Subpath:修正请求路径前缀与多层编码错位
- Tunnel:改写请求到后端 HTTP 隧道透传
- Hybrid:智能混合(大部分走 Subpath,包含
%2F的走 Tunnel)
- 导航拦截器:自动注入脚本,修复子路径环境下的导航行为
- 自动注册:添加端口时自动注册 SW
- 路由:
- GET
/静态首页 - GET
/api/port/{port}单端口查询(LRU 缓存,最多 100 个) - GET
/api/url-template环境 URL 模板 - GET
/api/test-encoding/{path:.*}原始路径(用于编码检测) - ANY
/api/http-tunnel/{port}HTTP 隧道透传 - GET
/unified_service_worker.js统一 SW 脚本 - GET
/navigation_interceptor.js导航拦截器 /static/*静态资源
- GET
- 端口与进程:psutil.net_connections 定位进程,返回 PID/名称/完整 cmdline
- LRU 缓存:自动淘汰不常用端口,无需显式删除
- 启动条件:仅在检测到子路径环境时启动
- JupyterLab:枚举运行中的服务器,返回
base_url + proxy/{{port}}/ - Code Server:读取环境变量
VSCODE_PROXY_URI - AI Studio:拼接
STUDIO_MODEL_API_URL_PREFIX + JUPYTERHUB_SERVICE_PREFIX + gradio/{{port}}/ - detect_service_config:识别服务类型,返回路径段数最短的模板
- generate_proxy_url(port):用模板替换
{{port}}
- 自动安装依赖(aiohttp、jupyter-server、psutil、requests)
- 以
GRADIO_SERVER_PORT(默认7860)启动PortServer,不做子路径检查
- index.html / style.css:VSCode 风格端口管理界面
- app.js:
- 数据源:从 SW registrations 读取端口列表
- 智能增量更新:只更新变化的端口行,保护用户交互
- 编码检测:自动检测基准解码深度和
%2F特殊解码 - 策略切换:通过
postMessage动态配置 SW - 快速删除:立即清理前端状态,异步注销 SW
- unified_service_worker.js:统一 SW 脚本
- 支持四种策略:None/Subpath/Tunnel/Hybrid
- 通过
postMessage动态配置 - Hybrid 模式:根据路径内容智能路由
- 导航拦截器注入
- navigation_interceptor.js:修正子路径导航行为
// 1. 基准深度检测(使用空格,避免 %2F 干扰)
const testSegment = "test path"; // → "test%20path"
// 发送多层编码,通过反向编码计算解码深度
// 2. %2F 特殊解码检测
const testSegment = "test/path"; // → "test%2Fpath"
// 如果后端返回 "test/path"(包含真实斜杠),说明 %2F 被额外解码// 只更新变化的端口行,保护用户交互
displayPorts(ports) {
// 检测用户是否正在操作
if (document.activeElement.tagName === 'SELECT') {
return; // 跳过更新,避免打断
}
// 增量更新:只添加/删除/更新变化的行
allPorts.forEach(port => {
const existingRow = tbody.querySelector(`tr[data-port="${port.port}"]`);
if (!existingRow) {
tbody.appendChild(createRow(port)); // 新端口
} else {
updateRowIfNeeded(existingRow, port); // 只更新变化的单元格
}
});
}// SW 中根据路径内容动态选择处理方式
if (strategy === 'hybrid') {
if (slashExtraDecoding && /%2F/i.test(pathname)) {
TunnelHandler.handleFetch(event); // 包含 %2F 走 Tunnel
} else {
SubpathHandler.handleFetch(event); // 其他走 Subpath
}
}GET / # 主界面
GET /api/port/{port} # 单端口信息(后端 LRU 缓存)
GET /api/url-template # 代理模板与支持标记
GET /api/test-encoding/{path} # 返回原始路径(用于编码检测)
* /api/http-tunnel/{port}?u=/... # HTTP 隧道透传
GET /unified_service_worker.js # 统一 SW 脚本
GET /navigation_interceptor.js # 导航拦截器
GET /static/* # 静态资源
| 策略 | 适用场景 | 原理 | 优势 |
|---|---|---|---|
| None | 端口服务已正确处理子路径 | 不处理任何请求 | 零开销 |
| Subpath | 标准子路径代理 | 修正路径前缀与编码错位 | 轻量、高性能 |
| Tunnel | 复杂代理环境 | 改写为后端隧道透传 | 覆盖面广 |
| Hybrid | %2F 被特殊解码的环境 |
智能路由(普通走 Subpath,%2F 走 Tunnel) |
兼顾性能与兼容性 |
python server.py --host 0.0.0.0 --port 3000
# 仅在检测到子路径环境时启动python main.gradio.py
# 默认端口 7860,可通过 GRADIO_SERVER_PORT 环境变量指定pip install -r requirements.txt