@@ -28,130 +28,130 @@ import { DurableObject } from 'cloudflare:workers';
2828
2929// Worker
3030export default {
31- async fetch(request : Request , env : Env , ctx : ExecutionContext ): Promise <Response > {
32- if (request .url .endsWith (' /websocket' )) {
33- // Expect to receive a WebSocket Upgrade request.
34- // If there is one, accept the request and return a WebSocket Response.
35- const upgradeHeader = request .headers .get (' Upgrade' );
36- if (! upgradeHeader || upgradeHeader !== ' websocket' ) {
37- return new Response (' Worker expected Upgrade: websocket' , {
38- status: 426 ,
39- });
40- }
41-
42- if (request .method !== ' GET' ) {
43- return new Response (' Worker expected GET method' , {
44- status: 400 ,
45- });
46- }
47-
48- // Since we are hard coding the Durable Object ID by providing the constant name 'foo',
49- // all requests to this Worker will be sent to the same Durable Object instance.
50- let id = env .WEBSOCKET_HIBERNATION_SERVER .idFromName (' foo' );
51- let stub = env .WEBSOCKET_HIBERNATION_SERVER .get (id );
52-
53- return stub .fetch (request );
54- }
55-
56- return new Response (
57- ` Supported endpoints:
58- /websocket: Expects a WebSocket upgrade request ` ,
59- {
60- status: 200 ,
61- headers: {
62- ' Content-Type' : ' text/plain' ,
63- },
64- }
65- );
66- },
67- };
68-
69- // Durable Object
70- export class WebSocketHibernationServer extends DurableObject {
71- // Keeps track of all WebSocket connections
72- // When the DO hibernates, gets reconstructed in the constructor
73- sessions: Map <WebSocket , { [key : string ]: string }>;
74-
75- constructor (ctx : DurableObjectState , env : Env ) {
76- super (ctx , env );
77- this .sessions = new Map ();
78-
79- // As part of constructing the Durable Object,
80- // we wake up any hibernating WebSockets and
81- // place them back in the `sessions` map.
82-
83- // Get all WebSocket connections from the DO
84- this .ctx .getWebSockets ().forEach ((ws ) => {
85- let attachment = ws .deserializeAttachment ();
86- if (attachment ) {
87- // If we previously attached state to our WebSocket,
88- // let's add it to `sessions` map to restore the state of the connection.
89- this .sessions .set (ws , { ... attachment });
90- }
31+ async fetch(request : Request , env : Env , ctx : ExecutionContext ): Promise <Response > {
32+ if (request .url .endsWith (' /websocket' )) {
33+ // Expect to receive a WebSocket Upgrade request.
34+ // If there is one, accept the request and return a WebSocket Response.
35+ const upgradeHeader = request .headers .get (' Upgrade' );
36+ if (! upgradeHeader || upgradeHeader !== ' websocket' ) {
37+ return new Response (' Worker expected Upgrade: websocket' , {
38+ status: 426 ,
9139 });
40+ }
9241
93- // Sets an application level auto response that does not wake hibernated WebSockets.
94- this .ctx .setWebSocketAutoResponse (new WebSocketRequestResponsePair (' ping' , ' pong' ));
95- }
96-
97- async fetch(request : Request ): Promise <Response > {
98- // Creates two ends of a WebSocket connection.
99- const webSocketPair = new WebSocketPair ();
100- const [client, server] = Object .values (webSocketPair );
101-
102- // Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
103- // request within the Durable Object. It has the effect of "accepting" the connection,
104- // and allowing the WebSocket to send and receive messages.
105- // Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
106- // is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
107- // the connection is open. During periods of inactivity, the Durable Object can be evicted
108- // from memory, but the WebSocket connection will remain open. If at some later point the
109- // WebSocket receives a message, the runtime will recreate the Durable Object
110- // (run the `constructor`) and deliver the message to the appropriate handler.
111- this .ctx .acceptWebSocket (server );
112-
113- // Generate a random UUID for the session.
114- const id = crypto .randomUUID ();
115-
116- // Attach the session ID to the WebSocket connection and serialize it.
117- // This is necessary to restore the state of the connection when the Durable Object wakes up.
118- server .serializeAttachment ({ id });
119-
120- // Add the WebSocket connection to the map of active sessions.
121- this .sessions .set (server , { id });
122-
123- return new Response (null , {
124- status: 101 ,
125- webSocket: client ,
42+ if (request .method !== ' GET' ) {
43+ return new Response (' Worker expected GET method' , {
44+ status: 400 ,
12645 });
127- }
128-
129- async webSocketMessage(ws : WebSocket , message : ArrayBuffer | string ) {
130- // Get the session associated with the WebSocket connection.
131- const session = this .sessions .get (ws )! ;
46+ }
13247
133- // Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
134- // and the total number of connections with the "[Durable Object]: " prefix
135- ws .send (` [Durable Object] message: ${message }, from: ${session .id }. Total connections: ${this .sessions .size } ` );
48+ // Since we are hard coding the Durable Object ID by providing the constant name 'foo',
49+ // all requests to this Worker will be sent to the same Durable Object instance.
50+ let id = env .WEBSOCKET_HIBERNATION_SERVER .idFromName (' foo' );
51+ let stub = env .WEBSOCKET_HIBERNATION_SERVER .get (id );
13652
137- // Send a message to all WebSocket connections, loop over all the connected WebSockets.
138- this .sessions .forEach ((attachment , session ) => {
139- session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
140- });
141-
142- // Send a message to all WebSocket connections except the connection (ws),
143- // loop over all the connected WebSockets and filter out the connection (ws).
144- this .sessions .forEach ((attachment , session ) => {
145- if (session !== ws ) {
146- session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
147- }
148- });
53+ return stub .fetch (request );
14954 }
15055
151- async webSocketClose(ws : WebSocket , code : number , reason : string , wasClean : boolean ) {
152- // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
153- ws .close (code , ' Durable Object is closing WebSocket' );
154- }
56+ return new Response (
57+ ` Supported endpoints:
58+ /websocket: Expects a WebSocket upgrade request ` ,
59+ {
60+ status: 200 ,
61+ headers: {
62+ ' Content-Type' : ' text/plain' ,
63+ },
64+ }
65+ );
66+ },
67+ };
68+
69+ // Durable Object
70+ export class WebSocketHibernationServer extends DurableObject {
71+ // Keeps track of all WebSocket connections
72+ // When the DO hibernates, gets reconstructed in the constructor
73+ sessions: Map <WebSocket , { [key : string ]: string }>;
74+
75+ constructor (ctx : DurableObjectState , env : Env ) {
76+ super (ctx , env );
77+ this .sessions = new Map ();
78+
79+ // As part of constructing the Durable Object,
80+ // we wake up any hibernating WebSockets and
81+ // place them back in the `sessions` map.
82+
83+ // Get all WebSocket connections from the DO
84+ this .ctx .getWebSockets ().forEach ((ws ) => {
85+ let attachment = ws .deserializeAttachment ();
86+ if (attachment ) {
87+ // If we previously attached state to our WebSocket,
88+ // let's add it to `sessions` map to restore the state of the connection.
89+ this .sessions .set (ws , { ... attachment });
90+ }
91+ });
92+
93+ // Sets an application level auto response that does not wake hibernated WebSockets.
94+ this .ctx .setWebSocketAutoResponse (new WebSocketRequestResponsePair (' ping' , ' pong' ));
95+ }
96+
97+ async fetch(request : Request ): Promise <Response > {
98+ // Creates two ends of a WebSocket connection.
99+ const webSocketPair = new WebSocketPair ();
100+ const [client, server] = Object .values (webSocketPair );
101+
102+ // Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
103+ // request within the Durable Object. It has the effect of "accepting" the connection,
104+ // and allowing the WebSocket to send and receive messages.
105+ // Unlike `ws.accept()`, `this.ctx.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
106+ // is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
107+ // the connection is open. During periods of inactivity, the Durable Object can be evicted
108+ // from memory, but the WebSocket connection will remain open. If at some later point the
109+ // WebSocket receives a message, the runtime will recreate the Durable Object
110+ // (run the `constructor`) and deliver the message to the appropriate handler.
111+ this .ctx .acceptWebSocket (server );
112+
113+ // Generate a random UUID for the session.
114+ const id = crypto .randomUUID ();
115+
116+ // Attach the session ID to the WebSocket connection and serialize it.
117+ // This is necessary to restore the state of the connection when the Durable Object wakes up.
118+ server .serializeAttachment ({ id });
119+
120+ // Add the WebSocket connection to the map of active sessions.
121+ this .sessions .set (server , { id });
122+
123+ return new Response (null , {
124+ status: 101 ,
125+ webSocket: client ,
126+ });
127+ }
128+
129+ async webSocketMessage(ws : WebSocket , message : ArrayBuffer | string ) {
130+ // Get the session associated with the WebSocket connection.
131+ const session = this .sessions .get (ws )! ;
132+
133+ // Upon receiving a message from the client, the server replies with the same message, the session ID of the connection,
134+ // and the total number of connections with the "[Durable Object]: " prefix
135+ ws .send (` [Durable Object] message: ${message }, from: ${session .id }. Total connections: ${this .sessions .size } ` );
136+
137+ // Send a message to all WebSocket connections, loop over all the connected WebSockets.
138+ this .sessions .forEach ((attachment , session ) => {
139+ session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
140+ });
141+
142+ // Send a message to all WebSocket connections except the connection (ws),
143+ // loop over all the connected WebSockets and filter out the connection (ws).
144+ this .sessions .forEach ((attachment , session ) => {
145+ if (session !== ws ) {
146+ session .send (` [Durable Object] message: ${message }, from: ${attachment .id }. Total connections: ${this .sessions .size } ` );
147+ }
148+ });
149+ }
150+
151+ async webSocketClose(ws : WebSocket , code : number , reason : string , wasClean : boolean ) {
152+ // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
153+ ws .close (code , ' Durable Object is closing WebSocket' );
154+ }
155155}
156156```
157157</TypeScriptExample >
0 commit comments