跳转到内容
搜索文档

何时使用 Snippets 与 Workers

最后更新 查看 MarkdownAgent 设置

本指南帮助您决定在 Cloudflare 的全球网络上何时使用 Snippets 或 Workers。它提供了最佳实践、比较和实际用例,以帮助您为您的工作负载选择合适的产品。

什么是 Snippets?

Cloudflare Snippets 提供了一种在边缘修改 HTTP 请求和响应的快速、声明式方法,无需完整的计算平台。Snippets 扩展了 Cloudflare Rules,允许您编写基于 JavaScript 的逻辑,在请求到达源站之前修改请求,并在响应从上游返回后修改响应。

Snippets 使您能够:

  • 修改标头、验证 JWT 以及实施复杂的重写或重定向。
  • 重试到不同源站的失败请求并应用自定义缓存策略。
  • 顺序执行多个 Snippets,每个 Snippet 在将其传递给下一个之前修改请求或响应。

Snippets 包含在所有付费计划中,无需额外费用,是轻量级边缘逻辑的首选解决方案。

什么是 Workers?

相比之下,Cloudflare Workers 提供了一个全栈计算平台,专为需要状态、计算以及与 Cloudflare Developer Platform 集成的应用程序而设计。Workers 采用基于使用量的定价模型,并包含一个免费层。


选择合适的产品

Snippets 是在边缘进行快速、免费的请求和响应修改的理想选择。它们扩展了 Cloudflare Rules,无需额外的基础设施或外部解决方案。

何时使用 Snippets

  • 直接在 Cloudflare 网络上应用超快速的流量修改。
  • 将 Cloudflare Rules 扩展到内置操作之外,以获得更大的控制权。
  • 通过替换 VCL、EdgeWorkers 或本地逻辑来简化 CDN 迁移。
  • 修改标头、缓存响应和执行重定向。
  • 使用 JavaScript 将边缘逻辑集成到开发工作流程中。

Snippets 不适用于什么

主要功能


Snippets 与 Workers:功能对比

