# ADR-002:沿用 POC 的 Tunnel 協定(WebSocket + yamux) ## 狀態 Accepted — 2026-04-21 ## 背景 (Context) visionA Cloud 最核心的技術要素是:**瀏覽器透過雲端反向代理,操作使用者本地電腦上的 Kneron 裝置**。 這要求在「雲端 Proxy」與「使用者電腦的 local agent」之間建立一條: - **由 local agent 主動出站建立**(穿越 NAT / 企業防火牆) - **雙向多工**:同時傳遞 HTTP 控制指令、長輪詢 WebSocket、MJPEG 串流、推論結果串流 - **低延遲**:串流資料需保留 real-time 特性 - **可重連**:網路短暫斷線後自動恢復 POC(edge-ai-platform)已用 **WebSocket + hashicorp/yamux** 實作並驗證可行: - 外層 `gorilla/websocket` 負責「由 client 發起、能穿越企業代理」 - 內層 `yamux` 把單條 WebSocket 升級為多工 stream,每個 HTTP 請求開一條 yamux stream - WebSocket binary frame 之上用 `wsconn` adapter 把 WebSocket 包成 `net.Conn` POC 源碼重點: - `relay/server.go`:收 `/tunnel/connect`、建立 `yamux.Server(netConn)`、按 token 收在 `map[token]*yamux.Session` - `tunnel/client.go`:Dial WebSocket → `yamux.Client()` → 接收 stream → 轉給本機 `127.0.0.1:3721` - `pkg/wsconn/wsconn.go`:`websocket.Conn` → `net.Conn` adapter(binary frame) ## 決策 (Decision) **雛形沿用 POC 的 tunnel 協定不做重大改變**: - 外層:`github.com/gorilla/websocket`(version ≥ 1.5.3) - 多工:`github.com/hashicorp/yamux`(default config) - Adapter:把 POC 的 `wsconn` 搬到 `visionA-backend/internal/wsconn`(或 `pkg/wsconn`,視是否對外暴露) - 端點:`WS /tunnel/connect?token=xxx`(remote-proxy 側) - 訊息語義:沿用 POC 的「yamux stream 裡傳完整 HTTP request/response」 **沿用的好處:代碼已在 POC 驗證過能穿越 NAT、能處理 WebSocket upgrade(`proxyWebSocket` 的 Hijack 邏輯)、能處理 MJPEG streaming。** **雛形會升級的部分(與 ADR-003 相關)**: - Token 生成機制(SHA256(MAC) → Pairing Token,詳見 ADR-003) - `handleTunnel` 加上 token 驗證 hook(雛形驗 env 寫死 token;未來驗 DB) - Session 管理抽 interface(雛形 in-memory,未來 Redis,詳見 ADR-001) **未來可能會重新評估的項目(非雛形範疇)**: - 若 stream 吞吐或 head-of-line blocking 成瓶頸 → 評估 HTTP/3 (QUIC) 或 gRPC bidi stream - 若需要端對端加密(繞過雲端看到明文)→ 在 yamux 之上再包一層 TLS / Noise - 若需要跨雲的地理就近路由 → 加入 proxy routing 層,由 local agent 選擇 entrypoint ## 考慮過的替代方案 | 方案 | 優點 | 缺點 | 排除原因 | |------|------|------|---------| | **重頭設計 gRPC bidi stream** | 現代 RPC,有 code-gen、型別安全 | 要重新定義所有訊息 schema;POC 已用 HTTP semantics 讓搬遷成本低 | 雛形期不值得,未來可升級 | | **HTTP/3 (QUIC) 原生多工** | 未來標準、天生多工、零 HOL blocking | Go 生態的 QUIC 客戶端 / 伺服器還在 beta,反向代理成熟度低 | 還太新 | | **單一 WebSocket 無多工(每請求新開 WS)** | 最簡單 | 每請求一次 WS handshake(~50-200ms),串流無法疊加 | 效能差 | | **SSH tunnel / WireGuard 等 VPN 方案** | 標準 | 需要使用者端安裝額外軟體、開特定埠、難穿越企業防火牆 | 使用者體驗差 | | **純 TCP tunnel over TLS** | 簡單 | 通常被企業防火牆封鎖,WebSocket 因為外觀像 HTTP 能穿透 | 穿透力差 | ## 後果 (Consequences) ### 正面影響 - **零移植成本**:POC relay / tunnel 程式碼可直接搬到 `visionA-backend/internal/relay` 和 `visionA-backend/internal/tunnel` - **已驗證 NAT 穿透**:POC 測試過能穿越公司 NAT、家用路由器 NAT - **已驗證 WebSocket-in-WebSocket**:瀏覽器 → Proxy → tunnel stream → local agent 的 WebSocket upgrade 流程,POC 的 `proxyWebSocket` 已處理 Hijack 並雙向 pipe,這個最難的部分已經能跑 - **已驗證 MJPEG streaming**:POC 的 `handleProxy` 用 `http.Flusher` 處理串流回應,visionA 的 camera stream 可直接用 - **擴展空間大**:yamux 天生多工,一個 tunnel 可以同時跑 10+ 個 HTTP 請求不互相阻塞 ### 負面影響(接受的取捨) - **訊息格式是 HTTP 明文**:yamux stream 內跑的是 raw HTTP request/response,需靠 TLS(WebSocket 外層)保密;Proxy 節點可以看到明文請求內容 - **yamux 依賴**:hashicorp/yamux 已久無大版本更新,但 v0.x 穩定 - **單 WebSocket = 單 tunnel**:一個 local agent 一條 WebSocket;若要跨地理區域 active-active,需應用層路由 - **HOL blocking 風險低但存在**:yamux 是多工,但底層單一 TCP connection,若網路抖動整條都延遲 ### 風險 - **WebSocket 升級失敗率**:企業防火牆 / 中間人代理可能阻擋 WebSocket upgrade。雛形先不處理 fallback(SSE / long-poll),TODO - **單條 WebSocket 頻寬極限**:若使用者端上傳 MJPEG 4K 60fps 會打滿一條 TCP。實測後若有問題再引入「多條 WebSocket + 分流」 - **yamux 的 keepalive**:POC 用 default config,是否足夠穿越企業 idle timeout?TODO 壓測驗證 ## 合規性 - [x] Architect 確認 - [x] POC 實測驗證(`edge-ai-platform` 已部署到 EC2 + 本地測試) - [ ] 未來壓測:TODO ## 相關文件 - Design Doc §3(資料流) - TDD §6(Tunnel 協定) - TDD §7(Session 管理) - POC 源碼:`edge-ai-platform/server/internal/{relay,tunnel}`、`edge-ai-platform/server/pkg/wsconn`