From 9228c5ee4e4a94fa202804275b6a511d5a0cf0f8 Mon Sep 17 00:00:00 2001 From: lsemenenko Date: Sat, 1 Mar 2025 01:40:12 +0000 Subject: [PATCH] Add index.js --- index.js | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..fe35f9f --- /dev/null +++ b/index.js @@ -0,0 +1,136 @@ +/** + * Cloudflare Worker to cache a URL on all US Edge servers + * + * This worker: + * 1. Accepts a target URL via query parameter or POST body + * 2. Fetches the URL using Cloudflare's fetch API + * 3. Forces caching by setting appropriate cache headers + * 4. Can be configured to cache on specific US regions or all of them + */ + +// Define US Cloudflare regions (These are the major US colo locations) +const US_REGIONS = [ + "IAD", // Washington DC (Northern Virginia) + "EWR", // Newark, NJ + "ATL", // Atlanta, GA + "MIA", // Miami, FL + "ORD", // Chicago, IL + "DFW", // Dallas, TX + "DEN", // Denver, CO + "SEA", // Seattle, WA + "LAX", // Los Angeles, CA + "SJC", // San Jose, CA + "PHX" // Phoenix, AZ +]; + +export default { + async fetch(request, env, ctx) { + // Create the response headers with CORS support + const headers = new Headers({ + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + "Content-Type": "application/json" + }); + + // Handle OPTIONS requests (pre-flight for CORS) + if (request.method === "OPTIONS") { + return new Response(null, { headers }); + } + + // Get the target URL from query params or POST body + let targetUrl; + if (request.method === "POST") { + try { + const body = await request.json(); + targetUrl = body.url; + } catch (error) { + return new Response( + JSON.stringify({ error: "Invalid JSON body" }), + { headers, status: 400 } + ); + } + } else { + const url = new URL(request.url); + targetUrl = url.searchParams.get("url"); + } + + // Validate the target URL + if (!targetUrl) { + return new Response( + JSON.stringify({ error: "Missing 'url' parameter" }), + { headers, status: 400 } + ); + } + + try { + new URL(targetUrl); // Validate URL format + } catch (error) { + return new Response( + JSON.stringify({ error: "Invalid URL format" }), + { headers, status: 400 } + ); + } + + // Process all regions in parallel + const results = await Promise.allSettled(US_REGIONS.map(async (region) => { + return await cacheInRegion(targetUrl, region); + })); + + // Compile results + const response = { + url: targetUrl, + cached: true, + regionResults: results.map((result, index) => ({ + region: US_REGIONS[index], + success: result.status === "fulfilled", + status: result.status === "fulfilled" ? result.value.status : null, + error: result.status === "rejected" ? result.reason.message : null + })) + }; + + return new Response(JSON.stringify(response, null, 2), { headers }); + } +}; + +/** + * Attempts to cache the URL in a specific Cloudflare region + * + * @param {string} url - The URL to cache + * @param {string} region - The Cloudflare region code + * @returns {Promise} - The fetch response details + */ +async function cacheInRegion(url, region) { + // Create fetch request with cache control headers + const cacheRequest = new Request(url, { + headers: { + // CF-specific header to route to a specific region + "CF-Worker-Request-Region": region, + // Headers to encourage caching + "Cache-Control": "public, max-age=86400", + // Tell Cloudflare that we're a cache warmer + "User-Agent": "Cloudflare-Cache-Warmer/1.0" + }, + cf: { + // Force caching even if origin has no-store + cacheEverything: true, + // Cache for 1 day + cacheTtl: 86400, + // Attempt to resolve through the specified region + resolveOverride: region + } + }); + + // Perform the fetch + const response = await fetch(cacheRequest); + + return { + status: response.status, + cached: response.headers.get("CF-Cache-Status") === "HIT" || + response.headers.get("CF-Cache-Status") === "MISS", // MISS on first cache, but still stored + headers: { + "CF-Cache-Status": response.headers.get("CF-Cache-Status"), + "CF-Ray": response.headers.get("CF-Ray") + } + }; +} \ No newline at end of file