功能 Snippets Workers
基于请求属性(例如标头、地理位置和 cookies)执行脚本
在特定的 URL 路由上执行代码
修改 HTTP 请求/响应或提供不同的响应
动态添加删除重写标头
在边缘缓存资产
源服务器之间动态路由流量
对请求进行身份验证预签名 URL,运行 A/B 测试
使用 JavaScript 和 Web APIs 定义逻辑
执行计算密集型任务(例如,AI图像转换
存储持久数据(例如,KVDurable ObjectsD1
构建 API全栈应用程序
使用 TypeScript、Python、Rust 或其他编程语言
支持非 HTTP 协议
分析执行日志和跟踪性能指标
通过命令行界面 (CLI) 部署
逐步推出,回滚到以前的版本
使用 Smart Placement 优化执行

代码示例:常见 Snippets 模板

下面是展示 Snippets 实际应用的实用用例。您可以在 Examples 部分找到更多帮助您入门的模板。

修改 HTTP 标头

动态修改请求和响应标头。

export default {
	async fetch(request) {
		// Get the current timestamp
		const timestamp = Date.now();

		// Convert the timestamp to hexadecimal format
		const hexTimestamp = timestamp.toString(16);

		// Clone the request and add the custom header with HEX timestamp
		const modifiedRequest = new Request(request, {
			headers: new Headers(request.headers),
		});
		modifiedRequest.headers.set("X-Hex-Timestamp", hexTimestamp);

		// Pass the modified request to the origin
		const response = await fetch(modifiedRequest);

		// Clone the response so that it's no longer immutable
		const newResponse = new Response(response.body, response);

		// Add a custom header with a value to the response
		newResponse.headers.append(
			"x-snippets-hello",
			"Hello from Cloudflare Snippets",
		);

		// Delete headers from the response
		newResponse.headers.delete("x-header-to-delete");
		newResponse.headers.delete("x-header2-to-delete");

		// Adjust the value for an existing header in the response
		newResponse.headers.set("x-header-to-change", "NewValue");

		// Serve modified response to the visitor
		return newResponse;
	},
};

提供自定义维护页面

当您的源站进行计划内维护时,将流量路由到维护页面。

export default {
	async fetch(request) {
		return new Response(
			`
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>We'll Be Right Back!</title>
                <style> body { font-family: Arial, sans-serif; text-align: center; padding: 20px; } </style>
            </head>
            <body>
                <h1>We'll Be Right Back!</h1>
                <p>Our site is undergoing maintenance. Check back soon!</p>
            </body>
            </html>
        `,
			{ status: 503, headers: { "Content-Type": "text/html" } },
		);
	},
};

自定义缓存

在边缘执行编程缓存以减少源站负载。

const CACHE_DURATION = 30 * 24 * 60 * 60; // 30 days

export default {
	async fetch(request) {
		const cache = caches.default;
		const cacheKey = new Request(request.url, { method: "GET" });

		let response = await cache.match(cacheKey);
		if (!response) {
			response = await fetch(request);
			response = new Response(response.body, response);
			response.headers.set("Cache-Control", `s-maxage=${CACHE_DURATION}`);
			await cache.put(cacheKey, response.clone());
		}
		return response;
	},
};

基于国家/地区代码的重定向

根据访问者的地理位置进行重定向。

export default {
	async fetch(request) {
		const country = request.cf.country;
		const redirectMap = {
			US: "https://example.com/us",
			EU: "https://example.com/eu",
		};
		if (redirectMap[country])
			return Response.redirect(redirectMap[country], 301);
		return fetch(request);
	},
};

将 403 Forbidden 重定向到不同的页面

如果源站响应 403 Forbidden 错误代码,则将访问者重定向到不同的页面。

export default {
	async fetch(request) {
		// Send original request to the origin
		const response = await fetch(request);
		// Check if origin responded with 403 status code
		if (response.status == 403) {
			// If so, redirect to this URL
			const destinationURL = "https://example.com";
			// With this status code
			const statusCode = 301;
			// Serve redirect
			return Response.redirect(destinationURL, statusCode);
		}
		// Otherwise, serve origin's response
		else {
			return response;
		}
	},
};

重试到另一个源站

如果对原始请求的响应不是 200 OK 或重定向,则发送到另一个源站。

export default {
	async fetch(request) {
		// Send original request to the origin
		const response = await fetch(request);

		// If response is not 200 OK or a redirect, send to another origin
		if (!response.ok && !response.redirected) {
			// First, clone the original request to construct a new request
			const newRequest = new Request(request);
			// Add a header to identify a re-routed request at the new origin
			newRequest.headers.set("X-Rerouted", "1");
			// Clone the original URL
			const url = new URL(request.url);
			// Send request to a different origin / hostname
			url.hostname = "example.com";
			// Serve response to the new request from the origin
			return await fetch(url, newRequest);
		}

		// If response is 200 OK or a redirect, serve it
		return response;
	},
};

从 API 响应中删除字段

如果源站响应 JSON,则在向访问者返回响应之前删除敏感字段。

export default {
	async fetch(request) {
		// Send original request to the origin
		const response = await fetch(request);
		// Check if origin responded with JSON
		try {
			// Parse API response as JSON
			var api_response = response.json();
			// Specify the fields you want to delete. For example, to delete "botManagement" array from parsed JSON:
			delete api_response.botManagement;
			// Serve modified API response
			return Response.json(api_response);
		} catch (err) {
			// On failure, serve unmodified origin's response
			return response;
		}
	},
};

设置 CORS 标头

调整跨域资源共享 (CORS) 标头并处理预检请求。

// Define CORS headers
const corsHeaders = {
	"Access-Control-Allow-Origin": "*", // Replace * with your allowed origin(s)
	"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", // Adjust allowed methods as needed
	"Access-Control-Allow-Headers": "Content-Type, Authorization", // Adjust allowed headers as needed
	"Access-Control-Max-Age": "86400", // Adjust max age (in seconds) as needed
};

export default {
	async fetch(request) {
		// Make a copy of the request to modify its headers
		const modifiedRequest = new Request(request);

		// Handle preflight requests (OPTIONS)
		if (request.method === "OPTIONS") {
			return new Response(null, {
				headers: {
					...corsHeaders,
				},
				status: 200, // Respond with OK status for preflight requests
			});
		}

		// Pass the modified request through to the origin
		const response = await fetch(modifiedRequest);

		// Make a copy of the response to modify its headers
		const modifiedResponse = new Response(response.body, response);

		// Set CORS headers on the response
		Object.keys(corsHeaders).forEach((header) => {
			modifiedResponse.headers.set(header, corsHeaders[header]);
		});

		return modifiedResponse;
	},
};

重写 HTML 页面上的链接

替换过时的链接而无需在源站进行更改。

export default {
	async fetch(request) {
		// Define the old hostname here.
		const OLD_URL = "oldsite.com";
		// Then add your new hostname that should replace the old one.
		const NEW_URL = "newsite.com";

		class AttributeRewriter {
			constructor(attributeName) {
				this.attributeName = attributeName;
			}
			element(element) {
				const attribute = element.getAttribute(this.attributeName);
				if (attribute) {
					element.setAttribute(
						this.attributeName,
						attribute.replace(OLD_URL, NEW_URL),
					);
				}
			}
		}

		const rewriter = new HTMLRewriter()
			.on("a", new AttributeRewriter("href"))
			.on("img", new AttributeRewriter("src"));

		const res = await fetch(request);
		const contentType = res.headers.get("Content-Type");

		// If the response is HTML, it can be transformed with
		// HTMLRewriter -- otherwise, it should pass through
		if (contentType.startsWith("text/html")) {
			return rewriter.transform(res);
		} else {
			return res;
		}
	},
};

减慢请求

定义当传入请求匹配您的规则时使用的延迟。对于可疑请求很有用。

export default {
	async fetch(request) {
		// Define delay
		const delay_in_seconds = 5;
		// Introduce a delay
		await new Promise((resolve) =>
			setTimeout(resolve, delay_in_seconds * 1000),
		); // Set delay in milliseconds

		// Pass the request to the origin
		const response = await fetch(request);
		return response;
	},
};

将 Snippets 和 Workers 结合使用

虽然 Snippets 和 Workers 具有不同的功能,但它们可以协同工作以处理复杂的流量工作流程。

为避免冲突,Snippets 和 Workers 应该在单独的请求路径上运行,而不是在相同的 URL 上运行。让它们在其逻辑中作为子请求提取各自的 URL,以确保平滑的执行和缓存行为。

示例 1:在 Snippets 和 Workers 之间传递数据

Snippets 可以在传入请求到达 Worker 之前修改它们,而 Workers 可以读取这些修改,执行额外的转换,并将它们传递到下游。

Snippet:添加自定义标头

export default {
	async fetch(request) {
		// Get the current timestamp
		const timestamp = Date.now();
		const hexTimestamp = timestamp.toString(16);

		// Clone request and add a custom header
		const modifiedRequest = new Request(request, {
			headers: new Headers(request.headers),
		});
		modifiedRequest.headers.set("X-Hex-Timestamp", hexTimestamp);

		console.log(`X-Hex-Timestamp: ${hexTimestamp}`);

		// Pass modified request to origin
		return fetch(modifiedRequest);
	},
};

Worker:读取标头并将其添加到响应中

export default {
	async fetch(request) {
		const response = await fetch("https://{snippets_url}", request); // Ensure {snippets_url} points to the endpoint modified by Snippets
		const newResponse = new Response(response.body, response);

		let hexTimestamp = request.headers.get("X-Hex-Timestamp") || "null";
		console.log(hexTimestamp);

		newResponse.headers.set("X-Hex-Timestamp", hexTimestamp);
		return newResponse;
	},
};

结果: Snippet 设置 X-Hex-Timestamp,Worker 读取它并将其转发到源站。

示例 2:使用 Snippets 缓存 Worker 响应

Worker 执行计算密集型处理(例如图像转换),而 Snippet 提供缓存结果以避免不必要的 Worker 执行。这在不希望于缓存之前运行 Workers 的情况下会很有帮助。

Worker:转换并缓存响应

export default {
	async fetch(request) {
		const url = new URL(request.url);
		url.hostname = "origin.example.com"; // Ensure this hostname points to the origin where the resource is hosted

		const newRequest = new Request(url, request);
		const customKey = `https://${url.hostname}${url.pathname}`; // This custom cache key should be the same in both Worker and Snippet configuration for cache to work

		// Fetch and modify response
		const response = await fetch(newRequest);
		const newResponse = new Response(response.body, response);

		// Cache the transformed response
		const cache = caches.default;
		const cachedResponse = newResponse.clone();
		cachedResponse.headers.set("X-Cached-In-Workers", "true");
		await cache.put(customKey, cachedResponse);

		newResponse.headers.set("X-Retrieved-From-Workers", "true");
		return newResponse;
	},
};

Snippet:提供缓存响应或转发到 Worker

export default {
	async fetch(request) {
		const url = new URL(request.url);
		url.hostname = "origin.example.com"; // Ensure this hostname points to the origin where the resource is hosted
		const cacheKey = `https://${url.hostname}${url.pathname}`; // This custom cache key should be the same in both Worker and Snippet configuration for cache to work

		// Access cache
		const cache = caches.default;
		let response = await cache.match(cacheKey);

		if (!response) {
			console.log(`Cache miss for: ${cacheKey}. Fetching from Worker...`);
			url.hostname = "worker.example.com"; // Ensure this hostname points to the Workers route
			response = await fetch(new Request(url, request));

			// Cache the response for future use
			response = new Response(response.body, response);
			response.headers.set("Cache-Control", `s-maxage=3600`);
			response.headers.set("x-snippets-cache", "stored");
		} else {
			console.log(`Cache hit for: ${cacheKey}`);
			response = new Response(response.body, response);
			response.headers.set("x-snippets-cache", "hit");
		}

		return response;
	},
};

结果: 转换后的响应 (X-Cached-In-Workers: true) 从缓存中提供,避免了冗余的 Worker 执行(不存在 X-Retrieved-From-Workers)。当缓存过期时,Snippet 会获取新版本。


在 Snippets 和 Workers 之间迁移

Snippets 和 Workers 共享相同的 Workers runtime,这意味着不依赖于绑定、持久存储或高级执行功能的 JavaScript 代码可以在它们之间无缝迁移。

何时将工作负载迁移到 Snippets

如果 Worker 满足以下条件,您应该考虑将其迁移到 Snippets:

  • 仅修改标头、重定向、缓存规则或源站路由。
  • 不需要绑定、持久存储或外部集成。
  • 是一个具有简单逻辑的轻量级 JavaScript 函数。
  • 需要在 Pro、Business 或 Enterprise 计划上免费运行无限次。

迁移到 Snippets 允许您:

  • 利用 Ruleset Engine 进行高级请求匹配。
  • 消除基于使用量的计费——Snippets 在所有付费计划中均免费包含。
  • 通过将流量修改直接集成到 Cloudflare Rules 中来简化管理。

何时将工作负载迁移到 Workers

如果您的逻辑满足以下条件,您应该从 Snippets 迁移到 Workers:

如果您的 Snippet 达到了执行时间、内存或功能的限制,过渡到 Workers 可以确保您的逻辑能够无限制地扩展。


结论

Cloudflare Snippets 为快速、声明式的边缘流量逻辑提供了生产就绪的解决方案,弥合了 Cloudflare RulesDeveloper Platform 之间的差距。

Snippets 和 Workers 解决不同的问题:

  • 使用 Snippets 在边缘进行快速、轻量级的流量修改,包括标头重写、缓存、重定向、源站路由、自定义响应、A/B 测试和身份验证。
  • Workers 专为高级计算、持久状态和全栈应用程序而构建。

这篇文档对您有帮助吗?