RFC: WebSocket reverse-connection gateway (替代 Cloudflare Tunnel) #210

Closed
opened 2026-05-12 05:30:22 +00:00 by xingyue · 0 comments
Owner

Summary

用 WebSocket 反向连接替代 Cloudflare Tunnel,本地 serve 主动连接 Gateway,Gateway 通过已有 WS 通道下发请求。

Motivation

当前架构依赖 cloudflared tunnel 做 NAT 穿透:

Dashboard → Gateway (CF Worker) → Tunnel URL → 本地 serve

痛点:

  • 依赖 cloudflared:需要安装、配置命名 tunnel 或临时 tunnel
  • KV TTL heartbeat:agent 需要每分钟 POST 续注册,TTL 5min 过期就 530
  • URL 漂移:临时 tunnel URL 变化 + heartbeat 覆盖命名 tunnel,导致 530
  • 链路长且脆弱:Gateway → Tunnel → cloudflared → localhost,任何一环断都挂

Proposed Architecture

本地 serve ——WebSocket——→ Gateway (CF Worker + Durable Object)
Dashboard ——HTTP——→ Gateway ——通过已有 WS——→ 本地 serve

核心变化

  1. 本地 serve 启动时,主动 WebSocket 连接到 wss://workflow-gateway.shazhou.workers.dev/ws/connect?name=sora&secret=xxx
  2. Gateway 用 Durable Object 维持每个 agent 的 WS 连接状态
  3. Dashboard 请求 /api/agents/sora/workflows 时,Gateway 通过 WS 下发请求,等待 agent 回复
  4. 连接 = 注册,断开 = 离线。不需要 heartbeat、不需要 KV TTL

请求-响应协议

// Gateway → Agent (via WS)
type WsRequest = {
  id: string        // requestId (UUID)
  method: string    // "GET" | "POST" | ...
  path: string      // "/api/workflows"
  headers: Record<string, string>
  body: string | null
}

// Agent → Gateway (via WS)
type WsResponse = {
  id: string        // 对应的 requestId
  status: number
  headers: Record<string, string>
  body: string
}

连接管理

  • Agent 断线自动重连(指数退避)
  • Gateway 用 WebSocket ping/pong 检测连接存活
  • Durable Object per-agent,持有 WS handle + 请求队列
  • Dashboard 查询 endpoints 时,直接看 DO 里谁有活跃连接

Benefits

现在 (Tunnel) 提议 (WS)
外部依赖 cloudflared
NAT 穿透 cloudflared 做 WebSocket 天然支持
注册机制 HTTP heartbeat + KV TTL 连接即注册
在线检测 lastHeartbeat 时间差 WS 连接是否存活
延迟 Gateway → Tunnel → cloudflared → localhost Gateway → WS → localhost
可靠性 多环节,任一断都 530 单连接
流式传输 需要 SSE 穿透 tunnel WS 原生双向流

Implementation Plan

Phase 1: Durable Object + WS 连接

  • Gateway 加 Durable Object class AgentSocket
  • /ws/connect 路由:验证 secret → upgrade to WS → 存储连接
  • agent 端 WS 客户端 + 自动重连

Phase 2: 请求代理

  • /api/agents/:agent/* 改为通过 WS 转发(替代 HTTP fetch)
  • 请求超时处理(30s)
  • /api/gateway/endpoints 改为查询 DO 连接状态

Phase 3: 清理

  • 移除 KV ENDPOINTS binding
  • 移除 startTunnel / startHeartbeat / registerWithGateway
  • --tunnel-url / --no-tunnel 参数废弃
  • cloudflared 不再是 serve 的依赖

Open Questions

  1. Durable Object 计费:DO 按 wall-clock duration 计费,长连接的成本如何?需要评估
  2. 多实例:同一个 agent name 多个 serve 实例连接时的行为?(后连覆盖前连 vs 拒绝)
  3. 流式 thread 输出:是否在 Phase 2 就支持 WS 流式推送 thread 进度到 dashboard?
  4. fallback:是否保留 HTTP 直连作为 fallback(内网场景)?

References

## Summary 用 WebSocket 反向连接替代 Cloudflare Tunnel,本地 serve 主动连接 Gateway,Gateway 通过已有 WS 通道下发请求。 ## Motivation 当前架构依赖 cloudflared tunnel 做 NAT 穿透: ``` Dashboard → Gateway (CF Worker) → Tunnel URL → 本地 serve ``` 痛点: - **依赖 cloudflared**:需要安装、配置命名 tunnel 或临时 tunnel - **KV TTL heartbeat**:agent 需要每分钟 POST 续注册,TTL 5min 过期就 530 - **URL 漂移**:临时 tunnel URL 变化 + heartbeat 覆盖命名 tunnel,导致 530 - **链路长且脆弱**:Gateway → Tunnel → cloudflared → localhost,任何一环断都挂 ## Proposed Architecture ``` 本地 serve ——WebSocket——→ Gateway (CF Worker + Durable Object) Dashboard ——HTTP——→ Gateway ——通过已有 WS——→ 本地 serve ``` ### 核心变化 1. **本地 serve 启动时**,主动 WebSocket 连接到 `wss://workflow-gateway.shazhou.workers.dev/ws/connect?name=sora&secret=xxx` 2. **Gateway 用 Durable Object** 维持每个 agent 的 WS 连接状态 3. **Dashboard 请求** `/api/agents/sora/workflows` 时,Gateway 通过 WS 下发请求,等待 agent 回复 4. **连接 = 注册**,断开 = 离线。不需要 heartbeat、不需要 KV TTL ### 请求-响应协议 ```typescript // Gateway → Agent (via WS) type WsRequest = { id: string // requestId (UUID) method: string // "GET" | "POST" | ... path: string // "/api/workflows" headers: Record<string, string> body: string | null } // Agent → Gateway (via WS) type WsResponse = { id: string // 对应的 requestId status: number headers: Record<string, string> body: string } ``` ### 连接管理 - Agent 断线自动重连(指数退避) - Gateway 用 WebSocket ping/pong 检测连接存活 - Durable Object per-agent,持有 WS handle + 请求队列 - Dashboard 查询 endpoints 时,直接看 DO 里谁有活跃连接 ## Benefits | | 现在 (Tunnel) | 提议 (WS) | |---|---|---| | 外部依赖 | cloudflared | 无 | | NAT 穿透 | cloudflared 做 | WebSocket 天然支持 | | 注册机制 | HTTP heartbeat + KV TTL | 连接即注册 | | 在线检测 | lastHeartbeat 时间差 | WS 连接是否存活 | | 延迟 | Gateway → Tunnel → cloudflared → localhost | Gateway → WS → localhost | | 可靠性 | 多环节,任一断都 530 | 单连接 | | 流式传输 | 需要 SSE 穿透 tunnel | WS 原生双向流 | ## Implementation Plan ### Phase 1: Durable Object + WS 连接 - Gateway 加 Durable Object class `AgentSocket` - `/ws/connect` 路由:验证 secret → upgrade to WS → 存储连接 - agent 端 WS 客户端 + 自动重连 ### Phase 2: 请求代理 - `/api/agents/:agent/*` 改为通过 WS 转发(替代 HTTP fetch) - 请求超时处理(30s) - `/api/gateway/endpoints` 改为查询 DO 连接状态 ### Phase 3: 清理 - 移除 KV `ENDPOINTS` binding - 移除 `startTunnel` / `startHeartbeat` / `registerWithGateway` - `--tunnel-url` / `--no-tunnel` 参数废弃 - cloudflared 不再是 serve 的依赖 ## Open Questions 1. **Durable Object 计费**:DO 按 wall-clock duration 计费,长连接的成本如何?需要评估 2. **多实例**:同一个 agent name 多个 serve 实例连接时的行为?(后连覆盖前连 vs 拒绝) 3. **流式 thread 输出**:是否在 Phase 2 就支持 WS 流式推送 thread 进度到 dashboard? 4. **fallback**:是否保留 HTTP 直连作为 fallback(内网场景)? ## References - Cloudflare Durable Objects + WebSocket: https://developers.cloudflare.com/durable-objects/api/websockets/ - 当前 Gateway 实现: `packages/workflow-gateway/src/index.ts` - 当前 Tunnel 实现: `packages/cli-workflow/src/commands/serve/tunnel.ts` - Dashboard 530 问题: tunnel URL 漂移 + KV TTL 过期
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#210