Skip to content

WebSocket 代理 ​

Luker 提供了 WebSocket(WS)代理功能,通过持久的 WebSocket 隧道传输 AI 生成请求,替代传统的 HTTP 请求方式。这在网络不稳定或受限的环境中尤其有用。

什么是 WS 代理 ​

在传统模式下,AI 生成请求各自是独立的 HTTP 请求。如果网络出现波动,请求可能中断,导致生成结果丢失。

WS 代理将这些请求通过一条持久的 WebSocket 连接进行传输。WebSocket 连接一旦建立,就会保持打开状态,所有的生成请求和响应均通过这条连接进行双向通信,无需反复建立新连接。

d2 Diagram

简单来说:

  • 传统 HTTP 模式:每次生成 → 建立连接 → 发送请求 → 接收响应 → 关闭连接
  • WS 代理模式:建立一次连接 → 所有生成请求复用这条连接 → 持续通信

为什么需要 WS 代理 ​

网络不稳定环境 ​

在移动网络、跨国网络或 Wi-Fi 信号较弱的环境中,HTTP 长连接容易因为短暂的网络波动而中断。WS 代理通过心跳保活和自动重连机制,能够更好地应对这些情况。

防火墙和代理限制 ​

某些网络环境中,防火墙或企业代理可能会对长时间的 HTTP 连接进行超时断开。WebSocket 协议在建立连接后的通信方式不同于普通 HTTP,在部分场景下能够绕过这些限制。

流式生成的可靠性 ​

AI 生成通常使用流式传输(SSE),一次生成可能持续数十秒。WS 代理为流式传输提供了更可靠的底层通道。

重连与恢复能力 ​

WS 代理内置了多项健壮性机制:

心跳保活 ​

连接建立后,客户端和服务端会定期交换心跳消息,确保连接处于活跃状态。如果一方长时间未收到心跳,会主动检测连接状态。

断线自动重连 ​

当 WebSocket 连接意外断开时,客户端会自动尝试重新建立连接,无需用户手动干预。

流偏移恢复 ​

如果在 AI 生成过程中连接短暂中断,WS 代理支持流偏移恢复——重连后从断点处继续接收生成内容,而不是从头开始。这意味着即使网络闪断,你也不会丢失已经生成的内容。

内部调度机制 ​

WS 代理由两段构成:升级阶段用一次性 ticket 完成鉴权,派发阶段用 app.handle() 直接调度 Express 路由。这样请求依旧经过应用层中间件(cookie session、CSRF、登录检查),但 Basic Auth 这一层 HTTP 网关只在 ticket 签发时校验一次,WS 通道本身就是认证边界。

d2 Diagram

为什么用 ticket 而不是直接转 Authorization ​

浏览器原生 WebSocket 构造器不允许 JS 设置任何 HTTP header,只能依赖底层环境自动从 Basic Auth 缓存里附带——但 iOS Safari、WKWebView 套壳 app、frpc / cloudflared 之类的反向代理在 WebSocket 升级时常常会剥掉 Authorization 头,即便同源 HTTP 请求是有的。结果就是 WS upgrade 看似成功,后续派发却因为缺 Authorization 在 basicAuth 中间件 401。

Sec-WebSocket-Protocol 是 WS 协议自身的字段,JS 可以从 new WebSocket(url, protocols) 第二参数塞进去,反代和 WebView 均不会动它。所以:ticket 走 HTTP(在那里 Authorization 是可靠的)发放,鉴权信号通过子协议进入 WS 通道。绕开了"WebSocket 不能加 header"这个限制。

