跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

在独立 hostname 上提供预览

最后更新 查看 MarkdownAgent 设置

从独立 hostname 提供每个 sandbox 预览,例如 ada.example-previews.com。预览中的代码运行在访问者的浏览器中,每个预览 hostname 都是独立 origin。一个预览中的代码无法读取你的应用页面或另一个预览的存储。路径会原样到达 server,因此应用不需要 base path。

.example-previews.com
A wildcard DNS record and the route *.example-previews.com/* send every preview hostname to your Worker.
ada
Your Worker reads the sandbox name and calls getByName("ada"). A name that is not a valid DNS label gets a 404.
/app/
The path reaches the web server in the container unchanged, with the preview hostname in the Host header.

前提条件

  • Durable Object,其 fetch() handler 将请求转发到其 container 中的 Web server。要构建一个,请参阅 预览 Web 应用。
  • Cloudflare 上用于预览的域名,例如 example-previews.com,并由 Cloudflare 管理其 DNS。请使用你的应用未使用的域名。

将预览 hostname 路由到 Worker

  1. 在预览域名的 DNS 设置中,添加已代理的 通配记录。例如,添加名称为 *、内容为 100:: 的 AAAA 记录,并将代理状态设为 Proxied。

    该记录让 Cloudflare 接收每个预览 hostname 的请求。Worker 路由处理这些请求,因此 Cloudflare 永远不会联系记录中的地址。

  2. 在 wrangler.jsonc 中,将预览域名添加为变量,并将其子域名路由到 Worker。将 example-previews.com 替换为你的预览域名:

    {
    	"vars": {
    		"PREVIEW_DOMAIN": "example-previews.com",
    	},
    	"routes": [
    		{
    			"pattern": "*.example-previews.com/*",
    			"zone_name": "example-previews.com",
    		},
    	],
    	"workers_dev": true,
    }
    workers_dev = true
    
    [vars]
    PREVIEW_DOMAIN = "example-previews.com"
    
    [[routes]]
    pattern = "*.example-previews.com/*"
    zone_name = "example-previews.com"

    设置 routes 时,Wrangler 会关闭 workers.dev URL。workers_dev: true 会保留它,用于管理预览的请求。

  3. 为新变量生成类型:

    npx wrangler types
  4. 在 Worker 的 fetch() handler 中,将每个预览 hostname 发送到同名 Durable Object:

    src/index.jsjs
    export default {
    	async fetch(request, env) {
    		const url = new URL(request.url);
    		const suffix = `.${env.PREVIEW_DOMAIN}`;
    
    		// A preview hostname reaches only the sandbox with the same name.
    		if (url.hostname.endsWith(suffix)) {
    			const name = url.hostname.slice(0, -suffix.length);
    
    			// Accept only sandbox names that are valid DNS labels
    			if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name)) {
    				return new Response("Not found", { status: 404 });
    			}
    
    			// Forward with the `http:` scheme, because `getTcpPort().fetch()`
    			// does not accept `https:` URLs
    			url.protocol = "http:";
    			return env.MY_CONTAINER.getByName(name).fetch(new Request(url, request));
    		}
    
    		// Other hostnames manage previews.
    		return new Response("Not found", { status: 404 });
    	},
    };
    src/index.tsts
    export default {
    	async fetch(request: Request, env: Env): Promise<Response> {
    		const url = new URL(request.url);
    		const suffix = `.${env.PREVIEW_DOMAIN}`;
    
    		// A preview hostname reaches only the sandbox with the same name.
    		if (url.hostname.endsWith(suffix)) {
    			const name = url.hostname.slice(0, -suffix.length);
    
    			// Accept only sandbox names that are valid DNS labels
    			if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name)) {
    				return new Response("Not found", { status: 404 });
    			}
    
    			// Forward with the `http:` scheme, because `getTcpPort().fetch()`
    			// does not accept `https:` URLs
    			url.protocol = "http:";
    			return env.MY_CONTAINER.getByName(name).fetch(new Request(url, request));
    		}
    
    		// Other hostnames manage previews.
    		return new Response("Not found", { status: 404 });
    	},
    } satisfies ExportedHandler<Env>;

    server 会收到完整路径,以及 Host header 中的预览 hostname。

    发往预览 hostname 的每个请求都会进入预览。将管理预览的路由(例如停止预览)放在其他 hostname 上,以免预览中的代码从其自身 origin 调用它们。在预览 hostname 上认证预览访问者,不要将应用 session cookie 发送到那里。

  5. 部署你的 Worker:

    npx wrangler deploy
  6. 在浏览器中打开名为 ada 的预览,并在其浏览器控制台中存储一个值。将 example-previews.com 替换为你的预览域名:

    https://ada.example-previews.com/
    localStorage.setItem("name", "ada");
  7. 打开名为 grace 的预览,并在其浏览器控制台中读取该值:

    https://grace.example-previews.com/
    localStorage.getItem("name");

    控制台会打印 null。每个预览 hostname 都是独立 origin,因此 grace 无法读取 ada 的存储。

在子域名下提供预览

要在你已使用的域名的子域名下提供预览(例如 previews.example.com),请按该子域名为通配记录和路由命名:

  1. 在 example.com 的 DNS 设置中,添加名称为 *.previews、内容为 100:: 的已代理 AAAA 记录。名为 * 的 通配记录 在 previews.example.com 有自己的记录时不适用于其下方,并且还会匹配 example.com 的每个其他未使用子域名。名为 *.previews 的记录只匹配预览 hostname。

  2. 在 wrangler.jsonc 中,将 PREVIEW_DOMAIN 设为该子域名,并将其子域名路由到 Worker:

    {
    	"vars": {
    		"PREVIEW_DOMAIN": "previews.example.com",
    	},
    	"routes": [
    		{
    			"pattern": "*.previews.example.com/*",
    			"zone_name": "example.com",
    		},
    	],
    	"workers_dev": true,
    }
    workers_dev = true
    
    [vars]
    PREVIEW_DOMAIN = "previews.example.com"
    
    [[routes]]
    pattern = "*.previews.example.com/*"
    zone_name = "example.com"

应用也使用的域名下的预览会与应用共享 cookie。浏览器会将设置了 Domain=example.com 的 cookie 发送到每个子域名,包括 previews.example.com 下的每个预览。

一个预览中的代码可以设置 Domain=example-previews.com 的 cookie,然后每个其他预览都会收到该 cookie。在预览 hostname 上设置 cookie 时不要带 Domain 属性,也不要因为 cookie 到达预览 hostname 就信任它。

故障排除

container 中的 server 使用 HTTPS:从 container 提供明文 HTTP。Cloudflare 处理预览 hostname 的 TLS,你的 Worker 通过 HTTP 将每个请求转发到 container。只接受 HTTPS 的 server(例如使用 Node.js 中的 https.createServer() 或框架的 HTTPS 标志启动的 server)无法处理这些请求。请改为用 HTTP 启动 server。

相关资源

这篇文档对您有帮助吗?