跳转到内容
搜索文档

预览 URL

最后更新 查看 MarkdownAgent 设置

快速部署

对于快速预览部署,我们推荐使用 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/users

Cloudflare Tunnel 支持目前具有以下限制:

  • 无法控制生成的 URL。
  • 除随机生成的 URL 外,没有身份验证机制。
  • 每个 URL 都会在 sandbox 上使用一个额外的 cloudflared 进程。

请参阅 tunnels API 参考 以了解完整的 API 和功能集。

生产使用、稳定 URL 与自定义域名

对于生产环境的使用,我们推荐使用 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}/

URL 格式

生产环境: 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 类型

自动生成的 Token (默认)

当没有指定自定义 Token 时,将自动生成一个随机的 16 字符 Token:

const exposed = await sandbox.exposePort(8000, { hostname });
// https://8000-sandbox-id-abc123random4567.yourdomain.com

取消暴露并重新暴露端口时,带有自动生成 Token 的 URL 会发生改变。

用于稳定 URL 的自定义 Token

对于生产环境部署或共享的 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 的集成测试

ID 大小写敏感性 (ID Case Sensitivity)

预览 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

请求路由 (Request Routing)

你必须首先在 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) → 你的服务。

多个端口 (Multiple Ports)

同时暴露多个服务:

// 从请求中提取主机名
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

支持的功能 (What Works)

  • HTTP/HTTPS 请求
  • WebSocket 连接
  • Server-Sent Events
  • 所有 HTTP 方法 (GET, POST, PUT, DELETE 等)
  • 请求和响应标头

不支持的功能 (What Does Not Work)

  • 原始 TCP/UDP 连接
  • 自定义协议(必须包裹在 HTTP 中)
  • 范围 1024-65535 之外的端口
  • 端口 3000(在 SDK 内部使用)

WebSocket 支持

预览 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 之上添加了第二层安全性。

故障排除

URL 无法访问

检查服务是否正在运行并监听:

// 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));

生产环境错误

对于自定义域名问题,请参阅生产部署故障排除

本地开发

相关资源

这篇文档对您有帮助吗?