Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 14 additions & 46 deletions src/content/docs/sandbox/api/ports.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,73 +86,41 @@ console.log(`${port.name || port.port}: ${port.exposedAt}`);
```
</TypeScriptExample>

### `connect()`
### `wsConnect()`

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

```ts
import { connect } from '@cloudflare/sandbox';
**Common use cases:**
- Route incoming WebSocket upgrade requests with custom authentication or authorization
- Connect from your Worker to get real-time data from sandbox services

For exposing WebSocket services via public preview URLs, use `exposePort()` with `proxyToSandbox()` instead. See [WebSocket Connections guide](/sandbox/guides/websocket-connections/) for examples.

const response = await connect(sandbox: Sandbox, request: Request, port: number): Promise<Response>
```ts
const response = await sandbox.wsConnect(request: Request, port: number): Promise<Response>
```

**Parameters**:

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

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

<TypeScriptExample>
```ts
import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

type Env = {
Sandbox: DurableObjectNamespace<Sandbox>;
};

const initialized = new Set<string>();
import { getSandbox } from "@cloudflare/sandbox";

export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandboxId = "my-sandbox";
const sandbox = getSandbox(env.Sandbox, sandboxId);

if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
// Initialize WebSocket server on first connection
if (!initialized.has(sandboxId)) {
const serverCode = `

Bun.serve({
port: 8080,
fetch(req, server) {
if (server.upgrade(req)) return;
return new Response('WebSocket server', { status: 200 });
},
websocket: {
message(ws, msg) {
ws.send(\`Echo: \${msg}\`);
}
}
});
`;
await sandbox.writeFile('/workspace/server.js', serverCode);
await sandbox.startProcess('bun /workspace/server.js');
await new Promise(resolve => setTimeout(resolve, 1000));
initialized.add(sandboxId);
}

return await connect(sandbox, request, 8080);
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
return await sandbox.wsConnect(request, 8080);
}

return new Response('WebSocket endpoint. Connect using ws:// protocol.', { status: 200 });

},
return new Response('WebSocket endpoint', { status: 200 });
}
};

```
</TypeScriptExample>

Expand Down
23 changes: 23 additions & 0 deletions src/content/docs/sandbox/concepts/preview-urls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const admin = await sandbox.exposePort(3001, { name: "admin" });
## What Works

- HTTP/HTTPS requests
- WebSocket connections
- Server-Sent Events
- All HTTP methods (GET, POST, PUT, DELETE, etc.)
- Request and response headers
Expand All @@ -82,6 +83,28 @@ const admin = await sandbox.exposePort(3001, { name: "admin" });
- Ports outside range 1024-65535
- Port 3000 (used internally by the SDK)

## WebSocket Support

Preview URLs support WebSocket connections. When a WebSocket upgrade request hits an exposed port, the routing layer automatically handles the connection handshake.

```typescript
// Start a WebSocket server
await sandbox.startProcess("bun run ws-server.ts 8080");
const { exposedAt } = await sandbox.exposePort(8080);

// Clients connect using WebSocket protocol
// Browser: new WebSocket('wss://8080-abc123.example.com')

// Your Worker routes automatically
export default {
async fetch(request, env) {
return proxyToSandbox(request, env.Sandbox, "sandbox-id");
},
};
```

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).

## Security

:::caution
Expand Down
216 changes: 216 additions & 0 deletions src/content/docs/sandbox/guides/websocket-connections.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
title: WebSocket Connections
pcx_content_type: how-to
sidebar:
order: 5
description: Connect to WebSocket servers running in sandboxes.
---

import { TypeScriptExample } from "~/components";

This guide shows you how to work with WebSocket servers running in your sandboxes.

## Choose your approach

**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.

**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.

## Connect to WebSocket echo server

**Create the echo server:**

```typescript title="echo-server.ts"
Bun.serve({
port: 8080,
hostname: "0.0.0.0",
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("WebSocket echo server");
},
websocket: {
message(ws, message) {
ws.send(`Echo: ${message}`);
},
open(ws) {
console.log("Client connected");
},
close(ws) {
console.log("Client disconnected");
},
},
});

