WebSocketヒバーネーションを使用したWebSocketサーバーの構築
Durable ObjectsとWorkersを使用してWebSocketヒバーネーションを利用したWebSocketサーバーを構築します。
この例は、WebSocketサーバーの構築の例に似ていますが、WebSocketヒバーネーションAPIを使用しています。WebSocketヒバーネーションAPIは、Durable Objects上に構築されたWebSocketサーバーアプリケーションに推奨されます。これは、料金の発生時間を大幅に減少させ、WebSocketアプリケーションと相性の良い追加機能を提供するためです。詳細については、WebSocketsでDurable Objectsを使用するを参照してください。
import { DurableObject } from "cloudflare:workers";
// Workerexport default { async fetch(request, env, ctx) { if (request.url.endsWith("/websocket")) { // WebSocketアップグレードリクエストを受け取ることを期待します。 // もしあれば、リクエストを受け入れ、WebSocketレスポンスを返します。 const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { return new Response('Durable Object expected Upgrade: websocket', { status: 426 }); }
// この例は、名前「foo」がハードコーディングされているため、 // 同じDurable Objectインスタンスを参照します。 let id = env.WEBSOCKET_HIBERNATION_SERVER.idFromName("foo"); let stub = env.WEBSOCKET_HIBERNATION_SERVER.get(id);
return stub.fetch(request); }
return new Response(null, { status: 400, statusText: 'Bad Request', headers: { 'Content-Type': 'text/plain', }, }); }};
// Durable Objectexport class WebSocketHibernationServer extends DurableObject {
async fetch(request) { // WebSocket接続の2つの端を作成します。 const webSocketPair = new WebSocketPair(); const [client, server] = Object.values(webSocketPair);
// `acceptWebSocket()`を呼び出すことで、このWebSocketがDurable Object内でリクエストを終了することを // ランタイムに通知します。これは接続を「受け入れる」効果があり、 // WebSocketがメッセージを送受信できるようにします。 // `ws.accept()`とは異なり、`state.acceptWebSocket(ws)`はWebSocketが「ヒバーネート可能」であることを // Workersランタイムに通知するため、接続がオープンの間、Durable Objectをメモリに固定する必要がありません。 // 非アクティブな期間中、Durable Objectはメモリから追い出される可能性がありますが、 // WebSocket接続はオープンのままです。後でWebSocketがメッセージを受信した場合、 // ランタイムはDurable Objectを再作成し(`constructor`を実行し)、メッセージを適切なハンドラーに配信します。 this.ctx.acceptWebSocket(server);
return new Response(null, { status: 101, webSocket: client, }); }
async webSocketMessage(ws, message) { // クライアントからメッセージを受信した際、同じメッセージで応答しますが、 // メッセージの前に「[Durable Object]: 」を付け加え、接続の総数を返します。 ws.send(`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`); }
async webSocketClose(ws, code, reason, wasClean) { // クライアントが接続を閉じた場合、ランタイムはwebSocketClose()ハンドラーを呼び出します。 ws.close(code, "Durable Object is closing WebSocket"); }}import { DurableObject } from "cloudflare:workers";
export interface Env { WEBSOCKET_HIBERNATION_SERVER: DurableObjectNamespace<WebSocketHibernationServer>;}
// Workerexport default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { if (request.url.endsWith("/websocket")) { // WebSocketアップグレードリクエストを受け取ることを期待します。 // もしあれば、リクエストを受け入れ、WebSocketレスポンスを返します。 const upgradeHeader = request.headers.get('Upgrade'); if (!upgradeHeader || upgradeHeader !== 'websocket') { return new Response('Durable Object expected Upgrade: websocket', { status: 426 }); }
// この例は、名前「foo」がハードコーディングされているため、 // 同じDurable Objectインスタンスを参照します。 let id = env.WEBSOCKET_HIBERNATION_SERVER.idFromName("foo"); let stub = env.WEBSOCKET_HIBERNATION_SERVER.get(id);
return stub.fetch(request); }
return new Response(null, { status: 400, statusText: 'Bad Request', headers: { 'Content-Type': 'text/plain', }, }); }};
// Durable Objectexport class WebSocketHibernationServer extends DurableObject {
async fetch(request: Request): Promise<Response> { // WebSocket接続の2つの端を作成します。 const webSocketPair = new WebSocketPair(); const [client, server] = Object.values(webSocketPair);
// `acceptWebSocket()`を呼び出すことで、このWebSocketがDurable Object内でリクエストを終了することを // ランタイムに通知します。これは接続を「受け入れる」効果があり、 // WebSocketがメッセージを送受信できるようにします。 // `ws.accept()`とは異なり、`state.acceptWebSocket(ws)`はWebSocketが「ヒバーネート可能」であることを // Workersランタイムに通知するため、接続がオープンの間、Durable Objectをメモリに固定する必要がありません。 // 非アクティブな期間中、Durable Objectはメモリから追い出される可能性がありますが、 // WebSocket接続はオープンのままです。後でWebSocketがメッセージを受信した場合、 // ランタイムはDurable Objectを再作成し(`constructor`を実行し)、メッセージを適切なハンドラーに配信します。 this.ctx.acceptWebSocket(server);
return new Response(null, { status: 101, webSocket: client, }); }
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) { // クライアントからメッセージを受信した際、サーバーは同じメッセージで応答し、 // 接続の総数を「[Durable Object]: 」のプレフィックス付きで返します。 ws.send(`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`); }
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) { // クライアントが接続を閉じた場合、ランタイムはwebSocketClose()ハンドラーを呼び出します。 ws.close(code, "Durable Object is closing WebSocket"); }}最後に、wrangler.tomlファイルを構成して、選択した名前空間とクラス名に基づいてDurable Objectのバインディングとマイグレーションを含めます。
name = "websocket-hibernation-server"
[[durable_objects.bindings]]name = "WEBSOCKET_HIBERNATION_SERVER"class_name = "WebSocketHibernationServer"
[[migrations]]tag = "v1"new_classes = ["WebSocketHibernationServer"]