INTEGRITY Dokumentace

Vytvořte server WebSocket pomocí WebSocket Hibernation

Tento příklad je podobný Vytvořte server WebSocket příklad, ale používá WebSocket Hibernation API. Pro serverové WebSocket aplikace postavené na Durable Objects byste měli upřednostnit WebSocket Hibernation API, protože výrazně snižuje poplatky za dobu trvání a nabízí další funkce, které se s WebSocket aplikacemi dobře doplňují. Další informace najdete v Použití Durable Objects s WebSockets.

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)

Nakonec nakonfigurujte soubor Wrangler tak, aby obsahoval Durable Object binding a migrace na základě dříve zvoleného namespace a názvu třídy.

{
	"$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" ]