← Cloudflare Durable Objects / durable-objects / best-practices
Použití WebSockets
Durable Objects mohou fungovat jako servery WebSocket, které k jedné instanci připojí tisíce klientů. WebSockets můžete použít i jako klienta pro připojení k jiným serverům nebo Durable Objects.
K dispozici jsou dvě WebSocket API:
- Hibernation WebSocket API - Umožňuje Durable Object hibernovat, aniž by se při nečinnosti odpojili klienti. (doporučeno)
- Standardní webové WebSocket API - Používá známé
addEventListenervzor události.
Co jsou WebSockets?
WebSockets jsou dlouhotrvající TCP připojení, která umožňují obousměrnou komunikaci mezi klientem a serverem v reálném čase.
Klíčové vlastnosti:
- Workers i Durable Objects mohou fungovat jako koncové body WebSocket (klient nebo server)
- Relace WebSocketu jsou dlouhotrvající, díky čemuž jsou Durable Objects ideální pro přijímání připojení
- Jedna instance Durable Object dokáže koordinovat více klientů (například chatovací místnosti nebo hry pro více hráčů)
Viz Cloudflare Edge Chat Demo ↗ pro příklad použití Durable Objects s WebSockets.
Proč používat Hibernation?
Hibernation WebSocket API snižuje náklady tím, že umožňuje Durable Objects přejít do klidového režimu, když jsou neaktivní:
- Klienti zůstávají připojení, i když Durable Object není v paměti
- Poplatky za Billable Duration (GB-s) se během hibernace neúčtují
- Jakmile dorazí zpráva, Durable Object se automaticky probudí
Durable Objects Hibernation WebSocket API
Hibernation WebSocket API rozšiřuje Standardní webové WebSocket API pro snížení nákladů během období nečinnosti.
Jak funguje hibernace
Pokud Durable Object po určitou krátkou dobu nepřijme žádné události (například alarmy nebo zprávy), je vyřazen z paměti. Během hibernace:
- Klienti WebSocketu zůstávají připojeni k síti Cloudflare
- Stav v paměti se resetuje
- Jakmile dorazí událost, Durable Object se znovu inicializuje a jeho
constructorběží
Chcete-li po hibernaci obnovit stav, použijte serializeAttachment a deserializeAttachment pro trvalé uložení dat u každého WebSocket připojení.
Viz Životní cyklus Durable Object s dalšími informacemi.
Příklad hibernace
Chcete-li s Durable Objects používat WebSockets:
- Předejte požadavek z Workeru do Durable Object
- Zavolejte
DurableObjectState::acceptWebSocketpro přijetí spojení na straně serveru - Definujte metody handlerů pro příslušné události ve třídě Durable Object
Pokud dojde k události u hibernovaného Durable Object, runtime jej znovu inicializuje voláním konstruktoru. Při použití hibernace udržujte práci v konstruktoru na minimu.
import { DurableObject } from "cloudflare:workers";
// Durable Object
export class WebSocketHibernationServer extends DurableObject {
async fetch(request) {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
// When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
this.ctx.acceptWebSocket(server);
return new Response(null, {
status: 101,
webSocket: client,
});
}
async webSocketMessage(ws, message) {
// Upon receiving a message from the client, reply with the same message,
// but will prefix the message with "[Durable Object]: " and return the number of connections.
ws.send(
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
);
}
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);
}
}import { DurableObject } from "cloudflare:workers";
export interface Env {
WEBSOCKET_HIBERNATION_SERVER: DurableObjectNamespace<WebSocketHibernationServer>;
}
// Durable Object
export class WebSocketHibernationServer extends DurableObject {
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()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
// When the Durable Object receives a message during Hibernation, it will run the `constructor` to be re-initialized
this.ctx.acceptWebSocket(server);
return new Response(null, {
status: 101,
webSocket: client,
});
}
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
// Upon receiving a message from the client, reply with the same message,
// but will prefix the message with "[Durable Object]: " and return the number of connections.
ws.send(
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
);
}
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);
}
}from workers import Response, DurableObject
from js import WebSocketPair
# Durable Object
class WebSocketHibernationServer(DurableObject):
def **init**(self, state, env):
super().**init**(state, env)
self.ctx = state
async def fetch(self, request):
# Creates two ends of a WebSocket connection.
client, server = WebSocketPair.new().object_values()
# Calling `acceptWebSocket()` connects the WebSocket to the Durable Object, allowing the WebSocket to send and receive messages.
# Unlike `ws.accept()`, `state.acceptWebSocket(ws)` allows the Durable Object to be hibernated
# When the Durable Object receives a message during Hibernation, it will run the `__init__` to be re-initialized
self.ctx.acceptWebSocket(server)
return Response(
None,
status=101,
web_socket=client
)
async def webSocketMessage(self, ws, message):
# Upon receiving a message from the client, reply with the same message,
# but will prefix the message with "[Durable Object]: " and return the number of connections.
ws.send(
f"[Durable Object] message: {message}, connections: {len(self.ctx.get_websockets())}"
)
async def webSocketClose(self, ws, code, reason, was_clean):
# 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)Nakonfigurujte soubor Wrangler s Durable Object binding a migrace:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "websocket-hibernation-server",
"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"
[[durable_objects.bindings]]
name = "WEBSOCKET_HIBERNATION_SERVER"
class_name = "WebSocketHibernationServer"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "WebSocketHibernationServer" ]Úplný příklad najdete v Vytvořte server WebSocket pomocí WebSocket Hibernation.
Automatická obsluha ping/pong
Cloudflare runtime automaticky zpracovává rámce ping protokolu WebSocket:
- Příchozí rámce ping ↗ přijímat automatické odpovědi pong
- Zpracování ping/pong nepřerušuje hibernaci
-
webSocketMessagehandler se nevolá pro řídicí rámce
Toto chování udržuje spojení aktivní, aniž by se Durable Object probudil.
Dávkujte zprávy a snižte tak režii
Každá zpráva WebSocket s sebou nese režii zpracování kvůli přepínání kontextu mezi JavaScript runtime a podkladovým systémem. Odesílání mnoha malých zpráv může jeden Durable Object zahltit, a to i v případě, že celkový objem dat je malý.
Chcete-li maximalizovat propustnost:
- Sloučte více logických zpráv do dávky do jednoho rámce WebSocketu
- Použití jednoduchého formátu obálky pro balení a rozbalení dávkových zpráv
- Posílejte méně, ale větší zprávy namísto mnoha malých
import { DurableObject } from "cloudflare:workers";
// Define a batch envelope format
// Client-side: batch messages before sending
function sendBatch(ws, messages) {
const batch = {
messages,
timestamp: Date.now(),
};
ws.send(JSON.stringify(batch));
}
// Durable Object: process batched messages
export class GameRoom extends DurableObject {
async webSocketMessage(ws, message) {
if (typeof message !== "string") return;
const batch = JSON.parse(message);
// Process all messages in the batch in a single handler invocation
for (const msg of batch.messages) {
this.handleMessage(ws, msg);
}
}
handleMessage(ws, msg) {
// Handle individual message logic
}
}import { DurableObject } from "cloudflare:workers";
// Define a batch envelope format
interface BatchedMessage {
messages: Array<{ type: string; payload: unknown }>;
timestamp: number;
}
// Client-side: batch messages before sending
function sendBatch(
ws: WebSocket,
messages: Array<{ type: string; payload: unknown }>,
) {
const batch: BatchedMessage = {
messages,
timestamp: Date.now(),
};
ws.send(JSON.stringify(batch));
}
// Durable Object: process batched messages
export class GameRoom extends DurableObject<Env> {
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
if (typeof message !== "string") return;
const batch = JSON.parse(message) as BatchedMessage;
// Process all messages in the batch in a single handler invocation
for (const msg of batch.messages) {
this.handleMessage(ws, msg);
}
}
private handleMessage(
ws: WebSocket,
msg: { type: string; payload: unknown },
) {
// Handle individual message logic
}
}Proč dávkové zpracování pomáhá
Čtení WebSocketu vyžaduje přepínání kontextu mezi jádrem a runtime JavaScriptu. Tuto režii vyvolá každá jednotlivá zpráva. Sloučením 10 až 100 logických zpráv do jednoho WebSocket rámce se počet přepnutí kontextu úměrně sníží.
Pro vysokofrekvenční data, jako jsou hodnoty ze senzorů nebo aktualizace stavu hry, používejte dávkování založené na čase nebo na počtu zpráv. Dávku odesílejte každých 50-100ms nebo po každých 50-100 zprávách, podle toho, co nastane dřív.
Rozšířené metody
V Hibernation WebSocket API jsou k dispozici následující metody. Použijte je k uložení a obnovení stavu před hibernací a po ní.
WebSocket.serializeAttachment
serializeAttachment(value:any)void
Uchovává kopii value přidružený k WebSocket připojení.
Klíčové chování:
- Serializované přílohy přetrvávají hibernaci, dokud zůstává WebSocket funkční
- Pokud kterákoli strana uzavře spojení, přílohy se ztratí
- Úpravy
valuepo zavolání této metody se neuchovávají, pokud ji nezavoláte znovu -
valuemůže být libovolného typu podporovaného algoritmus strukturovaného klonování ↗ - Maximální serializovaná velikost je 16,384 bajtů
Pro větší hodnoty nebo data, která musí přetrvat i po skončení životnosti WebSocketu, použijte Storage API a uložit odpovídající klíč jako přílohu.
WebSocket.deserializeAttachment
deserializeAttachment():any
Načte nejnovější hodnotu předanou do serializeAttachment(), nebo null pokud žádný neexistuje.
Příklad připojení
Použijte serializeAttachment a deserializeAttachment pro trvalé uložení stavu připojení napříč hibernací:
import { DurableObject } from "cloudflare:workers";
export class WebSocketServer extends DurableObject {
async fetch(request) {
const url = new URL(request.url);
const orderId = url.searchParams.get("orderId") ?? "anonymous";
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
this.ctx.acceptWebSocket(server);
// Persist per-connection state that survives hibernation
const state = {
orderId,
joinedAt: Date.now(),
};
server.serializeAttachment(state);
return new Response(null, { status: 101, webSocket: client });
}
async webSocketMessage(ws, message) {
// Restore state after potential hibernation
const state = ws.deserializeAttachment();
ws.send(`Hello ${state.orderId}, you joined at ${state.joinedAt}`);
}
async webSocketClose(ws, code, reason, wasClean) {
const state = ws.deserializeAttachment();
console.log(`${state.orderId} disconnected`);
// 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);
}
}import { DurableObject } from "cloudflare:workers";
interface ConnectionState {
orderId: string;
joinedAt: number;
}
export class WebSocketServer extends DurableObject<Env> {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
const orderId = url.searchParams.get("orderId") ?? "anonymous";
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
this.ctx.acceptWebSocket(server);
// Persist per-connection state that survives hibernation
const state: ConnectionState = {
orderId,
joinedAt: Date.now(),
};
server.serializeAttachment(state);
return new Response(null, { status: 101, webSocket: client });
}
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
// Restore state after potential hibernation
const state = ws.deserializeAttachment() as ConnectionState;
ws.send(`Hello ${state.orderId}, you joined at ${state.joinedAt}`);
}
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) {
const state = ws.deserializeAttachment() as ConnectionState;
console.log(`${state.orderId} disconnected`);
// 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);
}
}Standardní WebSocket API
Připojení WebSocket se navazují odesláním HTTP GET požadavku s Upgrade: websocket hlavička.
Typický postup:
- Worker ověří požadavek na upgrade
- Worker předá požadavek Durable Object
- Durable Object přijme spojení na straně serveru
- Worker vrátí v odpovědi spojení na straně klienta
// Worker
export default {
async fetch(request, env, ctx) {
if (request.method === "GET" && 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(null, {
status: 426,
statusText: "Durable Object expected Upgrade: websocket",
headers: {
"Content-Type": "text/plain",
},
});
}
// This example will refer to a single Durable Object instance, since the name "foo" is
// hardcoded
let stub = env.WEBSOCKET_SERVER.getByName("foo");
// The Durable Object's fetch handler will accept the server side connection and return
// the client
return stub.fetch(request);
}
return new Response(null, {
status: 400,
statusText: "Bad Request",
headers: {
"Content-Type": "text/plain",
},
});
},
};// Worker
export default {
async fetch(request, env, ctx): Promise<Response> {
if (request.method === "GET" && 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(null, {
status: 426,
statusText: "Durable Object expected Upgrade: websocket",
headers: {
"Content-Type": "text/plain",
},
});
}
// This example will refer to a single Durable Object instance, since the name "foo" is
// hardcoded
let stub = env.WEBSOCKET_SERVER.getByName("foo");
// The Durable Object's fetch handler will accept the server side connection and return
// the client
return stub.fetch(request);
}
return new Response(null, {
status: 400,
statusText: "Bad Request",
headers: {
"Content-Type": "text/plain",
},
});
},
} satisfies ExportedHandler<Env>;from workers import Response, WorkerEntrypoint
# Worker
class Default(WorkerEntrypoint):
async def fetch(self, request):
if request.method == "GET" and 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(
None,
status=426,
status_text="Durable Object expected Upgrade: websocket",
headers={
"Content-Type": "text/plain",
},
)
# This example will refer to a single Durable Object instance, since the name "foo" is
# hardcoded
stub = self.env.WEBSOCKET_SERVER.getByName("foo")
# The Durable Object's fetch handler will accept the server side connection and return
# the client
return await stub.fetch(request)
return Response(
None,
status=400,
status_text="Bad Request",
headers={
"Content-Type": "text/plain",
},
)Následující Durable Object vytvoří spojení WebSocket a na zprávy odpovídá celkovým počtem spojení:
import { DurableObject } from "cloudflare:workers";
// Durable Object
export class WebSocketServer extends DurableObject {
currentlyConnectedWebSockets;
constructor(ctx, env) {
super(ctx, env);
this.currentlyConnectedWebSockets = 0;
}
async fetch(request) {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Calling `accept()` connects the WebSocket to this Durable Object
server.accept();
this.currentlyConnectedWebSockets += 1;
// Upon receiving a message from the client, the server replies with the same message,
// and the total number of connections with the "[Durable Object]: " prefix
server.addEventListener("message", (event) => {
server.send(
`[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`,
);
});
// When the client closes the connection, clean up the server side.
// 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.
server.addEventListener("close", (cls) => {
this.currentlyConnectedWebSockets -= 1;
server.close(cls.code, "Durable Object is closing WebSocket");
});
return new Response(null, {
status: 101,
webSocket: client,
});
}
}// Durable Object
export class WebSocketServer extends DurableObject {
currentlyConnectedWebSockets: number;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.currentlyConnectedWebSockets = 0;
}
async fetch(request: Request): Promise<Response> {
// Creates two ends of a WebSocket connection.
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// Calling `accept()` connects the WebSocket to this Durable Object
server.accept();
this.currentlyConnectedWebSockets += 1;
// Upon receiving a message from the client, the server replies with the same message,
// and the total number of connections with the "[Durable Object]: " prefix
server.addEventListener("message", (event: MessageEvent) => {
server.send(
`[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`,
);
});
// When the client closes the connection, clean up the server side.
// 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.
server.addEventListener("close", (cls: CloseEvent) => {
this.currentlyConnectedWebSockets -= 1;
server.close(cls.code, "Durable Object is closing WebSocket");
});
return new Response(null, {
status: 101,
webSocket: client,
});
}
}from workers import Response, DurableObject
from js import WebSocketPair
from pyodide.ffi import create_proxy
# Durable Object
class WebSocketServer(DurableObject):
def **init**(self, ctx, env):
super().**init**(ctx, env)
self.currently_connected_websockets = 0
async def fetch(self, request):
# Creates two ends of a WebSocket connection.
client, server = WebSocketPair.new().object_values()
# Calling `accept()` connects the WebSocket to this Durable Object
server.accept()
self.currently_connected_websockets += 1
# Upon receiving a message from the client, the server replies with the same message,
# and the total number of connections with the "[Durable Object]: " prefix
def on_message(event):
server.send(
f"[Durable Object] currentlyConnectedWebSockets: {self.currently_connected_websockets}"
)
server.addEventListener("message", create_proxy(on_message))
# When the client closes the connection, clean up the server side.
# 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.
def on_close(event):
self.currently_connected_websockets -= 1
server.close(event.code, "Durable Object is closing WebSocket")
server.addEventListener("close", create_proxy(on_close))
return Response(
None,
status=101,
web_socket=client,
)Nakonfigurujte soubor Wrangler s Durable Object binding a migrace:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "websocket-server",
"durable_objects": {
"bindings": [
{
"name": "WEBSOCKET_SERVER",
"class_name": "WebSocketServer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["WebSocketServer"]
}
]
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "websocket-server"
[[durable_objects.bindings]]
name = "WEBSOCKET_SERVER"
class_name = "WebSocketServer"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "WebSocketServer" ]Úplný příklad najdete v Vytvořte server WebSocket.
Související zdroje
- Dokumentace Mozilla Developer Network (MDN) ke třídě WebSocket ↗
- Šablona WebSocket od Cloudflare pro vytváření aplikací ve Workers pomocí WebSockets ↗
- základní třída Durable Object
- Rozhraní Durable Object State