项目全链路

26 年 6 月 26 日 星期五
1258 字
7 分钟

从输入 URL 到页面加载完成的全链路

整个链路可分为 6 个阶段,跨越 浏览器主线程 → SharedWorker → SSE → HUB → WebRTC → Worker


第一阶段:路由匹配与布局嵌套

  1. SolidStart 文件路由 匹配到 routes/dev/browse/index.tsx

  2. 布局嵌套链(每个 (name) 是一个无路径分段的 route group):

    text
    app.tsx Layout
      └─ MetaProvider + StorageProvider + ThemeProvider + Suspense

    /dev/browse 是独立路由,不在 (connected)/(live) 组下,所以它自己包了自己的 ApiProviderConnectionManagerProvider

  3. ConnectionManagerProvider(连接管理器)onMount 时启动三项关键工作:

    • registerSharedWorkerPort(tabId) — 建立与 SharedWorker 的 MessagePort 通道
    • 向 SharedWorker 发送 SSE_CREATE_REQUEST 请求建立 SSE 长连接
    • 初始化心跳检测

第二阶段: SharedWorker 启动 & SSE 长连接建立

  1. 注册 SharedWorker 端口 (eventManager.ts:registerSharedWorkerPort):
    • 创建 SharedWorker 实例(connection/worker/SharedWorker.ts
    • 通过 port.postMessage 发送注册消息
    • MessageRouter 收到后记录该 tab 信息并回复 PORT_REGISTER_RESPONSE
    • 页面收到后启动 ClientHeartbeat(心跳检测)
  2. SSE 长连接建立 (ConnectionManagerProvider.initSSEConnection):
    • 页面通过 port.postMessage 发送 SSE_CREATE_REQUEST(含 URL 和 token)
    • MessageRouter 将其交给 SSEManager(运行在 SharedWorker 线程)
    • SSEManager 创建 EventSource 连接到 hub 的 SSE 端点
    • 成功连接后 hub 推送 {"type":"connected","peer_uuid":"..."}
    • SSEManager 广播 SSE_CREATE_RESPONSE 状态回所有页面
    • SSE 通道成为信令通道,后续 WebRTC 的 offer/answer/ICE candidate 都通过它中转

第三阶段: WebRTC 连接建立(按需触发)

  1. 当页面第一次调用 createPeerToPeerLink(workerId) 时(如 useFileServiceuseTerminalService),Peer 类构造函数被执行:
    • 向 SharedWorker 发送 RTC_CREATE_REQUEST(含 workerId)
    • MessageRouter 收到后:
      • 检查该 worker 是否已有 RTCProviderTab,有则复用
      • 没有则选举一个 RTCProviderTab(当前可见页面)
      • 检查 SSE 状态,未就绪则入队等待
      • 发送 RTC_CREATE_REQUEST 到选中的 provider tab
  2. 主线程 RTCManager 收到 RTC_CREATE_REQUEST
    • 创建 RTCPeerConnection 实例
    • 创建默认 DataChannel(control + stream
    • 生成 SDP Offer,通过 Hub 的信令 API 转发给 Worker
    • Hub 通过 SSE 推送回 Worker 的 SDP Answer + ICE candidates
    • WebRTC peerConnection.onconnectionstatechange → "connected"
    • 发射 RTC_CREATE_RESPONSE,通知页面 RTC 就绪
    • commandQueueManager.notifyCanSend() — 命令队列开始发包
  3. Worker 端收到 termSpawnRequest 等指令后:
    • 解析 CBOR 信封
    • 授权验证(MAC 签名校验)
    • 执行对应操作

第四阶段: 页面组件渲染

  1. Browse 页面渲染内容(结构布局):

    text
    ┌──────────────────────────────────────────────────────┐
    │ Header: "Navbar"                                     │
    ├───────────┬──────────────────────────────────────────┤
    │ Left 25%  │          Right 75%                       │
    │ ┌───────┐ │ ┌──────────────────┬───────────────────┐│
    │ │Parent │ │ │  FileList (60%)  │  Preview (40%)    ││
    │ │Folder │ │ │  [breadcrumb]    │  (空/图片/子目录)  ││
    │ │       │ │ │  [搜索]          │                   ││
    │ │ 67%   │ │ │  [文件列表]      │                   ││
    │ │       │ │ └──────────────────┴───────────────────┘│
    │ ├───────┤ │ ┌──────────────────────────────────────┐│
    │ │Pinboard│ │ │  XtermTerminal (33%)                ││
    │ │ 33%   │ │ │  [Logo] [shell选择] [连接按钮]       ││
    │ └───────┘ │ └──────────────────────────────────────┘│
    └───────────┴──────────────────────────────────────────┘
  2. FileListPanelParentFolderPanel 各自调用 useFileService(workerId)

    • useFileServiceuseConnectionManager().createPeerToPeerLink(workerId)
    • 即触发第三阶段的 WebRTC 连接流程
    • 创建成功后,通过 peer.fsRootsRequest() 获取磁盘根路径列表
    • 通过 peer.readdirRequest({path}) 读取目录
  3. 文件请求的数据路径

    text
    useFileService.fsRoots() / readdir(path)
      → peer().fsRootsRequest() / readdirRequest({path})
        → emitPeerCommand()  →  生成 [cmd, seqId, params] 信封
        → port.postMessage(RTC_SEND_DATA_REQUEST)   →  SharedWorker
        → MessageRouter → port.postMessage()  →  RTCProviderTab
        → RTCManager.handlePeerCommand()
          → 1. getCertificate()  →  HTTP POST 到 hub 获取权限证书
          → 2. validate + CBOR 编码
          → 3. commandQueueManager.add() → RTCDataChannel.send(cborEnvelope)
          → Worker 收到 → 执行 → 回复 [cmd, null, result] 通过 DataChannel
        → RTCManager.onControlChannelMessage()
          → handleCommandSuccessResponse
          → emit(RTC_SEND_DATA_RESPONSE) → port.postMessage() → SharedWorker
        → MessageRouter.handleSendRTCDataResult
          → port.postMessage() → 主线程 Peer.handleReceivedEnvelope
            → sendQueue 中对应 Promise 的 resolve(payload)
  4. XtermTerminal 渲染:

    • 此时显示空闲状态service.state() === "idle"
    • 叠加层显示 ASCII-NLAKE Logo + shell 选择下拉框 + "连接" 按钮
    • xterm.js 终端已初始化,但未连接 PTY

第五阶段: 终端连接(用户点击"连接"后)

  1. 用户选择 shell(默认/ bash / zsh / powershell / fish),点击连接

  2. doSpawn()service.spawn(cols, rows, shell, handlers)

    • 注册 peer.onTermOutputpeer.onTermExit 监听器
    • 设置 state → "connecting"
    • 发送 peer.termSpawnRequest({shell, cols, rows})
    • 经过 RTC 链路到达 Worker
    • Worker 创建 PTY session(含 shell injection 脚本注入)
    • 返回 {session_id, control_token}
    • 设置 state → "connected"
  3. 进入 connected 状态后,createEffect 触发:

    text
    发送彩色 prompt 配置命令:
      prompt $E[38;2;122;126;133m[$T]$E[0m ... & cls
    设置 initDone = true
    • 此后 PTY 输出开始写入 xterm
  4. 用户输入term.onDataservice.sendInput(data)peer.termInput(...) → DataChannel → Worker → PTY stdin

  5. PTY 输出 → Worker → DataChannel onTermOutput → 回调 → term.write(data)


第六阶段: 页面完全加载

  1. 所有面板数据就绪:

    • 文件列表填充完毕(loading=false
    • 预览面板为空(等待用户点击文件)
    • 终端面板显示欢迎界面,等待用户点击连接
  2. SharedWorker 心跳持续运行,检测页面健康状态

  3. SSE 长连接保持,用于信令中转和后续 RTC 切换


关键架构要点

技术职责
页面 UISolidJS + SolidStart文件路由、响应式渲染、组件树
通信中枢SharedWorker (MessagePort)跨 tab 消息路由、心跳、SSE 管理
数据通道WebRTC (DataChannel)浏览器 ↔ Worker 全双工通信
信令Server-Sent Events (SSE)WebRTC offer/answer/candidate 中转
权限临时证书 (MAC)每条指令带有时效性签名
序列化CBOR二进制高效编码
服务端Hub + Worker (Rust)信令转发 / PTY 创建 + 文件操作

最复杂的一段路径

FileListPanel 首次请求文件时:Solid Effect → useFileService → Peer → MessagePort → SharedWorker(MessageRouter) → MessagePort → RTCManager → Hub(权限证书) → DataChannel → Worker — 然后原路返回。这条链路涉及 6 个进程间/网络跳转,每条指令都可能经过队列重试和 MAC 签名验证。

文章标题:项目全链路

文章作者:indulgein

文章链接:https://indulgein.top/posts/%E5%89%8D%E5%90%8E%E7%AB%AF%E5%85%A8%E9%93%BE%E8%B7%AF%E6%B5%81%E7%A8%8B[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。