工作原理 ​

  1. 拿 ticket:客户端 POST /api/ws-ticket,该端点挂在 setupPrivateEndpoints 里,过完整 HTTP 中间件栈(basicAuth + cookieSession + setUserData + requireLogin + CSRF)。服务端 crypto.randomBytes(32) 生成 64 字符 hex,存进进程内 Map,返回 { ticket }。
  2. 塞子协议:客户端 new WebSocket('/ws/proxy', [luker-ws-ticket.${ticket}])。浏览器把它写进 Sec-WebSocket-Protocol 请求头。
  3. 升级阶段验证:服务端 server.on('upgrade') 从该头提取 ticket,调 consumeTicket(ticket)。校验通过同时删除 entry(单次使用)。失败:写 HTTP/1.1 401,关 socket。
  4. echo 子协议:wss.handleUpgrade 内部调 handleProtocols,把同一个 luker-ws-ticket.<ticket> 字符串选回去,自动写到 101 响应头,握手完成。
  5. 派发请求:从 WS 消息提取 URL/方法/头/体,构造 mock IncomingMessage(Readable socket,req.push() 注入 body)。
  6. 派发标记:在 mock 请求上挂 WS_PROXY_AUTH_BYPASS(模块私有 Symbol,无法通过 header / query / body 伪造)。
  7. app.handle(req, res):进入 Express 中间件链——cookieSession 解析 cookie、CSRF 校验 token、setUserData 注入 request.user、requireLogin 校验登录态均正常运行;basicAuth 中间件读到 Symbol 后直接放行。
  8. 响应回流:mock ServerResponse 把 status/headers/chunk 经 WS 隧道返回给客户端。

安全边界 ​

攻击面现行 HTTP 路径ticket 方案
匿名basicAuth 拦/api/ws-ticket 被 basicAuth 拦,拿不到 ticket
偷 cookie 单独basicAuth 拦(无 Auth 头)/api/ws-ticket 被 basicAuth 拦,拿不到 ticket
偷 basicAuth 单独requireLogin 拦(无 session)/api/ws-ticket 被 requireLogin 拦,拿不到 ticket
两者均偷全套通过全套通过(无新增攻击面)
ticket 中间人窃听N/A30s TTL + 单次使用,HTTPS 路径下基本不可行
ticket 重放N/A单次使用,服务端在 consume 时立即删除
  • ticket 不绑用户身份,只授权"通道接入"。派发请求的用户身份完全由 cookie session 决定(setUserDataMiddleware + requireLoginMiddleware 在派发链路上均会跑)。即便 ticket 被偷,攻击者还是要用自己的 cookie 才能派发,得不到任何越权能力。
  • Symbol 不可伪造:WS_PROXY_AUTH_BYPASS 是模块内部的 Symbol;任何 header、query、body 字段均无法在 request 对象上设置同名 Symbol 属性。
  • 应用层中间件照常生效:cookieSession、CSRF、setUserData、requireLogin 在派发时全部运行,未登录或缺少 CSRF token 的请求依然会被拒绝。

连接健壮性 ​

  • 心跳保活:客户端和服务端定期交换心跳消息,防止中间网络设备因空闲超时断开连接
  • 流偏移恢复:生成过程中如果连接短暂中断,重连后可以从断点继续接收内容
  • 作业清理:使用 lastActivity 时间戳检测过期作业,而非 createdAt,确保活跃中的长生成不会被误清理
  • 重连重新发 ticket:断线重连时客户端会先 POST /api/ws-ticket 拿新 ticket,旧 ticket 已经被消费掉,无法重用。

使用场景 ​

以下场景特别适合使用 WS 代理:

  • 移动设备使用 — 手机网络切换(Wi-Fi ↔ 蜂窝)时保持生成不中断
  • 远程服务器部署 — Luker 部署在远程服务器上,通过不稳定的网络访问
  • 长文本生成 — 生成较长的回复时,减少因超时导致的失败
  • 企业网络环境 — 绕过可能干扰长连接的网络设备

TIP

WS 代理是 Luker 的内部传输优化,对用户来说是透明的——你不需要进行额外配置,Luker 会在适当的时候自动使用。

相关页面 ​

基于 SillyTavern 构建