INTEGRITY Dokumentace

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:

  1. Hibernation WebSocket API - Umožňuje Durable Object hibernovat, aniž by se při nečinnosti odpojili klienti. (doporučeno)
  2. Standardní webové WebSocket API - Používá známé addEventListener vzor 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:

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í:

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:

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:

  1. Předejte požadavek z Workeru do Durable Object
  2. Zavolejte DurableObjectState::acceptWebSocket pro přijetí spojení na straně serveru
  3. 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:

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:

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

Uchovává kopii value přidružený k WebSocket připojení.

Klíčové chování:

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

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:

  1. Worker ověří požadavek na upgrade
  2. Worker předá požadavek Durable Object
  3. Durable Object přijme spojení na straně serveru
  4. 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.