此示例与构建 WebSocket 服务器示例类似,但使用 WebSocket 休眠 API。对于基于 Durable Objects 构建的 WebSocket 服务器应用,应优先使用 WebSocket 休眠 API,因为它显著降低 duration 费用,并提供与 WebSocket 应用配合良好的额外功能。更多信息,请参阅将 Durable Objects 与 WebSocket 一起使用。
import { DurableObject } from "cloudflare:workers";
// Worker
export default {
async fetch(request, env, ctx) {
if (request.url.endsWith("/websocket")) {
// Expect to receive a WebSocket Upgrade request.
// If there is one, accept the request and return a WebSocket Response.
const upgradeHeader = request.headers.get("Upgrade");
if (!upgradeHeader || upgradeHeader !== "websocket") {
return new Response("Worker expected Upgrade: websocket", {
status: 426,
});
}
if (request.method !== "GET") {
return new Response("Worker expected GET method", {
status: 400,
});
}
// Since we are hard coding the Durable Object ID by providing the constant name 'foo',
// all requests to this Worker will be sent to the same Durable Object instance.
let stub = env.WEBSOCKET_HIBERNATION_SERVER.getByName("foo");
return stub.fetch(request);
}
return new Response(
`Supported endpoints:
/websocket: Expects a WebSocket upgrade request`,
{
status: 200,
headers: {
"Content-Type": "text/plain",
},
},
);
},
};
// Durable Object
export class WebSocketHibernationServer extends DurableObject {
// Keeps track of all WebSocket connections
// When the DO hibernates, gets reconstructed in the constructor
sessions;
constructor(ctx, env) {
super(ctx, env);
this.sessions = new Map();
// As part of constructing the Durable Object,
// we wake up any hibernating WebSockets and
// place them back in the `sessions` map.
// Get all WebSocket connections from the DO
this.ctx.getWebSockets().forEach((ws) => {
let attachment = ws.deserializeAttachment();
if (attachment) {
// If we previously attached state to our WebSocket,
// let's add it to `sessions` map to restore the state of the connection.
this.sessions.set(ws, { ...attachment });
}
});
// Sets an application level auto response that does not wake hibernated WebSockets.
this.ctx.setWebSocketAutoResponse(
new WebSocketRequestResponsePair("ping", "pong"),
);
}
async fetch(request) {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
// request within the Durable Object. It has the effect of "accepting" the connection,
// and allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
// the connection is open. During periods of inactivity, the Durable Object can be evicted
// from memory, but the WebSocket connection will remain open. If at some later point the
// WebSocket receives a message, the runtime will recreate the Durable Object
// (run the `constructor`) and deliver the message to the appropriate handler.
this.ctx.acceptWebSocket(server);
// Generate a random UUID for the session.
const id = crypto.randomUUID();
// Attach the session ID to the WebSocket connection and serialize it.
// This is necessary to restore the state of the connection when the Durable Object wakes up.
server.serializeAttachment({ id });
// Add the WebSocket connection to the map of active sessions.
this.sessions.set(server, { id });
return new Response(null, {
status: 101,
webSocket: client,
});
}
async webSocketMessage(ws, message) {
// Get the session associated with the WebSocket connection.
const session = this.sessions.get(ws);
// Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
// and the total number of connections with the "[Durable Object]: " prefix
ws.send(
`[Durable Object] message: ${message}, from: ${session.id}, to: the initiating client. Total connections: ${this.sessions.size}`,
);
// Send a message to all WebSocket connections, loop over all the connected WebSockets.
this.sessions.forEach((attachment, connectedWs) => {
connectedWs.send(
`[Durable Object] message: ${message}, from: ${session.id}, to: all clients. Total connections: ${this.sessions.size}`,
);
});
// Send a message to all WebSocket connections except the connection (ws),
// loop over all the connected WebSockets and filter out the connection (ws).
this.sessions.forEach((attachment, connectedWs) => {
if (connectedWs !== ws) {
connectedWs.send(
`[Durable Object] message: ${message}, from: ${session.id}, to: all clients except the initiating client. Total connections: ${this.sessions.size}`,
);
}
});
}
async webSocketClose(ws, code, reason, wasClean) {
// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
// auto-replies to Close frames. Calling close() is safe but no longer required.
ws.close(code, reason);
this.sessions.delete(ws);
}
}import { DurableObject } from 'cloudflare:workers';
// Worker
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
if (request.url.endsWith('/websocket')) {
// Expect to receive a WebSocket Upgrade request.
// If there is one, accept the request and return a WebSocket Response.
const upgradeHeader = request.headers.get('Upgrade');
if (!upgradeHeader || upgradeHeader !== 'websocket') {
return new Response('Worker expected Upgrade: websocket', {
status: 426,
});
}
if (request.method !== 'GET') {
return new Response('Worker expected GET method', {
status: 400,
});
}
// Since we are hard coding the Durable Object ID by providing the constant name 'foo',
// all requests to this Worker will be sent to the same Durable Object instance.
let stub = env.WEBSOCKET_HIBERNATION_SERVER.getByName("foo");
return stub.fetch(request);
}
return new Response(
`Supported endpoints:
/websocket: Expects a WebSocket upgrade request`,
{
status: 200,
headers: {
'Content-Type': 'text/plain',
},
}
);
},
};
// Durable Object
export class WebSocketHibernationServer extends DurableObject {
// Keeps track of all WebSocket connections
// When the DO hibernates, gets reconstructed in the constructor
sessions: Map<WebSocket, { [key: string]: string }>;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sessions = new Map();
// As part of constructing the Durable Object,
// we wake up any hibernating WebSockets and
// place them back in the `sessions` map.
// Get all WebSocket connections from the DO
this.ctx.getWebSockets().forEach((ws) => {
let attachment = ws.deserializeAttachment();
if (attachment) {
// If we previously attached state to our WebSocket,
// let's add it to `sessions` map to restore the state of the connection.
this.sessions.set(ws, { ...attachment });
}
});
// Sets an application level auto response that does not wake hibernated WebSockets.
this.ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair('ping', 'pong'));
}
async fetch(request: Request): Promise<Response> {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
// request within the Durable Object. It has the effect of "accepting" the connection,
// and allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
// the connection is open. During periods of inactivity, the Durable Object can be evicted
// from memory, but the WebSocket connection will remain open. If at some later point the
// WebSocket receives a message, the runtime will recreate the Durable Object
// (run the `constructor`) and deliver the message to the appropriate handler.
this.ctx.acceptWebSocket(server);
// Generate a random UUID for the session.
const id = crypto.randomUUID();
// Attach the session ID to the WebSocket connection and serialize it.
// This is necessary to restore the state of the connection when the Durable Object wakes up.
server.serializeAttachment({ id });
// Add the WebSocket connection to the map of active sessions.
this.sessions.set(server, { id });
return new Response(null, {
status: 101,
webSocket: client,
});
}
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
// Get the session associated with the WebSocket connection.
const session = this.sessions.get(ws)!;
// Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
// and the total number of connections with the "[Durable Object]: " prefix
ws.send(`[Durable Object] message: ${message}, from: ${session.id}, to: the initiating client. Total connections: ${this.sessions.size}`);
// Send a message to all WebSocket connections, loop over all the connected WebSockets.
this.sessions.forEach((attachment, connectedWs) => {
connectedWs.send(`[Durable Object] message: ${message}, from: ${session.id}, to: all clients. Total connections: ${this.sessions.size}`);
});
// Send a message to all WebSocket connections except the connection (ws),
// loop over all the connected WebSockets and filter out the connection (ws).
this.sessions.forEach((attachment, connectedWs) => {
if (connectedWs !== ws) {
connectedWs.send(`[Durable Object] message: ${message}, from: ${session.id}, to: all clients except the initiating client. Total connections: ${this.sessions.size}`);
}
});
}
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) {
// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
// auto-replies to Close frames. Calling close() is safe but no longer required.
ws.close(code, reason);
this.sessions.delete(ws);
}
}from workers import DurableObject, Response, WorkerEntrypoint
from js import WebSocketPair, WebSocketRequestResponsePair
import uuid
class Session:
def __init__(self, *, ws):
self.ws = ws
# Worker
class Default(WorkerEntrypoint):
async def fetch(self, request):
if request.url.endswith('/websocket'):
# Expect to receive a WebSocket Upgrade request.
# If there is one, accept the request and return a WebSocket Response.
upgrade_header = request.headers.get('Upgrade')
if not upgrade_header or upgrade_header != 'websocket':
return Response('Worker expected Upgrade: websocket', status=426)
if request.method != 'GET':
return Response('Worker expected GET method', status=400)
# Since we are hard coding the Durable Object ID by providing the constant name 'foo',
# all requests to this Worker will be sent to the same Durable Object instance.
stub = self.env.WEBSOCKET_HIBERNATION_SERVER.getByName("foo")
return await stub.fetch(request)
return Response(
"""Supported endpoints:
/websocket: Expects a WebSocket upgrade request""",
status=200,
headers={'Content-Type': 'text/plain'}
)
# Durable Object
class WebSocketHibernationServer(DurableObject):
def __init__(self, ctx, env):
super().__init__(ctx, env)
# Keeps track of all WebSocket connections, keyed by session ID
# When the DO hibernates, gets reconstructed in the constructor
self.sessions = {}
# As part of constructing the Durable Object,
# we wake up any hibernating WebSockets and
# place them back in the `sessions` map.
# Get all WebSocket connections from the DO
for ws in self.ctx.getWebSockets():
attachment = ws.deserializeAttachment()
if attachment:
# If we previously attached state to our WebSocket,
# let's add it to `sessions` map to restore the state of the connection.
# Use the session ID as the key
self.sessions[attachment] = Session(ws=ws)
# Sets an application level auto response that does not wake hibernated WebSockets.
self.ctx.setWebSocketAutoResponse(WebSocketRequestResponsePair.new('ping', 'pong'))
async def fetch(self, request):
# Creates two ends of a WebSocket connection.
client, server = WebSocketPair.new().object_values()
# Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
# request within the Durable Object. It has the effect of "accepting" the connection,
# and allowing the WebSocket to send and receive messages.
# Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
# is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
# the connection is open. During periods of inactivity, the Durable Object can be evicted
# from memory, but the WebSocket connection will remain open. If at some later point the
# WebSocket receives a message, the runtime will recreate the Durable Object
# (run the `constructor`) and deliver the message to the appropriate handler.
self.ctx.acceptWebSocket(server)
# Generate a random UUID for the session.
id = str(uuid.uuid4())
# Attach the session ID to the WebSocket connection and serialize it.
# This is necessary to restore the state of the connection when the Durable Object wakes up.
server.serializeAttachment(id)
# Add the WebSocket connection to the map of active sessions, keyed by session ID.
self.sessions[id] = Session(ws=server)
return Response(None, status=101, web_socket=client)
async def webSocketMessage(self, ws, message):
# Get the session ID associated with the WebSocket connection.
session_id = ws.deserializeAttachment()
# Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
# and the total number of connections with the "[Durable Object]: " prefix
ws.send(f"[Durable Object] message: {message}, from: {session_id}, to: the initiating client. Total connections: {len(self.sessions)}")
# Send a message to all WebSocket connections, loop over all the connected WebSockets.
for session in self.sessions.values():
session.ws.send(f"[Durable Object] message: {message}, from: {session_id}, to: all clients. Total connections: {len(self.sessions)}")
# Send a message to all WebSocket connections except the connection (ws),
# loop over all the connected WebSockets and filter out the connection (ws).
for session in self.sessions.values():
if session.ws != ws:
session.ws.send(f"[Durable Object] message: {message}, from: {session_id}, to: all clients except the initiating client. Total connections: {len(self.sessions)}")
async def webSocketClose(self, ws, code, reason, wasClean):
# With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
# auto-replies to Close frames. Calling close() is safe but no longer required.
ws.close(code, reason)
# Get the session ID from the WebSocket attachment to remove it from sessions
session_id = ws.deserializeAttachment()
if session_id:
self.sessions.pop(session_id, None)最后,根据先前选择的命名空间和类名,在 Wrangler 文件中配置 Durable Object 绑定和迁移。
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "websocket-hibernation-server",
"main": "src/index.ts",
"durable_objects": {
"bindings": [
{
"name": "WEBSOCKET_HIBERNATION_SERVER",
"class_name": "WebSocketHibernationServer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": [
"WebSocketHibernationServer"
]
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "websocket-hibernation-server"
main = "src/index.ts"
[[durable_objects.bindings]]
name = "WEBSOCKET_HIBERNATION_SERVER"
class_name = "WebSocketHibernationServer"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "WebSocketHibernationServer" ]