跳转到内容
搜索文档

使用 WebSocket 休眠构建 WebSocket 服务器

在 Durable Objects 和 Workers 上使用 WebSocket 休眠构建 WebSocket 服务器

最后更新 查看 MarkdownAgent 设置

此示例与构建 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" ]

这篇文档对您有帮助吗?