Skip to content

Commit a5e5178

Browse files
committed
Use updated wsConnect API
1 parent 346658f commit a5e5178

File tree

4 files changed

+270
-95
lines changed

4 files changed

+270
-95
lines changed

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

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -86,73 +86,41 @@ console.log(`${port.name || port.port}: ${port.exposedAt}`);
8686
```
8787
</TypeScriptExample>
8888

89-
### `connect()`
89+
### `wsConnect()`
9090

91-
Route incoming WebSocket upgrade requests to WebSocket servers running in the sandbox.
91+
Connect to WebSocket servers running in the sandbox. Use this when your Worker needs to establish WebSocket connections with services in the sandbox.
9292

93-
```ts
94-
import { connect } from '@cloudflare/sandbox';
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.
9598

96-
const response = await connect(sandbox: Sandbox, request: Request, port: number): Promise<Response>
99+
```ts
100+
const response = await sandbox.wsConnect(request: Request, port: number): Promise<Response>
97101
```
98102

99103
**Parameters**:
100104

101-
- `sandbox` - Sandbox instance containing the WebSocket server
102105
- `request` - Incoming WebSocket upgrade request
103106
- `port` - Port number (1024-65535, excluding 3000)
104107

105108
**Returns**: `Promise<Response>` - WebSocket response establishing the connection
106109

107110
<TypeScriptExample>
108111
```ts
109-
import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox";
110-
111-
export { Sandbox } from "@cloudflare/sandbox";
112-
113-
type Env = {
114-
Sandbox: DurableObjectNamespace<Sandbox>;
115-
};
116-
117-
const initialized = new Set<string>();
112+
import { getSandbox } from "@cloudflare/sandbox";
118113

119114
export default {
120115
async fetch(request: Request, env: Env): Promise<Response> {
121-
const sandboxId = "my-sandbox";
122-
const sandbox = getSandbox(env.Sandbox, sandboxId);
123-
124116
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
125-
// Initialize WebSocket server on first connection
126-
if (!initialized.has(sandboxId)) {
127-
const serverCode = `
128-
129-
Bun.serve({
130-
port: 8080,
131-
fetch(req, server) {
132-
if (server.upgrade(req)) return;
133-
return new Response('WebSocket server', { status: 200 });
134-
},
135-
websocket: {
136-
message(ws, msg) {
137-
ws.send(\`Echo: \${msg}\`);
138-
}
139-
}
140-
});
141-
`;
142-
await sandbox.writeFile('/workspace/server.js', serverCode);
143-
await sandbox.startProcess('bun /workspace/server.js');
144-
await new Promise(resolve => setTimeout(resolve, 1000));
145-
initialized.add(sandboxId);
146-
}
147-
148-
return await connect(sandbox, request, 8080);
117+
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
118+
return await sandbox.wsConnect(request, 8080);
149119
}
150120

151-
return new Response('WebSocket endpoint. Connect using ws:// protocol.', { status: 200 });
152-
153-
},
121+
return new Response('WebSocket endpoint', { status: 200 });
122+
}
154123
};
155-
156124
```
157125
</TypeScriptExample>
158126

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

Lines changed: 23 additions & 0 deletions
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
@@ -82,6 +83,28 @@ const admin = await sandbox.exposePort(3001, { name: "admin" });
8283
- Ports outside range 1024-65535
8384
- Port 3000 (used internally by the SDK)
8485

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+
85108
## Security
86109

87110
:::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

0 commit comments

Comments
 (0)