DSH plugin 注册 WebRoute:匹配顺序、fallback 席位与 index 挂接点
DSH plugin 的 HTTP 载体刻意「什么都不懂」:它不理解任何 Harness 概念,只提供命名路由、可选的 gzip、index 转换回调和一个可认领的回退处理器。 所有功能路由——/api 桥接、插件 bundle、HMR 事件流——都由别的插件注册。这个定位带来三个必须记住的规则:匹配顺序固定为 exact → 最长前缀 → fallback,注册顺序不携带语义;fallback 只有一个所有者;它只服务浏览器(Electron 走 file:// 加 IPC,不经过它)。
DSH plugin 的 HTTP 载体:只管浏览器,不碰业务
一句话结论:载体提供传输能力,功能路由全由插件注册;它不属于 agent loop,也不是能力 seam。
- 本体:一个提供
ctx.webServer的node:http插件。 - 包含什么:具名路由注册表、可选 gzip、index 转换回调、可认领的回退。
- 不包含什么:TLS、认证、Origin 策略都不在载体里。
- 服务对象:只服务浏览器;桌面端用
file://与 IPC,不用这个服务器。 - 输出归属:包本身不打印任何东西,启动 URL 行归 shell 所有。
DSH plugin 的 WebRoute 与匹配顺序:exact → 最长前缀 → fallback
一句话结论:匹配顺序固定,注册顺序不参与请求语义。
interface WebRoute {
kind: 'exact' | 'prefix'
path: string // 绝对路径名,无尾斜杠
handler: (req, res) => void | Promise<void>
}
- exact:逐字匹配路径名。
- prefix:匹配该前缀及其下任意子路径。
- 顺序:先 exact 表,再最长匹配前缀,最后 fallback。
- 互不相交:具名路由在组合上必须互不重叠,重复的
(kind, path)直接抛错。 - upgrade 路由:同一路径只能有一个协议所有者,重复注册同样抛错。
- handler 职责:拥有完整响应生命周期,可以像 SSE 那样把连接一直开着。
DSH plugin 的 fallback 席位:只能有一个所有者
一句话结论:回退处理器唯一,第二次注册抛错;未认领的请求在启动期返回 404。
- 语义:应答所有未被命名路由匹配的请求。
- 唯一性:两个回退无法组合,因此只允许一个所有者。
- 随产品交付的认领者:前端静态插件认领席位,提供 SPA dist 的固定语义。
- 静态语义细节:读取 dist 前先完成认证;非 index 资产公开;非 GET/HEAD 返回 405;越出根目录返回 403;缺失返回空 404;未知扩展名按 octet-stream。
- 启动期:席位未被认领时,未匹配请求按 404 应答,直到所有者注册。
DSH plugin 的 index 挂接:结构化注入与原始 tap
一句话结论:先渲染结构化注入行,再按注册顺序应用原始 html 转换。
- 结构化行:
collectIndexInjections()通过一次webserver/index-inject事件收集,订阅方各自推入当前行。 - 每次都是最新:调用时重新收集,订阅方可以读到当时的模块图与主题偏好。
- 原始逃生口:
tapIndex(transform)注册纯 html 到 html 的转换。 - 应用顺序:
renderIndex(html)先渲染结构化行,再按注册顺序应用 tap。 - 谁调用:fallback 所有者渲染每次 index 响应时调用。
- 模块清单:客户端模块包正是以启动 manifest 行回应这个事件。
DSH plugin 的配置与生命周期:监听、压缩与拆卸
一句话结论:激活即监听,失败就拒绝初始化;拆卸必须强制关闭连接。
| 配置 | 取值 | 默认 |
|---|---|---|
host | 127.0.0.1 或 0.0.0.0 | 回环 |
port | 数字,0 表示系统分配 | — |
compression | none / gzip | none |
compressionLevel | 0–9 | 1 |
compressionThresholdBytes | 最小压缩长度 | 1024 |
- 绑定即暴露:载体不拥有 TLS、认证或 Origin 策略,绑非回环地址就是把服务暴露出去,你必须自己补上这些控制。
- 随附命令更保守:
dsh web选回环并拒绝--host 0.0.0.0,其 Connection 插件负责 Host/Origin 校验与会话认证。 - 压缩只作用于符合条件的 socket 响应:已有内容编码、
no-transform、范围响应、SSE、ZIP 与预压缩文件保持 identity。 - 监听失败:初始化被拒绝,启动过程报告失败的 fiber。
- 异常处理:处理器抛错记警告并回 400(响应头已发出则销毁 socket),不会让进程退出。
- 拆卸:
close()配closeAllConnections(),否则 SSE 一类长连接会让拆卸挂起。
DSH plugin 排查注意事项
一句话结论:HTTP 载体问题先分「路由没命中」「席位被占」「index 没注入」三类。
- 请求 404 → 没有命名路由命中且 fallback 未认领,或路径与注册不一致。
- 路由冲突报错 → 重复的
(kind, path)或重复 upgrade 路径,属配置错误。 - fallback 注册抛错 → 已有所有者,检查是不是重复认领。
- 注入没生效 → 确认 fallback 所有者调用了
renderIndex,且行在事件里推入。 - gzip 没压 → 响应属于 identity 白名单(范围、SSE、预压缩等)。
- 修复状态如实:本文的匹配顺序、席位唯一性、index 挂接与配置语义均取自官方「HTTP 服务器」页面;静态认领者细节以对应包源码为准。
常见问题
DSH plugin 的路由匹配固定为:先查 exact 表,再取最长匹配前缀,最后落到 fallback。注册顺序不携带任何面向请求的语义,具名路由在组合上必须互不相交。
不能,DSH plugin 的 fallback 席位只有一个所有者,第二次注册会抛错,因为两个回退无法组合。发布的 Web 组合由前端静态插件认领该席位。
会抛错,DSH plugin 把重复的 (kind, path) 视为配置错误,因为路由模式是组合层约定。HTTP upgrade 路由同理:同一路径只能有一个协议所有者。
DSH plugin 提供两条:结构化的 IndexInjection 行,以及原始 html 到 html 的 tapIndex 转换。渲染时先应用结构化行,再按注册顺序应用 tap 转换。
DSH plugin 的 WebServer 在激活时立即监听,监听失败(例如端口被占用)会让初始化被拒绝,启动过程会报告失败的 fiber,而不是静默降级。
相关术语
- WebRoute(命名路由)
- WebRoute 是 DSH plugin HTTP 载体上的一条命名路由,含 exact 或 prefix 的匹配方式、绝对路径与拥有完整响应生命周期的处理器。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
- WebRouteKind(路由匹配方式)
- WebRouteKind 决定路径如何匹配:exact 逐字匹配路径名,prefix 匹配该前缀及其下任意子路径。匹配顺序永远是 exact、最长 prefix、fallback。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
- fallback 席位
- fallback 席位是 DSH plugin HTTP 载体上唯一的回退处理器,应答所有未被命名路由匹配的请求。它只有一个所有者,第二次注册抛错。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
- IndexInjection
- IndexInjection 是 DSH plugin 的结构化 index.html 注入行,通过一次 webserver/index-inject 事件收集,每次调用都是最新状态。— https://deepseek-harness.github.io/deepseek-harness/reference/subsystems/web-server
来源
- DeepSeek Harness 文档 — HTTP 服务器(路由、配置、服务与 index 注入)· deepseek-harness.github.io
- dsh-host-webserver 源码(路由注册与 fallback)· deepseek-ai(GitHub)
- dsh-host-frontend-static(认领 fallback 的 SPA dist 服务器)· deepseek-ai(GitHub)