console.log("WebSocket server listening on port 8080");
```

**Extend the Dockerfile:**

```dockerfile title="Dockerfile"
FROM docker.io/cloudflare/sandbox:0.3.3

# Copy echo server into the container
COPY echo-server.ts /workspace/echo-server.ts

# Create custom startup script
COPY startup.sh /container-server/startup.sh
RUN chmod +x /container-server/startup.sh
```

**Create startup script:**

```bash title="startup.sh"
#!/bin/bash
# Start your WebSocket server in the background
bun /workspace/echo-server.ts &
# Start SDK's control plane (needed for the SDK to work)
exec bun dist/index.js
```

**Connect from your Worker:**

<TypeScriptExample>
```ts
import { getSandbox } from '@cloudflare/sandbox';

export { Sandbox } from "@cloudflare/sandbox";

export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') {
const sandbox = getSandbox(env.Sandbox, 'echo-service');
return await sandbox.wsConnect(request, 8080);
}

return new Response('WebSocket endpoint');

}
};

````
</TypeScriptExample>

**Client connects:**

```javascript
const ws = new WebSocket('wss://your-worker.com');
ws.onmessage = (event) => console.log(event.data);
ws.send('Hello!'); // Receives: "Echo: Hello!"
````

## Expose WebSocket service via preview URL

Get a public URL for your WebSocket server:

<TypeScriptExample>
```ts
import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox';

export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, 'echo-service');

// Expose the port to get preview URL
const { exposedAt } = await sandbox.exposePort(8080);

// Return URL to clients
if (request.url.includes('/ws-url')) {
return Response.json({ url: exposedAt.replace('https', 'wss') });
}

// Auto-route all requests via proxyToSandbox
return proxyToSandbox(request, env.Sandbox, 'echo-service');

}
};

````
</TypeScriptExample>

**Client connects to preview URL:**

```javascript
// Get the preview URL
const response = await fetch('https://your-worker.com/ws-url');
const { url } = await response.json();

// Connect
const ws = new WebSocket(url);
ws.onmessage = (event) => console.log(event.data);
ws.send('Hello!'); // Receives: "Echo: Hello!"
````

## Connect from Worker to get real-time data

Your Worker can connect to a WebSocket service to get real-time data, even when the incoming request isn't a WebSocket:

<TypeScriptExample>
```ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const sandbox = getSandbox(env.Sandbox, 'data-processor');

// Incoming HTTP request needs real-time data from sandbox
const wsRequest = new Request('ws://internal', {
headers: {
'Upgrade': 'websocket',
'Connection': 'Upgrade'
}
});

// Connect to WebSocket service in sandbox
const wsResponse = await sandbox.wsConnect(wsRequest, 8080);

// Process WebSocket stream and return HTTP response
// (Implementation depends on your needs)

return new Response('Processed real-time data');

}
};

````
</TypeScriptExample>

This pattern is useful when you need streaming data from sandbox services but want to return HTTP responses to clients.

## Troubleshooting

### Upgrade failed

Verify request has WebSocket headers:

<TypeScriptExample>
```ts
console.log(request.headers.get('Upgrade')); // 'websocket'
console.log(request.headers.get('Connection')); // 'Upgrade'
````

</TypeScriptExample>

### Local development

Expose ports in Dockerfile for `wrangler dev`:

```dockerfile title="Dockerfile"
FROM docker.io/cloudflare/sandbox:0.3.3

COPY echo-server.ts /workspace/echo-server.ts
COPY startup.sh /container-server/startup.sh
RUN chmod +x /container-server/startup.sh

# Required for local development
EXPOSE 8080
```

:::note
Port exposure in Dockerfile is only required for local development. In production, all ports are automatically accessible.
:::

## Related resources

- [Ports API reference](/sandbox/api/ports/) - Complete API documentation
- [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work
- [Background processes guide](/sandbox/guides/background-processes/) - Managing long-running services
Loading