Gateway 核心:启动、WebSocket 协议、方法处理与认证
前言
在上一篇中,我们梳理了 Clawdbot 的配置系统:路径、JSON5、$include、环境变量、Zod 校验与运行时覆盖。配置是 Gateway 运行的基础——端口、绑定、认证、Control UI、HTTP 端点等均来自配置。本文是《Clawdbot 源码解读》系列的第四篇,我们将深入 Gateway 核心:从 CLI gateway run 到 startGatewayServer 的启动流程、运行时配置与状态、WebSocket 协议(连接、req/res、event)、方法注册与请求路由、以及认证与安全(Token/Password/Tailscale/设备配对)。
学习目标
- 理解 Gateway 从 CLI 到 HTTP/WebSocket 服务器的完整启动顺序
- 掌握运行时配置(bind、auth、Control UI、TLS)与运行时状态(HTTP 服务器、WS、broadcast)的构建
- 熟悉 WebSocket 协议:connect.challenge → connect → hello-ok、请求/响应帧、事件广播
- 了解方法注册、权限(role/scopes)与认证(token/password/tailscale/device)的协作方式
前置知识
- 已阅读系列前三篇(架构、CLI、配置)
- 对 HTTP/WebSocket、Node.js 服务器有基本概念
- 对 TypeBox/AJV 或类似 schema 校验有印象即可
一、核心概念
1.1 Gateway 的职责
Gateway 是 Clawdbot 的 控制平面:同一主机上只跑一个 Gateway 进程,负责:
- WebSocket 服务:客户端(CLI、macOS 应用、Web UI、iOS/Android 节点)通过 WebSocket 连接,发送 请求帧(req)并接收 响应帧(res)和 事件(event)。
- HTTP 服务:在同一端口上提供 Control UI(可选)、Canvas Host、Hooks、插件 HTTP、以及可选的 OpenAI 兼容
/v1/chat/completions和 OpenResponses/v1/responses。 - 渠道与 Agent:管理消息渠道(WhatsApp、Telegram 等)的启停、将消息路由到 Agent、广播心跳与状态变更。
- 认证与安全:连接时校验 Token/Password 或 Tailscale/设备配对,方法级权限由 role(operator/node)与 scopes(operator.read/write/admin 等)控制。
1.2 启动顺序概览
- CLI:
clawdbot gateway run解析端口、bind、auth、tailscale 等,可选--force释放端口,然后调用runGatewayLoop(start),其中start返回startGatewayServer(port, opts)。 - 配置与迁移:
readConfigFileSnapshot()读配置快照;若有 legacy 问题则migrateLegacyConfig并写回;若配置无效则抛错退出。 - 运行时配置:
resolveGatewayRuntimeConfig得到 bindHost、resolvedAuth、controlUi、openAi/openResponses、tailscale、hooks、canvasHost 等;认证未配置且非 loopback 绑定会拒绝启动。 - 运行时状态:
createGatewayRuntimeState创建 HTTP 服务器(含 Control UI、Canvas、Hooks、插件路由)、WebSocket 服务器、clients 集合、broadcast、chat/agent 状态等,并执行 listen。 - 渠道与侧车:创建 ChannelManager、Discovery(mDNS)、Cron、Maintenance 定时器、Agent/Heartbeat 事件订阅;attachGatewayWsHandlers 注册 WebSocket 连接与消息处理;最后 startGatewaySidecars(Browser 控制、Gmail watcher、内部 Hooks、插件服务等)。
- 关闭:返回
{ close };调用close()时依次停止 Discovery、Tailscale、渠道、插件、Cron、定时器、取消订阅、关闭 HTTP/WS。
1.3 WebSocket 协议要点
- 连接建立:服务端先发
event: connect.challenge(带 nonce);客户端发connect请求(params 含 client 信息、auth.token/auth.password 或 device 配对信息);服务端校验认证后回复hello-ok,之后才接受其他请求。 - 请求/响应:客户端发 req(
id、method、params),服务端回复 res(id、ok、result或error);方法由 coreGatewayHandlers 与插件/execApproval 的 extraHandlers 提供,先做 authorizeGatewayMethod(role/scopes),再调用对应 handler。 - 事件:服务端通过 broadcast(event, payload) 向所有已连接客户端推送事件(如
heartbeat、voicewake.changed、shutdown);部分事件带stateVersion(presence/health)用于客户端增量更新。
1.4 认证模式
- Token:
gateway.auth.mode === "token"(或未设且仅有 token),客户端在connect.params.auth.token中携带,与配置/环境变量中的 token 做 timingSafeEqual 比较。 - Password:
gateway.auth.mode === "password",客户端在connect.params.auth.password中携带;Tailscale Funnel 要求必须使用 password。 - Tailscale:当
gateway.tailscale.mode === "serve"且allowTailscale === true时,来自 Tailscale 代理的请求可凭 Tailscale 请求头 + whois 校验 通过,无需 token/password。 - 设备配对:客户端在
connect.params.device中携带设备 id、公钥、签名等,服务端校验签名与 nonce 后完成设备配对或复用已有 token。
二、代码解析
2.1 CLI 到 startGatewayServer
src/cli/gateway-cli/run.ts 中 runGatewayCommand:解析 --port、--bind、--token、--password、--auth、--tailscale、--force、--dev 等;若 --force 则调用 forceFreePortAndWait 释放目标端口;将 token/password 写入 process.env(CLAWDBOT_GATEWAY_TOKEN / CLAWDBOT_GATEWAY_PASSWORD);然后 runGatewayLoop({ start: async () => startGatewayServer(port, { bind, auth, tailscale }) })。循环内会调用 start() 得到 server,并监听 SIGINT/SIGTERM 后调用 server.close()。
// src/cli/gateway-cli/run.ts(节选)
await runGatewayLoop({
runtime: defaultRuntime,
start: async () =>
await startGatewayServer(port, {
bind,
auth: authMode || passwordRaw || tokenRaw ? { mode: authMode ?? undefined, token: tokenRaw, password: passwordRaw } : undefined,
tailscale: tailscaleMode ? { mode: tailscaleMode, resetOnExit: Boolean(opts.tailscaleResetOnExit) } : undefined,
}),
});
2.2 startGatewayServer:配置、运行时配置与状态
src/gateway/server.impl.ts 中 startGatewayServer(port, opts) 的主干:
- 配置快照与迁移:
readConfigFileSnapshot();若存在 legacyIssues 则migrateLegacyConfig、writeConfigFile,再重新读快照;若 exists && !valid 则抛错(提示运行clawdbot doctor)。 - 插件自动启用:
applyPluginAutoEnable,若有变更则写回配置。 - 加载配置:
loadConfig()得到 cfgAtStart;初始化 subagent、默认 agent、workspace;loadGatewayPlugins 得到 pluginRegistry 与 gatewayMethods(核心 + 渠道插件);resolveGatewayRuntimeConfig 得到 runtimeConfig(bindHost、resolvedAuth、controlUi、openAi/openResponses、tailscale、hooks、canvasHost)。 - TLS:
loadGatewayTlsRuntime(cfg.gateway?.tls);若配置启用 TLS 但加载失败则抛错。 - 运行时状态:createGatewayRuntimeState 创建 Canvas Host(若启用)、HTTP 处理器(Control UI、Hooks、插件)、createGatewayHttpServer 与 attachGatewayUpgradeHandler(WebSocket 升级)、listenGatewayHttpServer 在 bindHosts 上监听;返回 httpServer、wss、clients、broadcast、chatRunState、agentRunSeq、dedupe 等。
- 渠道与发现:createChannelManager、startGatewayDiscovery(mDNS/Bonjour)、NodeRegistry、server-node-subscriptions;startGatewayMaintenanceTimers(tick、health、dedupe 清理);onAgentEvent / onHeartbeatEvent 订阅并转发到 broadcast。
- WebSocket 处理:attachGatewayWsHandlers 将 attachGatewayWsConnectionHandler 挂到 wss 上,传入 gatewayMethods、events、extraHandlers(插件 + execApproval)、broadcast、context。
- 侧车:startGatewaySidecars 启动 Browser 控制服务、Gmail watcher、内部 Hooks、startPluginServices(插件服务);startGatewayTailscaleExposure 若配置了 tailscale serve/funnel。
- 热重载与关闭:createGatewayReloadHandlers、startGatewayConfigReloader(监听配置文件变更);返回 close,由 createGatewayCloseHandler 统一清理。
2.3 运行时配置:bind、auth、Control UI
src/gateway/server-runtime-config.ts 中 resolveGatewayRuntimeConfig:
- bindHost:由 resolveGatewayBindHost(bindMode, customBindHost) 得到;
bind来自 opts 或cfg.gateway?.bind,默认"loopback"(127.0.0.1);可选lan(0.0.0.0)、tailnet、auto、custom。 - controlUiEnabled:opts 或
cfg.gateway?.controlUi?.enabled,默认 true。 - openAiChatCompletionsEnabled / openResponsesEnabled:来自配置的
gateway.http.endpoints。 - resolvedAuth:resolveGatewayAuth(config + env),得到 mode(token/password)、token、password、allowTailscale;assertGatewayAuthConfigured 在未配置且非仅 Tailscale 时抛错。
- tailscaleMode:
off|serve|funnel;funnel 时强制要求 password;非 loopback 绑定时不能开 tailscale。 - 非 loopback 且无共享密钥时拒绝启动:避免裸奔监听公网。
2.4 运行时状态:HTTP、WS、broadcast
src/gateway/server-runtime-state.ts 中 createGatewayRuntimeState:
- 若 canvasHostEnabled,则 createCanvasHostHandler 并挂到 HTTP。
- createGatewayHooksRequestHandler、createGatewayPluginRequestHandler 供 HTTP 路由使用。
- resolveGatewayListenHosts(bindHost) 得到要监听的地址列表(如 IPv4/IPv6);对每个地址 createGatewayHttpServer(Control UI、Canvas、openAi/openResponses、Hooks、插件、认证)、attachGatewayUpgradeHandler(将 WebSocket 升级交给 wss)。
- createGatewayBroadcaster:向 clients 中所有 WebSocket 发送
{ type: "event", event, payload },支持 dropIfSlow、stateVersion。 - 返回 httpServer(第一个)、httpServers、wss、clients、broadcast、chatRunState、agentRunSeq、dedupe 等,供 attachGatewayWsHandlers 和后续逻辑使用。
2.5 WebSocket 连接与协议
src/gateway/server/ws-connection.ts:wss.on("connection", (socket, upgradeReq)) 时:
- 生成 connId,发送 connect.challenge(nonce、ts)。
- 设置 attachGatewayWsMessageHandler 处理后续消息;握手超时内未完成 connect 则关闭连接。
- 消息处理(message-handler.ts):解析 JSON;若为 connect 请求则校验 validateConnectParams、authorizeGatewayConnect(token/password/tailscale/device),通过则构建 client(id、role、scopes 等)加入 clients,回复 hello-ok,并发送快照(health、presence 等);若为 req 则 validateRequestFrame、handleGatewayRequest,回复 res(ok + result 或 error)。
- RequestFrame:
id、method、params;Response:id、ok、result或error(含 code、message);协议 schema 在 protocol/schema/ 下用 TypeBox 定义,校验通过后才交给 handler。
2.6 方法注册与路由
src/gateway/server-methods.ts:
- coreGatewayHandlers:由 connectHandlers、healthHandlers、channelsHandlers、chatHandlers、agentHandlers、sessionsHandlers、nodeHandlers、configHandlers、wizardHandlers、updateHandlers 等数十个模块合并而成,每个导出形如
{ "method.name": async (opts) => { ... } }。 - handleGatewayRequest:先 authorizeGatewayMethod(req.method, client)(根据 client 的 role、scopes 判断是否允许调用该方法);再取 handler = extraHandlers[method] ?? coreGatewayHandlers[method];无 handler 则返回
unknown method;有则 await handler({ req, params, client, respond, context })。 - authorizeGatewayMethod:node 角色只能调用 NODE_ROLE_METHODS(node.invoke.result、node.event、skills.bins);operator 需具备相应 scopes(operator.read、operator.write、operator.admin、operator.approvals、operator.pairing),否则返回 INVALID_REQUEST。
2.7 认证:resolveGatewayAuth 与 authorizeGatewayConnect
src/gateway/auth.ts:
- resolveGatewayAuth:从 authConfig 与 env 读取 token(gateway.auth.token 或 CLAWDBOT_GATEWAY_TOKEN)、password(gateway.auth.password 或 CLAWDBOT_GATEWAY_PASSWORD);mode 为 config 或推断(有 password 则 password);allowTailscale 由 config 或(tailscale serve 且 mode !== password)决定。
- authorizeGatewayConnect:先判断是否 isLocalDirectRequest(本机直连);若非本地且 allowTailscale,则用 resolveVerifiedTailscaleUser(请求头 + whois 校验);否则若 mode === "token" 则比较 connect.params.auth.token 与 auth.token(timingSafeEqual);若 mode === "password" 则比较 password;若客户端带 device 则走设备配对校验。返回 GatewayAuthResult(ok、method、user、reason)。
三、架构图解
3.1 Gateway 启动流程
graph TB
A[clawdbot gateway run] --> B[runGatewayCommand]
B --> C[forceFreePort?]
C --> D[runGatewayLoop]
D --> E[startGatewayServer]
E --> F[readConfigFileSnapshot + migrateLegacy]
F --> G[resolveGatewayRuntimeConfig]
G --> H[createGatewayRuntimeState]
H --> I[HTTP + WS listen]
I --> J[ChannelManager / Discovery / Cron / Maintenance]
J --> K[attachGatewayWsHandlers]
K --> L[startGatewaySidecars]
L --> M[return close]
M --> N[loop: wait for signal → server.close]
3.2 WebSocket 协议流程
sequenceDiagram
participant C as Client
participant G as Gateway
C->>G: WS connect
G->>C: event: connect.challenge (nonce)
C->>G: req: connect (params: client, auth/device)
G->>G: authorizeGatewayConnect
alt 认证通过
G->>C: res: hello-ok + snapshot
C->>G: req: health / agent / ...
G->>G: authorizeGatewayMethod → handler
G->>C: res: result / error
G->>C: event: heartbeat / ...
else 认证失败
G->>C: res: error → close
end
3.3 方法路由与权限
graph LR
A[req] --> B[validateRequestFrame]
B --> C[authorizeGatewayMethod]
C --> D{role/scopes}
D -->|通过| E[extraHandlers / coreGatewayHandlers]
D -->|拒绝| F[res error]
E --> G[handler]
G --> H[respond res]
四、实践建议
4.1 如何调试 Gateway 启动
- 端口被占用:使用
clawdbot gateway run --force释放端口,或修改--port;日志会提示 GatewayLockError 或端口诊断(inspectPortUsage)。 - 认证未配置:非 loopback 绑定时必须配置 gateway.auth.token 或 gateway.auth.password(或环境变量);否则 assertGatewayAuthConfigured 或 resolveGatewayRuntimeConfig 会抛错。
- 配置无效:若 readConfigFileSnapshot().valid === false,启动会抛错并列出 issues;可先运行
clawdbot doctor修复。 - 详细日志:
clawdbot gateway run --verbose;WebSocket 日志可通过--ws-log full或--compact控制。
4.2 如何扩展 Gateway 方法
- 在 server-methods/ 下新增模块(如 server-methods/foo.ts),导出 fooHandlers(
{ "foo.bar": async (opts) => { ... } })。 - 在 server-methods.ts 中 import 并合并到 coreGatewayHandlers(或通过插件 extraHandlers 注册)。
- 在 server-methods-list.ts 的 listGatewayMethods 中加入新 method 字符串,以便客户端与 hello-ok 中的方法列表一致。
- 若需权限控制,在 authorizeGatewayMethod 中为该方法挂到 READ_METHODS / WRITE_METHODS / ADMIN 等,或单独判断 scopes。
- 协议参数/结果若需严格校验,在 protocol/schema/ 下用 TypeBox 定义 Schema,并在 protocol/index.ts 中导出 validateXxx,在 handler 内调用。
4.3 认证与安全建议
- 生产环境:使用 password 或 token,并避免绑定到 0.0.0.0 却不配置认证。
- Tailscale:内网暴露用 serve,公网用 funnel(必须 password);allowTailscale 时 Tailscale 用户可免 token/password,需确保 Tailscale 网络可信。
- 设备配对:移动端/节点通过 connect.params.device 完成配对后,可获长期有效 token,便于无交互重连。
- 调试:本机直连(127.0.0.1)时部分路径会放宽;远程调试时务必设好 token/password 或 Tailscale。
4.4 常见问题
Q: 连接后立即被关闭?
A: 检查是否在握手超时内完成 connect 并携带正确 auth.token 或 auth.password(或 device);查看服务端 --ws-log full 下的关闭原因(如 token_mismatch、handshake_timeout)。
Q: 调用某方法返回 unknown method?
A: 确认方法名已加入 listGatewayMethods 且 coreGatewayHandlers 或 extraHandlers 中有对应 key;客户端与服务端协议版本一致。
Q: 返回 missing scope: operator.xxx?
A: 连接时 connect.params 中的 role、scopes 需满足 authorizeGatewayMethod 的要求;例如写操作需 operator.write,管理类需 operator.admin。
五、总结与下一篇预告
5.1 本文要点
- 启动链:CLI gateway run → runGatewayLoop → startGatewayServer;配置快照与迁移 → resolveGatewayRuntimeConfig(bind、auth、Control UI、tailscale)→ createGatewayRuntimeState(HTTP + WS + broadcast)→ attachGatewayWsHandlers → startGatewaySidecars → 返回 close。
- WebSocket 协议:connect.challenge → 客户端 connect(client、auth/device)→ 服务端 authorizeGatewayConnect → hello-ok;之后 req/res 与 event 广播;帧结构由 TypeBox schema 校验。
- 方法处理:handleGatewayRequest 先 authorizeGatewayMethod(role/scopes),再 coreGatewayHandlers / extraHandlers 执行;node 仅限 NODE_ROLE_METHODS,operator 需对应 scopes。
- 认证:resolveGatewayAuth 得到 token/password/allowTailscale;authorizeGatewayConnect 支持 token、password、Tailscale whois、设备配对;非 loopback 绑定必须配置共享密钥。
参考资源
- 项目仓库:github.com/clawdbot/cl…
- 官方文档:docs.clawd.bot
- Gateway 启动与关闭:
src/gateway/server.impl.ts、src/gateway/server-close.ts - CLI 启动命令:
src/cli/gateway-cli/run.ts、src/cli/gateway-cli/run-loop.ts - 运行时配置与状态:
src/gateway/server-runtime-config.ts、src/gateway/server-runtime-state.ts - WebSocket 连接与消息:
src/gateway/server/ws-connection.ts、src/gateway/server/ws-connection/message-handler.ts、src/gateway/server-ws-runtime.ts - 方法注册与路由:
src/gateway/server-methods.ts、src/gateway/server-methods-list.ts、src/gateway/server-methods/ - 认证:
src/gateway/auth.ts、src/gateway/device-auth.ts - 协议 Schema:
src/gateway/protocol/、src/gateway/protocol/schema/ - 侧车与发现:
src/gateway/server-startup.ts、src/gateway/server-discovery-runtime.ts