对于快速预览部署,我们推荐使用 Cloudflare Tunnel 为你的 Web 服务生成预览 URL。这些能在本地开发、workers.dev 和生产使用中协同工作。
await sandbox.startProcess("python -m http.server 8000");
const tunnel = await sandbox.tunnels.get(8000);
console.log(tunnel.url);
// https://acute-llama-dancing-roundly.trycloudflare.app
// 请求将被直接路由到运行在 sandbox 上的 Web 服务器。
const req = await fetch(`${tunnel.url}/api/users`); // => GET http://localhost:8000/api/usersCloudflare Tunnel 支持目前具有以下限制:
- 无法控制生成的 URL。
- 除随机生成的 URL 外,没有身份验证机制。
- 每个 URL 都会在 sandbox 上使用一个额外的
cloudflared进程。
请参阅 tunnels API 参考 以了解完整的 API 和功能集。
对于生产环境的使用,我们推荐使用 exposePort() API 并通过你的 Worker 路由流量。
预览 URL 为运行在 sandbox 内部的服务提供公共 HTTPS 访问。当你暴露一个端口时,你将获得一个唯一的 URL,用于将请求代理到你的服务。
// 从请求中提取主机名
const { hostname } = new URL(request.url);
await sandbox.startProcess("python -m http.server 8000");
const exposed = await sandbox.exposePort(8000, { hostname });
console.log(exposed.url);
// Production: https://8000-sandbox-id-abc123random4567.yourdomain.com
// Local dev: http://8000-sandbox-id-abc123random4567.localhost:{port}/生产环境: https://{port}-{sandbox-id}-{token}.yourdomain.com
- 带有自动生成的 Token:
https://8080-abc123-random16chars12.yourdomain.com - 带有自定义 Token:
https://8080-abc123-my_api_v1.yourdomain.com
本地开发: http://{port}-{sandbox-id}-{token}.localhost:{dev-server-port}
当没有指定自定义 Token 时,将自动生成一个随机的 16 字符 Token:
const exposed = await sandbox.exposePort(8000, { hostname });
// https://8000-sandbox-id-abc123random4567.yourdomain.com取消暴露并重新暴露端口时,带有自动生成 Token 的 URL 会发生改变。
对于生产环境部署或共享的 URL,请指定一个自定义 Token,以跨容器重启保持一致性:
const stable = await sandbox.exposePort(8000, {
hostname,
token: "api_v1",
});
// https://8000-sandbox-id-api_v1.yourdomain.com
// 每次都是相同的 URL ✓Token 要求:
- 长度为 1-16 个字符
- 仅限小写字母 (a-z)、数字 (0-9) 和下划线 (_)
- 在每个 sandbox 内部必须是唯一的
自定义 Token 的使用场景:
- 具有稳定端点的生产环境 API
- 与外部用户共享演示 URL
- 具有一致示例的文档
- 具有可预测 URL 的集成测试
预览 URL 从主机名中提取 sandbox ID 以路由请求。由于主机名是不区分大小写的(根据 RFC 3986),它们总是被转为小写:8080-MyProject-123.yourdomain.com 变为 8080-myproject-123.yourdomain.com。
存在的问题:如果你使用 "MyProject-123" 创建一个 sandbox,它作为具有该精确 ID 的 Durable Object 存在。但是预览 URL 路由到 "myproject-123"(从主机名转为小写)。这些是不同的 Durable Object,因此你的 sandbox 无法通过预览 URL 访问。
// 问题场景
const sandbox = getSandbox(env.Sandbox, "MyProject-123");
// Durable Object ID: "MyProject-123"
await sandbox.exposePort(8080, { hostname });
// Preview URL: 8080-myproject-123-token123.yourdomain.com
// 路由到: "myproject-123" (不同的 DO - 不存在!)解决方案:创建 sandbox 时使用 normalizeId: true 将 ID 转为小写:
const sandbox = getSandbox(env.Sandbox, "MyProject-123", {
normalizeId: true,
});
// Durable Object ID: "myproject-123" (转为小写)
// Preview URL: 8080-myproject-123-token123.yourdomain.com
// 路由到: "myproject-123" (相同的 DO - 有效!)没有 normalizeId: true 时,当 ID 包含大写字母时 exposePort() 会抛出错误。
最佳实践:从一开始就使用小写 ID ('my-project-123')。有关详细信息,请参阅 Sandbox 选项 - normalizeId。
你必须首先在 Worker 的 fetch 处理函数中调用 proxyToSandbox() 来路由预览 URL 请求:
import { proxyToSandbox, getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
// 首先处理预览 URL 路由
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
// 你的应用程序路由
// ...
},
};请求流:浏览器 → 你的 Worker → Durable Object (sandbox) → 你的服务。
同时暴露多个服务:
// 从请求中提取主机名
const { hostname } = new URL(request.url);
await sandbox.startProcess("node api.js"); // 端口 3000
await sandbox.startProcess("node admin.js"); // 端口 3001
const api = await sandbox.exposePort(3000, { hostname, name: "api" });
const admin = await sandbox.exposePort(3001, { hostname, name: "admin" });
// 每个服务都会获得带有唯一 Token 的自专 URL:
// https://3000-abc123-random16chars01.yourdomain.com
// https://3001-abc123-random16chars02.yourdomain.com- HTTP/HTTPS 请求
- WebSocket 连接
- Server-Sent Events
- 所有 HTTP 方法 (GET, POST, PUT, DELETE 等)
- 请求和响应标头
- 原始 TCP/UDP 连接
- 自定义协议(必须包裹在 HTTP 中)
- 范围 1024-65535 之外的端口
- 端口 3000(在 SDK 内部使用)
预览 URL 支持 WebSocket 连接。当 WebSocket 升级请求命中暴露的端口时,路由层会自动处理连接握手。
// 从请求中提取主机名
const { hostname } = new URL(request.url);
// 启动一个 WebSocket 服务器
await sandbox.startProcess("bun run ws-server.ts 8080");
const { url } = await sandbox.exposePort(8080, { hostname });
// 客户端使用 WebSocket 协议建立连接
// 浏览器: new WebSocket('wss://8080-abc123-token123.yourdomain.com')
// 你的 Worker 会自动进行路由
export default {
async fetch(request, env) {
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
},
};对于你的 Worker 需要基于请求属性控制连接到哪个 sandbox 或端口的自定义路由场景,请参阅 Ports API 中的 wsConnect()。
内置安全性:
- 基于 Token 的访问 - 每个暴露的端口在 URL 中都会获得一个唯一的 Token(例如
https://8080-sandbox-abc123token456.yourdomain.com) - 生产环境中的 HTTPS - 所有流量都经过 TLS 加密。证书会为第一级通配符自动配置(
*.yourdomain.com)。如果你的 Worker 运行在子域名上,请参阅生产部署中的 TLS 说明。 - 不可预测的 URL - 自动生成的 Token 是随机生成的且难以猜测
- Token 碰撞防护 - 校验自定义 Token 以确保在每个 sandbox 内部的唯一性
添加应用层身份验证:
如需额外的安全性,请在应用程序内部实现身份验证:
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/data')
def get_data():
# 检查你自已的身份验证 Token
auth_token = request.headers.get('Authorization')
if auth_token != 'Bearer your-secret-token':
abort(401)
return {'data': 'protected'}这在 URL Token 之上添加了第二层安全性。
检查服务是否正在运行并监听:
// 1. 服务正在运行吗?
const processes = await sandbox.listProcesses();
// 2. 端口暴露了吗?
const ports = await sandbox.getExposedPorts();
// 3. 服务是否绑定到了 0.0.0.0(而不是 127.0.0.1)?
// 正确:
app.run((host = "0.0.0.0"), (port = 3000));
// 错误 (仅限 localhost):
app.run((host = "127.0.0.1"), (port = 3000));对于自定义域名问题,请参阅生产部署故障排除。
- 生产部署 - 为生产环境设置自定义域名
- 公开服务 - 用于公开端口的实用模式
- Ports API - 完整的 API 参考
- Tunnels API - 零配置的
*.trycloudflare.comURL 作为开发的替代方案 - 安全模型 - 安全最佳实践