WebSocket 支持:upgradeWebSocket(c) 在 Workers 中的限制
Cloudflare Workers 无原生 WebSocket server,需使用 Durable Objects
Hono 提供了 upgradeWebSocket 工具,让你可以方便地在支持 WebSocket 的环境中(如 Node.js、Deno)处理 WebSocket 连接。然而,当你将 Hono 部署到 Cloudflare Workers 时,会遇到一个关键限制:Cloudflare Workers 本身不支持作为 WebSocket 服务器。
这并不意味着你无法在 Cloudflare 上使用 WebSocket —— 而是必须通过 Durable Objects 来实现。
一、问题根源:Cloudflare Workers 的架构限制
| 环境 | 是否支持原生 WebSocket Server |
|---|---|
| Node.js | 支持 (ws 或 uWebSockets.js) |
| Deno | 支持 (Deno.upgradeWebSocket) |
| Cloudflare Workers (常规) | 不支持 |
| Cloudflare Workers + Durable Objects | 支持 |
核心限制:
Cloudflare Workers 是无状态、短暂运行的函数,而 WebSocket 是长连接、有状态的协议。Workers 的执行模型无法维持长时间的 TCP 连接。
二、解决方案:Durable Objects (DO)
Durable Objects (DO) 是 Cloudflare 提供的有状态、持久化对象,每个 DO 实例可以:
- 维持一个或多个 WebSocket 连接;
- 存储状态(内存中);
- 跨请求共享数据;
- 实现聊天室、实时协作、游戏等场景。
Durable Objects 是 Cloudflare 上运行 WebSocket Server 的唯一方式。
三、Hono + Durable Objects 实现 WebSocket
虽然 Hono 的 upgradeWebSocket(c) 不能直接用于 Workers 的常规路由,但你可以:
- 在 Durable Object Class 中手动处理 WebSocket 升级;
- 使用 Hono 作为 HTTP 路由层,将 WebSocket 请求引导至 DO。
1. 定义 Durable Object Class
// durable-object.ts
export class WebSocketRoom {
private connections: WebSocket[] = []
private state: DurableObjectState
constructor(state: DurableObjectState, env: Env) {
this.state = state
}
async fetch(request: Request) {
const upgradeHeader = request.headers.get('Upgrade')
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Expected WebSocket', { status: 426 })
}
// 创建 WebSocket 对象
const [client, server] = Object.values(new WebSocketPair())
// 在 DO 端处理连接
await this.handleWebSocket(server)
// 返回客户端连接
return new Response(null, {
status: 101,
webSocket: client,
})
}
private async handleWebSocket(webSocket: WebSocket) {
webSocket.accept()
// 加入广播列表
this.connections.push(webSocket)
// 监听消息
webSocket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
// 广播给所有连接
this.broadcast({
type: 'message',
data: data.content,
from: data.user,
time: new Date().toISOString()
})
})
// 连接关闭
webSocket.addEventListener('close', () => {
this.connections = this.connections.filter(ws => ws !== webSocket)
})
}
private broadcast(message: any) {
const payload = JSON.stringify(message)
this.connections.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(payload)
}
})
}
}2. 在 Hono 中创建连接入口(HTTP 路由)
// routes/chat.ts
import { Hono } from 'hono'
const app = new Hono()
// 前端通过此接口获取 WebSocket 连接
app.get('/chat/connect', async (c) => {
const { env } = c
const roomId = 'global' // 可动态生成
// 获取 Durable Object Stub
const id = env.WS_ROOM.idFromName(roomId)
const obj = env.WS_ROOM.get(id)
// 转发请求到 Durable Object
const resp = await obj.fetch(c.req.url, c.req.raw.clone())
// 返回 WebSocket 升级响应
return resp
})
export default app3. wrangler.toml 配置
name = "hono-ws-bff"
main = "src/index.ts"
compatibility_date = "2025-03-01"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
migrations_dir = "./migrations"
# 定义 Durable Object
[[durable_objects.bindings]]
name = "WS_ROOM"
class_name = "WebSocketRoom"
# 生成 Durable Object 脚本
[[migrations]]
tag = "v1"
new_classes = ["WebSocketRoom"]4. 前端连接 WebSocket
// composables/useChat.ts
export function useChat() {
const messages = ref<any[]>([])
const ws = ref<WebSocket | null>(null)
const connect = async () => {
// 1. 请求连接入口
const resp = await fetch('/chat/connect')
// 2. 提取 WebSocket(浏览器自动升级)
const socket = resp.webSocket
if (!socket) throw new Error('WebSocket upgrade failed')
socket.accept()
ws.value = socket
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
messages.value.push(data)
})
socket.addEventListener('close', () => {
console.log('WebSocket closed')
})
}
const send = (content: string, user: string) => {
if (ws.value?.readyState === WebSocket.OPEN) {
ws.value.send(JSON.stringify({ user, content }))
}
}
return { messages, connect, send }
}四、Hono 的 upgradeWebSocket(c) 在哪里可用?
import { upgradeWebSocket } from 'hono/ws'
// 仅在 Node.js / Deno 环境中可用
app.get('/ws', (c) => {
return upgradeWebSocket(c, (ws) => {
ws.on('message', (event) => {
ws.send(`Echo: ${event.data}`)
})
})
})在 Cloudflare Workers 中使用此代码会报错,因为 WebSocket 构造函数不可用。
五、替代方案对比
| 方案 | 是否支持 | 说明 |
|---|---|---|
upgradeWebSocket(c) | ❌ Workers | 仅 Node.js/Deno |
| Durable Objects | ✅ | 官方推荐,有状态 |
| WebTransport | ✅ (实验性) | UDP 基础,低延迟 |
| Polling / SSE | ✅ | 简单场景可用,但非全双工 |
| 第三方服务 (Pusher, Socket.IO) | ✅ | 增加依赖和成本 |
六、最佳实践
状态分离:
- Hono 处理 REST API;
- Durable Objects 处理 WebSocket 状态。
连接管理:
- 使用
Map<roomId, Stub>管理多个房间; - 设置空闲超时自动销毁 DO 实例。
- 使用
安全:
- 验证 JWT 或 Session;
- 限制每用户连接数。
监控:
- 记录连接数、消息量;
- 使用 Cloudflare Analytics。
七、总结
| 问题 | 解决方案 |
|---|---|
| Workers 不能做 WebSocket Server | 使用 Durable Objects |
Hono upgradeWebSocket 不可用 | 手动在 DO 中处理 WebSocketPair |
| 需要全局状态 | DO 天然支持有状态实例 |
关键结论:
在 Cloudflare Workers 上实现 WebSocket,必须使用 Durable Objects。Hono 可作为 HTTP 入口层,将请求转发至 DO,实现前后端分离的实时通信架构。
通过这种模式,你可以在边缘网络中构建高性能、低延迟的实时应用,如聊天室、协作编辑、实时仪表盘等。