Skip to content

Commit 2400393

Browse files
elithrarOxyjun
andauthored
do: websockets reorder (#20855)
* do: websockets reorder Make it clear which we recommend. * Apply suggestions from code review --------- Co-authored-by: Jun Lee <[email protected]>
1 parent d60ca9d commit 2400393

File tree

1 file changed

+168
-162
lines changed

1 file changed

+168
-162
lines changed

src/content/docs/durable-objects/best-practices/websockets.mdx

Lines changed: 168 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,179 @@ sidebar:
77

88
import { Tabs, TabItem, GlossaryTooltip, Type } from "~/components";
99

10+
This guide covers how to use Durable Objects as WebSocket servers that can connect thousands of clients (per instance), as well as a WebSocket client to connect to other servers or even Durable Objects.
11+
12+
There are two sets of WebSockets API
13+
14+
1. Native Durable Object WebSocket API, which allows your Durable Object to hibernate without disconnecting clients when not actively doing work **(recommended)**
15+
2. Web Standard WebSocket APIs, using the familiar `addEventListener` event pattern.
16+
17+
### What are WebSockets?
18+
1019
WebSockets are long-lived TCP connections that enable bi-directional, real-time communication between client and server. Both Cloudflare Durable Objects and Workers can act as WebSocket endpoints – either as a client or as a server. Because WebSocket sessions are long-lived, applications commonly use Durable Objects to accept either the client or server connection. While there are other use cases for using Workers exclusively with WebSockets, for example proxying WebSocket messages, WebSockets are most useful when combined with Durable Objects.
1120

1221
Because Durable Objects provide a single-point-of-coordination between [Cloudflare Workers](/workers/), a single Durable Object instance can be used in parallel with WebSockets to coordinate between multiple clients, such as participants in a chat room or a multiplayer game. Refer to [Cloudflare Edge Chat Demo](https://github.com/cloudflare/workers-chat-demo) for an example of using Durable Objects with WebSockets.
1322

14-
Both Cloudflare Durable Objects and Workers can use the [Web Standard WebSocket API](/workers/runtime-apis/websockets/) to build applications, but a major differentiator of Cloudflare Durable Objects relative to other platforms is the ability to Hibernate WebSocket connections to save costs.
23+
Both Cloudflare Durable Objects and Workers can use the [Web Standard WebSocket API](/workers/runtime-apis/websockets/) to build applications, but a major differentiator of Cloudflare Durable Objects relative to other platforms is the ability to Hibernate WebSocket connections to save costs. Clients can remain connected when the Durable Object is idle (and not sending messages or running compute tasks), which allows you to push events to clients and minimize the active duration (GB-seconds) associated with long-running Durable Object processes.
24+
25+
### WebSocket Hibernation API
26+
27+
In addition to [Workers WebSocket API](/workers/runtime-apis/websockets/), Cloudflare Durable Objects can use the WebSocket Hibernation API which extends the Web Standard WebSocket API to reduce costs. Specifically, [billable Duration (GB-s) charges](/durable-objects/platform/pricing/) are not incurred during periods of inactivity. Note that other events, for example [alarms](/durable-objects/api/alarms/), can prevent a Durable Object from being inactive and therefore prevent this cost saving.
28+
29+
The WebSocket consists of Cloudflare-specific extensions to the Web Standard WebSocket API. These extensions are either present on the [DurableObjectState](/durable-objects/api/state) interface, or as handler methods on the Durable Object class.
30+
31+
:::note
32+
33+
Hibernation is only supported when a Durable Object acts as a WebSocket server. Currently, outgoing WebSockets cannot hibernate.
34+
35+
:::
36+
37+
The Worker used in the WebSocket Standard API example does not require any code changes to make use of the WebSocket Hibernation API. The changes to the Durable Object are described in the code sample below. In summary, [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket) is called to accept the server side of the WebSocket connection, and handler methods are defined on the Durable Object class for relevant event types rather than adding event listeners.
38+
39+
If an event occurs for a hibernated Durable Object's corresponding handler method, it will return to memory. This will call the Durable Object's constructor, so it is best to minimize work in the constructor when using WebSocket hibernation.
40+
41+
<Tabs> <TabItem label="JavaScript" icon="seti:javascript">
42+
43+
```js
44+
import { DurableObject } from "cloudflare:workers";
45+
46+
// Durable Object
47+
export class WebSocketHibernationServer extends DurableObject {
48+
async fetch(request) {
49+
// Creates two ends of a WebSocket connection.
50+
const webSocketPair = new WebSocketPair();
51+
const [client, server] = Object.values(webSocketPair);
52+
53+
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
54+
// request within the Durable Object. It has the effect of "accepting" the connection,
55+
// and allowing the WebSocket to send and receive messages.
56+
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
57+
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
58+
// the connection is open. During periods of inactivity, the Durable Object can be evicted
59+
// from memory, but the WebSocket connection will remain open. If at some later point the
60+
// WebSocket receives a message, the runtime will recreate the Durable Object
61+
// (run the `constructor`) and deliver the message to the appropriate handler.
62+
this.ctx.acceptWebSocket(server);
63+
64+
return new Response(null, {
65+
status: 101,
66+
webSocket: client,
67+
});
68+
}
69+
70+
async webSocketMessage(ws, message) {
71+
// Upon receiving a message from the client, reply with the same message,
72+
// but will prefix the message with "[Durable Object]: " and return the
73+
// total number of connections.
74+
ws.send(
75+
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
76+
);
77+
}
78+
79+
async webSocketClose(ws, code, reason, wasClean) {
80+
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
81+
ws.close(code, "Durable Object is closing WebSocket");
82+
}
83+
}
84+
```
85+
86+
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
87+
88+
```ts
89+
import { DurableObject } from "cloudflare:workers";
90+
91+
export interface Env {
92+
WEBSOCKET_HIBERNATION_SERVER: DurableObjectNamespace<WebSocketHibernationServer>;
93+
}
94+
95+
// Durable Object
96+
export class WebSocketHibernationServer extends DurableObject {
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()`, `state.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+
return new Response(null, {
114+
status: 101,
115+
webSocket: client,
116+
});
117+
}
118+
119+
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
120+
// Upon receiving a message from the client, the server replies with the same message,
121+
// and the total number of connections with the "[Durable Object]: " prefix
122+
ws.send(
123+
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
124+
);
125+
}
126+
127+
async webSocketClose(
128+
ws: WebSocket,
129+
code: number,
130+
reason: string,
131+
wasClean: boolean,
132+
) {
133+
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
134+
ws.close(code, "Durable Object is closing WebSocket");
135+
}
136+
}
137+
```
138+
139+
</TabItem> </Tabs>
140+
141+
Similar to the WebSocket Standard API example, to execute this code, configure your Wrangler file to include a Durable Object [binding](/durable-objects/get-started/tutorial/#5-configure-durable-object-bindings) and [migration](/durable-objects/reference/durable-objects-migrations/) based on the <GlossaryTooltip term="namespace">namespace</GlossaryTooltip> and class name chosen previously.
142+
143+
```toml title="wrangler.toml"
144+
name = "websocket-hibernation-server"
145+
146+
[[durable_objects.bindings]]
147+
name = "WEBSOCKET_HIBERNATION_SERVER"
148+
class_name = "WebSocketHibernationServer"
149+
150+
[[migrations]]
151+
tag = "v1"
152+
new_classes = ["WebSocketHibernationServer"]
153+
```
154+
155+
A full example can be found in [Build a WebSocket server with WebSocket Hibernation](/durable-objects/examples/websocket-hibernation-server/).
156+
157+
:::caution[Support for local development]
158+
159+
Prior to `[email protected]` and Miniflare `v3.20231016.0`, WebSockets did not hibernate when using local development environments such as `wrangler dev` or Miniflare.
160+
161+
If you are using older versions, note that while hibernatable WebSocket events such as [`webSocketMessage()`](/durable-objects/api/base/#websocketmessage) will still be delivered, the Durable Object will never be evicted from memory.
162+
163+
:::
164+
165+
## Extended methods
166+
167+
### `serializeAttachment`
168+
169+
- <code> serializeAttachment(value <Type text="any" />)</code>: <Type text="void" />
170+
171+
- Keeps a copy of `value` associated with the WebSocket to survive hibernation. The value can be any type supported by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), which is true of most types. If the value needs to be durable please use [Durable Object Storage](/durable-objects/api/storage-api/).
172+
173+
- If you modify `value` after calling this method, those changes will not be retained unless you call this method again. The serialized size of `value` is limited to 2,048 bytes, otherwise this method will throw an error. If you need larger values to survive hibernation, use the [Storage API](/durable-objects/api/storage-api/) and pass the corresponding key to this method so it can be retrieved later.
174+
175+
### `deserializeAttachment`
176+
177+
- `deserializeAttachment()`: <Type text='any' />
178+
179+
- Retrieves the most recent value passed to `serializeAttachment()`, or `null` if none exists.
15180

16-
This guide covers:
17-
1. Building a WebSocket server using Web Standard APIs
18-
2. Using WebSocket Hibernation APIs.
19181

20-
## WebSocket Standard API
182+
### WebSocket Standard API
21183

22184
WebSocket connections are established by making an HTTP GET request with the `Upgrade: websocket` header. A Cloudflare Worker is commonly used to validate the request, proxy the request to the Durable Object to accept the server side connection, and return the client side connection in the response.
23185

@@ -239,165 +401,9 @@ Code updates will disconnect all WebSockets. If you deploy a new version of a Wo
239401

240402
:::
241403

242-
## WebSocket Hibernation API
243-
244-
In addition to [Workers WebSocket API](/workers/runtime-apis/websockets/), Cloudflare Durable Objects can use the WebSocket Hibernation API which extends the Web Standard WebSocket API to reduce costs. Specifically, [billable Duration (GB-s) charges](/durable-objects/platform/pricing/) are not incurred during periods of inactivity. Note that other events, for example [alarms](/durable-objects/api/alarms/), can prevent a Durable Object from being inactive and therefore prevent this cost saving.
245-
246-
The WebSocket consists of Cloudflare-specific extensions to the Web Standard WebSocket API. These extensions are either present on the [DurableObjectState](/durable-objects/api/state) interface, or as handler methods on the Durable Object class.
247-
248-
:::note
249-
250-
Hibernation is only supported when a Durable Object acts as a WebSocket server. Currently, outgoing WebSockets cannot hibernate.
251-
252-
:::
253-
254-
The Worker used in the WebSocket Standard API example does not require any code changes to make use of the WebSocket Hibernation API. The changes to the Durable Object are described in the code sample below. In summary, [`DurableObjectState::acceptWebSocket`](/durable-objects/api/state/#acceptwebsocket) is called to accept the server side of the WebSocket connection, and handler methods are defined on the Durable Object class for relevant event types rather than adding event listeners.
255-
256-
If an event occurs for a hibernated Durable Object's corresponding handler method, it will return to memory. This will call the Durable Object's constructor, so it is best to minimize work in the constructor when using WebSocket hibernation.
257-
258-
<Tabs> <TabItem label="JavaScript" icon="seti:javascript">
259-
260-
```js
261-
import { DurableObject } from "cloudflare:workers";
262-
263-
// Durable Object
264-
export class WebSocketHibernationServer extends DurableObject {
265-
async fetch(request) {
266-
// Creates two ends of a WebSocket connection.
267-
const webSocketPair = new WebSocketPair();
268-
const [client, server] = Object.values(webSocketPair);
269-
270-
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
271-
// request within the Durable Object. It has the effect of "accepting" the connection,
272-
// and allowing the WebSocket to send and receive messages.
273-
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
274-
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
275-
// the connection is open. During periods of inactivity, the Durable Object can be evicted
276-
// from memory, but the WebSocket connection will remain open. If at some later point the
277-
// WebSocket receives a message, the runtime will recreate the Durable Object
278-
// (run the `constructor`) and deliver the message to the appropriate handler.
279-
this.ctx.acceptWebSocket(server);
280-
281-
return new Response(null, {
282-
status: 101,
283-
webSocket: client,
284-
});
285-
}
286-
287-
async webSocketMessage(ws, message) {
288-
// Upon receiving a message from the client, reply with the same message,
289-
// but will prefix the message with "[Durable Object]: " and return the
290-
// total number of connections.
291-
ws.send(
292-
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
293-
);
294-
}
295-
296-
async webSocketClose(ws, code, reason, wasClean) {
297-
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
298-
ws.close(code, "Durable Object is closing WebSocket");
299-
}
300-
}
301-
```
302-
303-
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
304-
305-
```ts
306-
import { DurableObject } from "cloudflare:workers";
307-
308-
export interface Env {
309-
WEBSOCKET_HIBERNATION_SERVER: DurableObjectNamespace<WebSocketHibernationServer>;
310-
}
311-
312-
// Durable Object
313-
export class WebSocketHibernationServer extends DurableObject {
314-
async fetch(request: Request): Promise<Response> {
315-
// Creates two ends of a WebSocket connection.
316-
const webSocketPair = new WebSocketPair();
317-
const [client, server] = Object.values(webSocketPair);
318-
319-
// Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
320-
// request within the Durable Object. It has the effect of "accepting" the connection,
321-
// and allowing the WebSocket to send and receive messages.
322-
// Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
323-
// is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
324-
// the connection is open. During periods of inactivity, the Durable Object can be evicted
325-
// from memory, but the WebSocket connection will remain open. If at some later point the
326-
// WebSocket receives a message, the runtime will recreate the Durable Object
327-
// (run the `constructor`) and deliver the message to the appropriate handler.
328-
this.ctx.acceptWebSocket(server);
329-
330-
return new Response(null, {
331-
status: 101,
332-
webSocket: client,
333-
});
334-
}
335-
336-
async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
337-
// Upon receiving a message from the client, the server replies with the same message,
338-
// and the total number of connections with the "[Durable Object]: " prefix
339-
ws.send(
340-
`[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
341-
);
342-
}
343-
344-
async webSocketClose(
345-
ws: WebSocket,
346-
code: number,
347-
reason: string,
348-
wasClean: boolean,
349-
) {
350-
// If the client closes the connection, the runtime will invoke the webSocketClose() handler.
351-
ws.close(code, "Durable Object is closing WebSocket");
352-
}
353-
}
354-
```
355-
356-
</TabItem> </Tabs>
357-
358-
Similar to the WebSocket Standard API example, to execute this code, configure your Wrangler file to include a Durable Object [binding](/durable-objects/get-started/tutorial/#5-configure-durable-object-bindings) and [migration](/durable-objects/reference/durable-objects-migrations/) based on the <GlossaryTooltip term="namespace">namespace</GlossaryTooltip> and class name chosen previously.
359-
360-
```toml title="wrangler.toml"
361-
name = "websocket-hibernation-server"
362-
363-
[[durable_objects.bindings]]
364-
name = "WEBSOCKET_HIBERNATION_SERVER"
365-
class_name = "WebSocketHibernationServer"
366-
367-
[[migrations]]
368-
tag = "v1"
369-
new_classes = ["WebSocketHibernationServer"]
370-
```
371-
372-
A full example can be found in [Build a WebSocket server with WebSocket Hibernation](/durable-objects/examples/websocket-hibernation-server/).
373-
374-
:::caution[Support for local development]
375-
376-
Prior to `[email protected]` and Miniflare `v3.20231016.0`, WebSockets did not hibernate when using local development environments such as `wrangler dev` or Miniflare.
377-
378-
If you are using older versions, note that while hibernatable WebSocket events such as [`webSocketMessage()`](/durable-objects/api/base/#websocketmessage) will still be delivered, the Durable Object will never be evicted from memory.
379-
380-
:::
381-
382-
## Extended methods
383-
384-
### `serializeAttachment`
385-
386-
- <code> serializeAttachment(value <Type text="any" />)</code>: <Type text="void" />
387-
388-
- Keeps a copy of `value` associated with the WebSocket to survive hibernation. The value can be any type supported by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), which is true of most types. If the value needs to be durable please use [Durable Object Storage](/durable-objects/api/storage-api/).
389-
390-
- If you modify `value` after calling this method, those changes will not be retained unless you call this method again. The serialized size of `value` is limited to 2,048 bytes, otherwise this method will throw an error. If you need larger values to survive hibernation, use the [Storage API](/durable-objects/api/storage-api/) and pass the corresponding key to this method so it can be retrieved later.
391-
392-
### `deserializeAttachment`
393-
394-
- `deserializeAttachment()`: <Type text='any' />
395-
396-
- Retrieves the most recent value passed to `serializeAttachment()`, or `null` if none exists.
397-
398404
## Related resources
399405

400406
- [Mozilla Developer Network's (MDN) documentation on the WebSocket class](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
401407
- [Cloudflare's WebSocket template for building applications on Workers using WebSockets](https://github.com/cloudflare/websocket-template)
402408
- [Durable Object base class](/durable-objects/api/base/)
403-
- [Durable Object State interface](/durable-objects/api/state/)
409+
- [Durable Object State interface](/durable-objects/api/state/)

0 commit comments

Comments
 (0)