Repository navigation
feat(stream): 控制台实时流(Engine.IO v4 + Socket.IO v5 最小客户端) - #1
Merged
Merged
Conversation
控制台原来只能 3 秒轮询 `outputlog`(纯文本尾部),实时性差、还浪费请求。面板本身有实时流:
`POST /api/protected_instance/stream_channel` 换一个一次性口令,然后连 `addr` 上的 socket.io,
节点的 stdout 就会一条条推过来(面板前端就是这么做的)。
**`MCSMAPI`(生成客户端侧)**
- spec 新增 `POST /api/protected_instance/stream_channel`(`getStreamChannel`):
响应同时声明外壳 `{status,data,time}` 与裸形状(面板 10.x 实际是前者),`text/plain` 也收;
- `MCSMStreamChannel`:把两种响应形状抹平成"连哪 + 口令 + 前缀 + 地址重映射",
还支持从纯文本 JSON 解析(面板的老毛病:JSON 走 `text/plain`)。
**`MCSMStream`(新 product,无第三方依赖)**
- `MCSMStreamEndpoint`:把 `addr` 解析成 socket.io URL。规则照抄面板前端
`parseForwardAddress`:显式协议优先,否则跟随面板(https→wss);
**`127.0.0.1`/`localhost` 换成本面板主机名**(面板返回的是节点视角地址,客户端连不上);
`prefix` 拼进出 `/socket.io` 路径。
- `SocketIOFrame`:Engine.IO v4 / Socket.IO v5 帧编解码(`0` open、`2/3` ping/pong、
`40/41` 连接、`42` 事件、`44` 错误)。事件参数保留**原始 JSON 文本**而不是 `[String: Any]`
(`Any` 既非 `Equatable` 也非 `Sendable`),取值走 `authAccepted` / `stdoutText` /
`instanceLifecycle`;编码用 `.withoutEscapingSlashes`,`stream/auth` 不会被写成 `stream\/auth`。
- `MCSMStreamClient`(actor):`URLSessionWebSocketTask` 跑一次完整连接
(open → `40` → `stream/auth` → `stream/detail` → 转发 `instance/stdout`),
自动回 pong(不回会被面板 25 秒后踢掉),握手阶段带超时。
**口令是一次性的**,所以重连要重新申请 → 构造时给的是 `makeEndpoint` 闭包而不是固定 URL;
重试用指数退避,全失败不崩,发 `.fallback(reason:)` 让调用方退回轮询
(`dataPlane=direct` 且节点在 NAT 后面时直连本来就不可达,这是正常路径)。
另有 `sendCommand(_:)`(走 `stream/input`),本包暂不使用,留给控制台"低延迟发命令"。
**测试 20 条**(`swift test` 本地全绿)
- 帧编解码:控制包、事件取值、怪帧不崩(空串/半截 JSON/二进制前缀/无参数)、编码形状;
- 地址解析:proxy/direct/回环替换/前缀/显式协议/http 面板/非法地址;
- 通道模型:外壳、裸形状、纯文本 JSON、缺字段;
- **端到端**:测试里起一个**真的 WebSocket 服务端**(Network.framework + `NWProtocolWebSocket`,
不依赖 node/npm),跑通「鉴权通过后收输出」「口令被拒 → 重试用尽 → `.fallback`」
「服务端 ping 要回 pong」「实例退出事件透传」「连不上不崩」。
**真机实测踩到的坑**:`wss://节点:443/socket.io?EIO=4&transport=websocket`(少尾部斜杠)
被反向代理直接 404,`URLSessionWebSocketTask` 表现为
`-1011 Websocket handshake failure`(看不到 404 的原因)。面板前端的 socket.io 客户端请求的是
`/socket.io/?EIO=4…`,所以路径必须补上尾部 `/`。
- `StreamEndpoint.resolve`:`/socket.io` → `/socket.io/`(并把这行注释写清楚,别再改回去);
- 单测的期望 URL 同步更新;
- 新增 `LiveStreamTests`(**默认跳过**,靠 `ORZMC_MCSM_LIVE_*` 环境变量启用):真面板上
申请通道 → 连 socket.io → 期望 `stream/auth` 回 `true` → 发 `/list` → 期望收到实时输出。
等待是**有界**的(实例安静时实时流本来就不发东西,不能无限等)。
实测(`mcs.jokerhub.cn`,direct 模式,节点 `wss://mcs-node.jokerhub.cn:443`):
```
[live] addr=wss://mcs-node.jokerhub.cn:443 prefix= mappings=0
[live] ready=true firstOutput=/l\u{1b}[31m/li\u{1b}[0m\u{1b}[K…
```
—— 鉴权通过,输出带 ANSI 码,说明协议、路径、口令投递都对。
`/api/auth` 是面板前端的登录态聚合接口,官方 apidoc 只写了 admin 那套 —— 我们用它列 普通用户的实例(实测 10.18.0 可用,README「权限」节有记录)。之前这个 job 一直红着, 红久了就没人看它了,反而放过真正的 drift。现在加**显式白名单**(带理由),其余端点照旧严查: 新加的 `/api/protected_instance/stream_channel` 就在官方文档里,能正常校验到。 (顺带记一笔:这条提交信息第一版用 `-m "…\`…\`…"` 写的,反引号被 shell 当命令执行掉了 —— AGENTS.md §7 早就写了"多行提交信息一律写进文件再 `-F`",这次又踩了一次。)
wangzhizhou
force-pushed
the
feat/console-stream
branch
from
October 27, 2026 00:15
0833aa0 to
8396aa3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
控制台原来只能 3 秒轮询
outputlog。面板本身有实时流:POST /api/protected_instance/stream_channel换一次性口令 → 连addr上的 socket.io → 节点 stdout 一条条推过来。MCSMAPIgetStreamChannel:响应同时声明外壳{status,data,time}与裸形状,text/plain也收;MCSMStreamChannel:抹平两种形状 + 支持从纯文本 JSON 解析。MCSMStream(新 product,零第三方依赖)MCSMStreamEndpoint:照抄面板前端parseForwardAddress的规则(显式协议优先;127.0.0.1/localhost换成本面板主机名;prefix拼进/socket.io);SocketIOFrame:Engine.IO v4 / Socket.IO v5 帧编解码;事件参数保留原始 JSON 文本(Any不满足Equatable/Sendable);编码用.withoutEscapingSlashes;MCSMStreamClient(actor):URLSessionWebSocketTask完成 open →40→stream/auth→ 转发instance/stdout,自动回 pong(不回会被面板 25 秒后踢),握手带超时。口令一次性 → 构造时收makeEndpoint闭包,重连重新申请;重试指数退避,全失败发.fallback让调用方回退轮询(direct模式 + 节点在 NAT 后时直连本来就不可达)。测试(
swift test本地 20 条全绿)含端到端:测试内起真 WebSocket 服务端(Network.framework +
NWProtocolWebSocket,不依赖 node/npm),覆盖「鉴权后收输出」「口令被拒 → 重试用尽 →.fallback」「服务端 ping 要回 pong」「实例退出透传」「连不上不崩」。