Skip to content

Commit 698da86

Browse files
Add WebSocket connection support to sandbox documentation (#26133)
* Add WebSocket connection support to sandbox documentation * fix escaping literal * fix formatting * update code examples * update code examples * fixed typo * fix the build * Use updated wsConnect API (#26182) --------- Co-authored-by: Naresh <naresh@cloudflare.com>
1 parent 6c738db commit 698da86

File tree

4 files changed

+301
-3
lines changed

4 files changed

+301
-3
lines changed

src/content/docs/sandbox/api/ports.mdx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const response = await sandbox.exposePort(port: number, options?: ExposePortOpti
2424
```
2525

2626
**Parameters**:
27+
2728
- `port` - Port number to expose (1024-65535)
2829
- `options` (optional):
2930
- `name` - Friendly name for the port
@@ -56,10 +57,11 @@ await sandbox.unexposePort(port: number): Promise<void>
5657
```
5758

5859
**Parameters**:
60+
5961
- `port` - Port number to unexpose
6062

6163
<TypeScriptExample>
62-
```
64+
```ts
6365
await sandbox.unexposePort(8000);
6466
```
6567
</TypeScriptExample>
@@ -79,11 +81,49 @@ const response = await sandbox.getExposedPorts(): Promise<GetExposedPortsRespons
7981
const { ports } = await sandbox.getExposedPorts();
8082
8183
for (const port of ports) {
82-
console.log(`${port.name || port.port}: ${port.exposedAt}`);
84+
console.log(`${port.name || port.port}: ${port.exposedAt}`);
8385
}
8486
```
8587
</TypeScriptExample>
8688

89+
### `wsConnect()`
90+
91+
Connect to WebSocket servers running in the sandbox. Use this when your Worker needs to establish WebSocket connections with services in the sandbox.
92+
93+
**Common use cases:**
94+
- Route incoming WebSocket upgrade requests with custom authentication or authorization
95+
- Connect from your Worker to get real-time data from sandbox services
96+
97+
For exposing WebSocket services via public preview URLs, use `exposePort()` with `proxyToSandbox()` instead. See [WebSocket Connections guide](/sandbox/guides/websocket-connections/) for examples.
98+
99+
```ts
100+
const response = await sandbox.wsConnect(request: Request, port: number): Promise<Response>
101+
```
102+
103+
**Parameters**:
104+
105+
- `request` - Incoming WebSocket upgrade request
106+
- `port` - Port number (1024-65535, excluding 3000)
107+
108+
**Returns**: `Promise<Response>` - WebSocket response establishing the connection
109+
110+
<TypeScriptExample>
111+
```ts
112+
import { getSandbox } from "@cloudflare/sandbox";
113+
114+
export default {
115+
async fetch(request: Request, env: Env): Promise<Response> {
116+
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
117+
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
118+
return await sandbox.wsConnect(request, 8080);
119+
}
120+
121+
return new Response('WebSocket endpoint', { status: 200 });
122+
}
123+
};
124+
```
125+
</TypeScriptExample>
126+
87127
## Related resources
88128

89129
- [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work

src/content/docs/sandbox/concepts/preview-urls.mdx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const admin = await sandbox.exposePort(3001, { name: "admin" });
7171
## What Works
7272

7373
- HTTP/HTTPS requests
74+
- WebSocket connections
7475
- Server-Sent Events
7576
- All HTTP methods (GET, POST, PUT, DELETE, etc.)
7677
- Request and response headers
@@ -79,10 +80,31 @@ const admin = await sandbox.exposePort(3001, { name: "admin" });
7980

8081
- Raw TCP/UDP connections
8182
- Custom protocols (must wrap in HTTP)
82-
- WebSocket connections
8383
- Ports outside range 1024-65535
8484
- Port 3000 (used internally by the SDK)
8585

86+
## WebSocket Support
87+
88+
Preview URLs support WebSocket connections. When a WebSocket upgrade request hits an exposed port, the routing layer automatically handles the connection handshake.
89+
90+
```typescript
91+
// Start a WebSocket server
92+
await sandbox.startProcess("bun run ws-server.ts 8080");
93+
const { exposedAt } = await sandbox.exposePort(8080);
94+
95+
// Clients connect using WebSocket protocol
96+
// Browser: new WebSocket('wss://8080-abc123.example.com')
97+
98+
// Your Worker routes automatically
99+
export default {
100+
async fetch(request, env) {
101+
return proxyToSandbox(request, env.Sandbox, "sandbox-id");
102+
},
103+
};
104+
```
105+
106+
For custom routing scenarios where your Worker needs to control which sandbox or port to connect to based on request properties, see `wsConnect()` in the [Ports API](/sandbox/api/ports/#wsconnect).
107+
86108
## Security
87109

88110
:::caution
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
---
2+
title: WebSocket Connections
3+
pcx_content_type: how-to
4+
sidebar:
5+
order: 5
6+
description: Connect to WebSocket servers running in sandboxes.
7+
---
8+
9+
import { TypeScriptExample } from "~/components";
10+
11+
This guide shows you how to work with WebSocket servers running in your sandboxes.
12+
13+
## Choose your approach
14+
15+
**Expose via preview URL** - Get a public URL for external clients to connect to. Best for public chat rooms, multiplayer games, or real-time dashboards.
16+
17+
**Connect with wsConnect()** - Your Worker establishes the WebSocket connection. Best for custom routing logic, authentication gates, or when your Worker needs real-time data from sandbox services.
18+
19+
## Connect to WebSocket echo server
20+
21+
**Create the echo server:**
22+
23+
```typescript title="echo-server.ts"
24+
Bun.serve({
25+
port: 8080,
26+
hostname: "0.0.0.0",
27+
fetch(req, server) {
28+
if (server.upgrade(req)) {
29+
return;
30+
}
31+
return new Response("WebSocket echo server");
32+
},
33+
websocket: {
34+
message(ws, message) {
35+
ws.send(`Echo: ${message}`);
36+
},
37+
open(ws) {
38+
console.log("Client connected");
39+
},
40+
close(ws) {
41+
console.log("Client disconnected");
42+
},
43+
},
44+
});
45+
46+
console.log("WebSocket server listening on port 8080");
47+
```
48+
49+
**Extend the Dockerfile:**
50+
51+
```dockerfile title="Dockerfile"
52+
FROM docker.io/cloudflare/sandbox:0.3.3
53+
54+
# Copy echo server into the container
55+
COPY echo-server.ts /workspace/echo-server.ts
56+
57+
# Create custom startup script
58+
COPY startup.sh /container-server/startup.sh
59+
RUN chmod +x /container-server/startup.sh
60+
```
61+
62+
**Create startup script:**
63+
64+
```bash title="startup.sh"
65+
#!/bin/bash
66+
# Start your WebSocket server in the background
67+
bun /workspace/echo-server.ts &
68+
# Start SDK's control plane (needed for the SDK to work)
69+
exec bun dist/index.js
70+
```
71+
72+
**Connect from your Worker:**
73+
74+
<TypeScriptExample>
75+
```ts
76+
import { getSandbox } from '@cloudflare/sandbox';
77+
78+
export { Sandbox } from "@cloudflare/sandbox";
79+
80+
export default {
81+
async fetch(request: Request, env: Env): Promise<Response> {
82+
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
83+
const sandbox = getSandbox(env.Sandbox, 'echo-service');
84+
return await sandbox.wsConnect(request, 8080);
85+
}
86+
87+
return new Response('WebSocket endpoint');
88+
89+
}
90+
};
91+
92+
````
93+
</TypeScriptExample>
94+
95+
**Client connects:**
96+
97+
```javascript
98+
const ws = new WebSocket('wss://your-worker.com');
99+
ws.onmessage = (event) => console.log(event.data);
100+
ws.send('Hello!'); // Receives: "Echo: Hello!"
101+
````
102+
103+
## Expose WebSocket service via preview URL
104+
105+
Get a public URL for your WebSocket server:
106+
107+
<TypeScriptExample>
108+
```ts
109+
import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';
110+
111+
export default {
112+
async fetch(request: Request, env: Env): Promise<Response> {
113+
const sandbox = getSandbox(env.Sandbox, 'echo-service');
114+
115+
// Expose the port to get preview URL
116+
const { exposedAt } = await sandbox.exposePort(8080);
117+
118+
// Return URL to clients
119+
if (request.url.includes('/ws-url')) {
120+
return Response.json({ url: exposedAt.replace('https', 'wss') });
121+
}
122+
123+
// Auto-route all requests via proxyToSandbox
124+
return proxyToSandbox(request, env.Sandbox, 'echo-service');
125+
126+
}
127+
};
128+
129+
````
130+
</TypeScriptExample>
131+
132+
**Client connects to preview URL:**
133+
134+
```javascript
135+
// Get the preview URL
136+
const response = await fetch('https://your-worker.com/ws-url');
137+
const { url } = await response.json();
138+
139+
// Connect
140+
const ws = new WebSocket(url);
141+
ws.onmessage = (event) => console.log(event.data);
142+
ws.send('Hello!'); // Receives: "Echo: Hello!"
143+
````
144+
145+
## Connect from Worker to get real-time data
146+
147+
Your Worker can connect to a WebSocket service to get real-time data, even when the incoming request isn't a WebSocket:
148+
149+
<TypeScriptExample>
150+
```ts
151+
export default {
152+
async fetch(request: Request, env: Env): Promise<Response> {
153+
const sandbox = getSandbox(env.Sandbox, 'data-processor');
154+
155+
// Incoming HTTP request needs real-time data from sandbox
156+
const wsRequest = new Request('ws://internal', {
157+
headers: {
158+
'Upgrade': 'websocket',
159+
'Connection': 'Upgrade'
160+
}
161+
});
162+
163+
// Connect to WebSocket service in sandbox
164+
const wsResponse = await sandbox.wsConnect(wsRequest, 8080);
165+
166+
// Process WebSocket stream and return HTTP response
167+
// (Implementation depends on your needs)
168+
169+
return new Response('Processed real-time data');
170+
171+
}
172+
};
173+
174+
````
175+
</TypeScriptExample>
176+
177+
This pattern is useful when you need streaming data from sandbox services but want to return HTTP responses to clients.
178+
179+
## Troubleshooting
180+
181+
### Upgrade failed
182+
183+
Verify request has WebSocket headers:
184+
185+
<TypeScriptExample>
186+
```ts
187+
console.log(request.headers.get('Upgrade')); // 'websocket'
188+
console.log(request.headers.get('Connection')); // 'Upgrade'
189+
````
190+
191+
</TypeScriptExample>
192+
193+
### Local development
194+
195+
Expose ports in Dockerfile for `wrangler dev`:
196+
197+
```dockerfile title="Dockerfile"
198+
FROM docker.io/cloudflare/sandbox:0.3.3
199+
200+
COPY echo-server.ts /workspace/echo-server.ts
201+
COPY startup.sh /container-server/startup.sh
202+
RUN chmod +x /container-server/startup.sh
203+
204+
# Required for local development
205+
EXPOSE 8080
206+
```
207+
208+
:::note
209+
Port exposure in Dockerfile is only required for local development. In production, all ports are automatically accessible.
210+
:::
211+
212+
## Related resources
213+
214+
- [Ports API reference](/sandbox/api/ports/) - Complete API documentation
215+
- [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work
216+
- [Background processes guide](/sandbox/guides/background-processes/) - Managing long-running services

src/content/docs/sandbox/index.mdx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,25 @@ df['sales'].sum() # Last expression is automatically returned
112112
};
113113
```
114114
</TabItem>
115+
<TabItem label="WebSocket Connections">
116+
```typescript
117+
import { getSandbox } from '@cloudflare/sandbox';
118+
119+
export default {
120+
async fetch(request: Request, env: Env): Promise<Response> {
121+
// Connect to WebSocket services in sandbox
122+
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
123+
const sandbox = getSandbox(env.Sandbox, 'user-123');
124+
return await sandbox.wsConnect(request, 8080);
125+
}
126+
127+
return Response.json({ message: 'WebSocket endpoint' });
128+
}
129+
};
130+
```
131+
132+
Connect to WebSocket servers running in sandboxes. Learn more: [WebSocket Connections](/sandbox/guides/websocket-connections/).
133+
</TabItem>
115134

116135
</Tabs>
117136

@@ -269,3 +288,4 @@ Stateful coordination layer that enables Sandbox to maintain persistent environm
269288
</LinkTitleCard>
270289

271290
</CardGrid>
291+
```

0 commit comments

Comments
 (0)