From 7aacdc2f2bf68aed64c50250521c3b641c4e55c4 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Tue, 28 Oct 2025 21:41:55 +0000 Subject: [PATCH 1/8] Add WebSocket connection support to sandbox documentation --- src/content/docs/sandbox/api/ports.mdx | 42 +++++++++++++++++++ .../docs/sandbox/concepts/preview-urls.mdx | 1 - src/content/docs/sandbox/index.mdx | 24 +++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index 73a1aeac27c6573..c5941d3e3723262 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -84,6 +84,48 @@ for (const port of ports) { ``` +### `connect()` + +Route incoming WebSocket upgrade requests to WebSocket servers running in the sandbox. + +```ts +import { connect } from '@cloudflare/sandbox'; + +const response = await connect(sandbox: Sandbox, request: Request, port: number): Promise +``` + +**Parameters**: +- `sandbox` - Sandbox instance containing the WebSocket server +- `request` - Incoming WebSocket upgrade request +- `port` - Port number (1024-65535, excluding 3000) + +**Returns**: `Promise` - WebSocket response establishing the connection + + +``` +import { getSandbox, connect } from '@cloudflare/sandbox'; + +export default { + async fetch(request: Request, env: Env): Promise { + const sandbox = getSandbox(env.Sandbox, 'my-sandbox'); + + // Start WebSocket echo server + await sandbox.writeFile('/workspace/server.js', \` + Bun.serve({ + port: 8080, + fetch(req, server) { if (server.upgrade(req)) return; }, + websocket: { message(ws, msg) { ws.send(\\\`Echo: \${msg}\\\`); } } + }); + \`); + await sandbox.startProcess('bun /workspace/server.js'); + + // Route WebSocket connections + return await connect(sandbox, request, 8080); + } +}; +``` + + ## Related resources - [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work diff --git a/src/content/docs/sandbox/concepts/preview-urls.mdx b/src/content/docs/sandbox/concepts/preview-urls.mdx index c6419f084f11324..410f446c4948ed8 100644 --- a/src/content/docs/sandbox/concepts/preview-urls.mdx +++ b/src/content/docs/sandbox/concepts/preview-urls.mdx @@ -79,7 +79,6 @@ const admin = await sandbox.exposePort(3001, { name: "admin" }); - Raw TCP/UDP connections - Custom protocols (must wrap in HTTP) -- WebSocket connections - Ports outside range 1024-65535 - Port 3000 (used internally by the SDK) diff --git a/src/content/docs/sandbox/index.mdx b/src/content/docs/sandbox/index.mdx index d70a31282ec5111..4daf929a7f522a2 100644 --- a/src/content/docs/sandbox/index.mdx +++ b/src/content/docs/sandbox/index.mdx @@ -112,6 +112,30 @@ df['sales'].sum() # Last expression is automatically returned }; ``` + + ```typescript + import { getSandbox, connect } from '@cloudflare/sandbox'; + + export default { + async fetch(request: Request, env: Env): Promise { + const sandbox = getSandbox(env.Sandbox, 'user-123'); + + // Start a WebSocket echo server + await sandbox.writeFile('/workspace/server.js', \` + Bun.serve({ + port: 8080, + fetch(req, server) { if (server.upgrade(req)) return; }, + websocket: { message(ws, msg) { ws.send(\\\`Echo: \${msg}\\\`); } } + }); + \`); + await sandbox.startProcess('bun /workspace/server.js'); + + // Route WebSocket connections to the server + return await connect(sandbox, request, 8080); + } + }; + ``` + From c5b0e597da92e94687fdbf79472af764f84658b8 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Tue, 28 Oct 2025 21:53:04 +0000 Subject: [PATCH 2/8] fix escaping literal --- src/content/docs/sandbox/index.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/content/docs/sandbox/index.mdx b/src/content/docs/sandbox/index.mdx index 4daf929a7f522a2..a1615d6d53a275e 100644 --- a/src/content/docs/sandbox/index.mdx +++ b/src/content/docs/sandbox/index.mdx @@ -121,13 +121,14 @@ df['sales'].sum() # Last expression is automatically returned const sandbox = getSandbox(env.Sandbox, 'user-123'); // Start a WebSocket echo server - await sandbox.writeFile('/workspace/server.js', \` + const serverCode = ` Bun.serve({ port: 8080, fetch(req, server) { if (server.upgrade(req)) return; }, - websocket: { message(ws, msg) { ws.send(\\\`Echo: \${msg}\\\`); } } + websocket: { message(ws, msg) { ws.send(\`Echo: \${msg}\`); } } }); - \`); + `; + await sandbox.writeFile('/workspace/server.js', serverCode); await sandbox.startProcess('bun /workspace/server.js'); // Route WebSocket connections to the server From b6accfa28037c5aa1468f1c554e43d9a78420d79 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Tue, 28 Oct 2025 22:02:39 +0000 Subject: [PATCH 3/8] fix formatting --- src/content/docs/sandbox/api/ports.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index c5941d3e3723262..82a9bf1a8692c1c 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -110,13 +110,14 @@ export default { const sandbox = getSandbox(env.Sandbox, 'my-sandbox'); // Start WebSocket echo server - await sandbox.writeFile('/workspace/server.js', \` + const serverCode = ` Bun.serve({ port: 8080, fetch(req, server) { if (server.upgrade(req)) return; }, - websocket: { message(ws, msg) { ws.send(\\\`Echo: \${msg}\\\`); } } + websocket: { message(ws, msg) { ws.send(\`Echo: \${msg}\`); } } }); - \`); + `; + await sandbox.writeFile('/workspace/server.js', serverCode); await sandbox.startProcess('bun /workspace/server.js'); // Route WebSocket connections From 7898b02a582feb7c608da29353253d7f4fced530 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Wed, 29 Oct 2025 17:31:33 +0000 Subject: [PATCH 4/8] update code examples --- src/content/docs/sandbox/api/ports.mdx | 82 +++++++++++++++++--------- src/content/docs/sandbox/index.mdx | 73 +++++++++++++++-------- 2 files changed, 105 insertions(+), 50 deletions(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index 82a9bf1a8692c1c..d571bda2ff8f805 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -24,6 +24,7 @@ const response = await sandbox.exposePort(port: number, options?: ExposePortOpti ``` **Parameters**: + - `port` - Port number to expose (1024-65535) - `options` (optional): - `name` - Friendly name for the port @@ -44,7 +45,8 @@ const api = await sandbox.exposePort(3000, { name: 'api' }); await sandbox.startProcess('npm run dev'); const frontend = await sandbox.exposePort(5173, { name: 'frontend' }); -``` + +```` ### `unexposePort()` @@ -53,16 +55,13 @@ Remove an exposed port and close its preview URL. ```ts await sandbox.unexposePort(port: number): Promise -``` +```` **Parameters**: + - `port` - Port number to unexpose - -``` -await sandbox.unexposePort(8000); -``` - +``` await sandbox.unexposePort(8000); ``` ### `getExposedPorts()` @@ -79,9 +78,10 @@ const response = await sandbox.getExposedPorts(): Promise ### `connect()` @@ -92,9 +92,10 @@ Route incoming WebSocket upgrade requests to WebSocket servers running in the sa import { connect } from '@cloudflare/sandbox'; const response = await connect(sandbox: Sandbox, request: Request, port: number): Promise -``` +```` **Parameters**: + - `sandbox` - Sandbox instance containing the WebSocket server - `request` - Incoming WebSocket upgrade request - `port` - Port number (1024-65535, excluding 3000) @@ -103,27 +104,53 @@ const response = await connect(sandbox: Sandbox, request: Request, port: number) ``` -import { getSandbox, connect } from '@cloudflare/sandbox'; +import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox"; + +export { Sandbox } from "@cloudflare/sandbox"; + +type Env = { +Sandbox: DurableObjectNamespace; +}; + +const initialized = new Set(); export default { async fetch(request: Request, env: Env): Promise { - const sandbox = getSandbox(env.Sandbox, 'my-sandbox'); - - // Start WebSocket echo server - const serverCode = ` - Bun.serve({ - port: 8080, - fetch(req, server) { if (server.upgrade(req)) return; }, - websocket: { message(ws, msg) { ws.send(\`Echo: \${msg}\`); } } - }); - `; - await sandbox.writeFile('/workspace/server.js', serverCode); - await sandbox.startProcess('bun /workspace/server.js'); - - // Route WebSocket connections - return await connect(sandbox, request, 8080); - } + 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); + } + + return new Response('WebSocket endpoint. Connect using ws:// protocol.', { status: 200 }); + +}, }; + ``` @@ -131,3 +158,4 @@ export default { - [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work - [Commands API](/sandbox/api/commands/) - Start background processes +``` diff --git a/src/content/docs/sandbox/index.mdx b/src/content/docs/sandbox/index.mdx index a1615d6d53a275e..6d0545a0a4e190d 100644 --- a/src/content/docs/sandbox/index.mdx +++ b/src/content/docs/sandbox/index.mdx @@ -114,29 +114,55 @@ df['sales'].sum() # Last expression is automatically returned ```typescript - import { getSandbox, connect } from '@cloudflare/sandbox'; - - export default { - async fetch(request: Request, env: Env): Promise { - const sandbox = getSandbox(env.Sandbox, 'user-123'); - - // Start a WebSocket echo server - const serverCode = ` - Bun.serve({ - port: 8080, - fetch(req, server) { if (server.upgrade(req)) return; }, - websocket: { message(ws, msg) { ws.send(\`Echo: \${msg}\`); } } - }); - `; - await sandbox.writeFile('/workspace/server.js', serverCode); - await sandbox.startProcess('bun /workspace/server.js'); - - // Route WebSocket connections to the server - return await connect(sandbox, request, 8080); - } - }; - ``` - + import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox"; + +export { Sandbox } from "@cloudflare/sandbox"; + +type Env = { +Sandbox: DurableObjectNamespace; +}; + +const initialized = new Set(); + +export default { + async fetch(request: Request, env: Env): Promise { + 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); + } + + return new Response('WebSocket endpoint. Connect using ws:// protocol.', { status: 200 }); + +}, +}; + +``` + @@ -294,3 +320,4 @@ Stateful coordination layer that enables Sandbox to maintain persistent environm +``` From e86e7e90286e2b27d84f59056f1ec0183a09fd41 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Wed, 29 Oct 2025 17:33:05 +0000 Subject: [PATCH 5/8] update code examples --- src/content/docs/sandbox/api/ports.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index d571bda2ff8f805..c3fb49a7484d5ec 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -103,7 +103,7 @@ const response = await connect(sandbox: Sandbox, request: Request, port: number) **Returns**: `Promise` - WebSocket response establishing the connection -``` +```ts import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox"; export { Sandbox } from "@cloudflare/sandbox"; From 90447d843144a3b91311f892a457d1004acbc2e0 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Wed, 29 Oct 2025 17:49:50 +0000 Subject: [PATCH 6/8] fixed typo --- src/content/docs/sandbox/api/ports.mdx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index c3fb49a7484d5ec..d29436ed742f80f 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -45,8 +45,7 @@ const api = await sandbox.exposePort(3000, { name: 'api' }); await sandbox.startProcess('npm run dev'); const frontend = await sandbox.exposePort(5173, { name: 'frontend' }); - -```` +``` ### `unexposePort()` @@ -55,7 +54,7 @@ Remove an exposed port and close its preview URL. ```ts await sandbox.unexposePort(port: number): Promise -```` +``` **Parameters**: @@ -80,8 +79,7 @@ const { ports } = await sandbox.getExposedPorts(); for (const port of ports) { console.log(`${port.name || port.port}: ${port.exposedAt}`); } - -```` +``` ### `connect()` @@ -92,7 +90,7 @@ Route incoming WebSocket upgrade requests to WebSocket servers running in the sa import { connect } from '@cloudflare/sandbox'; const response = await connect(sandbox: Sandbox, request: Request, port: number): Promise -```` +``` **Parameters**: From 346658fb8564714d1705aa0354296b0b81a80a89 Mon Sep 17 00:00:00 2001 From: katereznykova Date: Wed, 29 Oct 2025 18:02:16 +0000 Subject: [PATCH 7/8] fix the build --- src/content/docs/sandbox/api/ports.mdx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index d29436ed742f80f..8506226d73bfd03 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -60,7 +60,11 @@ await sandbox.unexposePort(port: number): Promise - `port` - Port number to unexpose -``` await sandbox.unexposePort(8000); ``` + +```ts +await sandbox.unexposePort(8000); +``` + ### `getExposedPorts()` @@ -156,4 +160,3 @@ initialized.add(sandboxId); - [Preview URLs concept](/sandbox/concepts/preview-urls/) - How preview URLs work - [Commands API](/sandbox/api/commands/) - Start background processes -``` From 1625015bdc3145c18b1e49956277b4816e3602b1 Mon Sep 17 00:00:00 2001 From: Naresh Date: Thu, 30 Oct 2025 15:43:59 +0000 Subject: [PATCH 8/8] Use updated wsConnect API (#26182) --- src/content/docs/sandbox/api/ports.mdx | 60 ++--- .../docs/sandbox/concepts/preview-urls.mdx | 23 ++ .../sandbox/guides/websocket-connections.mdx | 216 ++++++++++++++++++ src/content/docs/sandbox/index.mdx | 66 ++---- 4 files changed, 270 insertions(+), 95 deletions(-) create mode 100644 src/content/docs/sandbox/guides/websocket-connections.mdx diff --git a/src/content/docs/sandbox/api/ports.mdx b/src/content/docs/sandbox/api/ports.mdx index 8506226d73bfd03..5c98b5bdd67b0a4 100644 --- a/src/content/docs/sandbox/api/ports.mdx +++ b/src/content/docs/sandbox/api/ports.mdx @@ -86,19 +86,22 @@ console.log(`${port.name || port.port}: ${port.exposedAt}`); ``` -### `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 +```ts +const response = await sandbox.wsConnect(request: Request, port: number): Promise ``` **Parameters**: -- `sandbox` - Sandbox instance containing the WebSocket server - `request` - Incoming WebSocket upgrade request - `port` - Port number (1024-65535, excluding 3000) @@ -106,53 +109,18 @@ const response = await connect(sandbox: Sandbox, request: Request, port: number) ```ts -import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox"; - -export { Sandbox } from "@cloudflare/sandbox"; - -type Env = { -Sandbox: DurableObjectNamespace; -}; - -const initialized = new Set(); +import { getSandbox } from "@cloudflare/sandbox"; export default { async fetch(request: Request, env: Env): Promise { - 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 }); + } }; - ``` diff --git a/src/content/docs/sandbox/concepts/preview-urls.mdx b/src/content/docs/sandbox/concepts/preview-urls.mdx index 410f446c4948ed8..8ba00fadf8da3cb 100644 --- a/src/content/docs/sandbox/concepts/preview-urls.mdx +++ b/src/content/docs/sandbox/concepts/preview-urls.mdx @@ -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 @@ -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 diff --git a/src/content/docs/sandbox/guides/websocket-connections.mdx b/src/content/docs/sandbox/guides/websocket-connections.mdx new file mode 100644 index 000000000000000..ee4964a716a465b --- /dev/null +++ b/src/content/docs/sandbox/guides/websocket-connections.mdx @@ -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:** + + +```ts +import { getSandbox } from '@cloudflare/sandbox'; + +export { Sandbox } from "@cloudflare/sandbox"; + +export default { + async fetch(request: Request, env: Env): Promise { + 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'); + +} +}; + +```` + + +**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: + + +```ts +import { getSandbox, proxyToSandbox } from '@cloudflare/sandbox'; + +export default { + async fetch(request: Request, env: Env): Promise { + 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'); + +} +}; + +```` + + +**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: + + +```ts +export default { + async fetch(request: Request, env: Env): Promise { + 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'); + +} +}; + +```` + + +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: + + +```ts +console.log(request.headers.get('Upgrade')); // 'websocket' +console.log(request.headers.get('Connection')); // 'Upgrade' +```` + + + +### 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 diff --git a/src/content/docs/sandbox/index.mdx b/src/content/docs/sandbox/index.mdx index 6d0545a0a4e190d..b573099a710bb94 100644 --- a/src/content/docs/sandbox/index.mdx +++ b/src/content/docs/sandbox/index.mdx @@ -114,55 +114,23 @@ df['sales'].sum() # Last expression is automatically returned ```typescript - import { getSandbox, connect, type Sandbox } from "@cloudflare/sandbox"; - -export { Sandbox } from "@cloudflare/sandbox"; - -type Env = { -Sandbox: DurableObjectNamespace; -}; - -const initialized = new Set(); - -export default { - async fetch(request: Request, env: Env): Promise { - 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); - } - - return new Response('WebSocket endpoint. Connect using ws:// protocol.', { status: 200 }); - -}, -}; - -``` - + import { getSandbox } from '@cloudflare/sandbox'; + + export default { + async fetch(request: Request, env: Env): Promise { + // Connect to WebSocket services in sandbox + if (request.headers.get('Upgrade')?.toLowerCase() === 'websocket') { + const sandbox = getSandbox(env.Sandbox, 'user-123'); + return await sandbox.wsConnect(request, 8080); + } + + return Response.json({ message: 'WebSocket endpoint' }); + } + }; + ``` + + Connect to WebSocket servers running in sandboxes. Learn more: [WebSocket Connections](/sandbox/guides/websocket-connections/). +