INTEGRITY Документация

Когда использовать Snippets, а когда Workers

Это руководство поможет вам понять, когда использовать Snippets, а когда Workers в глобальной сети Cloudflare. В нем приведены рекомендации, сравнения и примеры из практики, которые помогут выбрать подходящий продукт для вашей задачи.

Что такое Snippets?

Cloudflare Snippets предоставляют быстрый декларативный способ изменять запросы и ответы HTTP на edge, не требуя полноценной вычислительной платформы. Snippets расширяют Cloudflare Rules позволяя писать логику на JavaScript, которая изменяет запросы до того, как они достигнут источника, и ответы после того, как они вернутся от вышестоящего сервера.

С помощью Snippets вы можете:

Snippets включены без дополнительной платы в все платные тарифные планы, что делает их предпочтительным решением для лёгкой логики на периферии сети.

Что такое Workers?

В отличие от этого, Cloudflare Workers предоставляют full-stack платформу для вычислений, разработанную для приложений, которым необходимы состояние, вычисления и интеграции с Developer Platform. Workers работают на основе модель ценообразования на основе использования и включают бесплатный уровень.


Выбор подходящего продукта

Snippets отлично подходят для быстрых и бесплатных изменений запросов и ответов на периферии сети. Они расширяют Cloudflare Rules без необходимости в дополнительной инфраструктуре или внешних решениях.

Когда использовать Snippets

Для чего Snippets не предназначены

Ключевые функции


Snippets vs Workers: сравнение функций

Возможность Snippets Workers
Выполнение скриптов на основе атрибутов запроса (например, заголовков, геолокации и файлов cookie)
Выполнение кода для определённого маршрута URL
Изменяйте HTTP запросы/ответы или отдавайте другой ответ
Add, удалить, или перезапись заголовки динамически
Кеш ресурсы на периферийных серверах
Динамически распределяйте трафик между исходные серверы
Аутентификация запросов, предварительная подпись URL-адресов выполните A/B тестирование
Определяйте логику с помощью JavaScript и Web API
Выполнение ресурсоёмких задач (например, AI, преобразования изображений)
Хранение постоянных данных (например, KV, Durable Objects, а также D1)
Создайте API и full-stack приложения
Используйте TypeScript, Python, Rust или другой язык программирования языки
Поддержка не-HTTP протоколы
Анализ выполнения журналы и отслеживать показатели производительности
Развёртывание через интерфейс командной строки (CLI)
Постепенное развёртывание, откат к предыдущей версии
Оптимизация выполнения с помощью Smart Placement

Примеры кода: шаблоны Common Snippets

Ниже приведены практические примеры использования Snippets. Больше шаблонов для начала работы можно найти в Примеры раздел.

Изменение заголовков 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

Настраивает Cross-Origin Resource Sharing (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;
	},
};

Заменяет устаревшие ссылки без необходимости вносить изменения на источнике.

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. Кеширование ответов Worker с помощью Snippets

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, то есть код JavaScript, не использующий привязки (bindings), постоянное хранилище или расширенные функции выполнения, можно переносить между ними без изменений.

Когда переносить нагрузки на Snippets

Рассмотрите миграцию Worker на Snippets, если он:

Миграция на Snippets позволяет:

Когда переносить нагрузки на Workers

Перейдите со Snippets на Workers, если ваша логика:

Если ваш Snippet достигает ограничений по времени выполнения, памяти или функциональности, переход на Workers обеспечит масштабируемость вашей логики без ограничений.


Заключение

Cloudflare Snippets предлагают готовое к продакшену решение для быстрой декларативной логики трафика на edge, устраняя разрыв между Cloudflare Rules и Developer Platform.

Snippets и Workers решают разные задачи: