Created
February 23, 2023 18:47
-
-
Save raiderrobert/7ad45541b066710c56f8d5f04d04258c to your computer and use it in GitHub Desktop.
Cloudflare Worker for Traffic Splitting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
getAssetFromKV, | |
mapRequestToAsset, | |
} from "@cloudflare/kv-asset-handler"; | |
/** | |
* The DEBUG flag will do two things that help during development: | |
* 1. we will skip caching on the edge, which makes it easier to | |
* debug. | |
* 2. we will return an error message on exception in your Response rather | |
* than the default 404.html page. | |
*/ | |
const DEBUG = true; | |
addEventListener("fetch", (event) => { | |
const { request } = event; | |
const url = new URL(request.url); | |
const { hostname } = url; | |
const requestUserAgent = (request.headers.get('User-Agent') || '').toLowerCase(); | |
const pathName = url.pathname.toLowerCase(); | |
const ext = pathName.substring(pathName.lastIndexOf('.') || pathName.length); | |
try { | |
if ( | |
BOT_AGENTS.some(e => requestUserAgent.includes(e)) | |
&& !isOneOfThem(IGNORE_EXTENSIONS, ext) | |
) { | |
event.respondWith(prerenderRequest(request)) | |
} else { | |
event.respondWith(handleEvent(event)); | |
} | |
} catch (e) { | |
event.respondWith( | |
new Response("Internal Error", { status: 500 }) | |
); | |
} | |
}); | |
async function handleEvent(event) { | |
const { request } = event; | |
const url = new URL(request.url); | |
let options = {}; | |
/** | |
* You can add custom logic to how we fetch your assets | |
* by configuring the function `mapRequestToAsset` | |
*/ | |
options.mapRequestToAsset = (req) => { | |
// First let's apply the default handler, which we imported from | |
// '@cloudflare/kv-asset-handler' at the top of the file. We do | |
// this because the default handler already has logic to detect | |
// paths that should map to HTML files, for which it appends | |
// `/index.html` to the path. | |
req = mapRequestToAsset(req); | |
// Now we can detect if the default handler decided to map to | |
// index.html in some specific directory. | |
if (req.url.endsWith("/index.html")) { | |
// Indeed. Let's change it to instead map to the root `/index.html`. | |
// This avoids the need to do a redundant lookup that we know will | |
// fail. | |
return new Request( | |
`${new URL(req.url).origin}/index.html`, | |
req | |
); | |
} else { | |
// The default handler decided this is not an HTML page. It's probably | |
// an image, CSS, or JS file. Leave it as-is. | |
return req; | |
} | |
}; | |
try { | |
if (DEBUG) { | |
// customize caching | |
options.cacheControl = { | |
bypassCache: true, | |
}; | |
} | |
return await getAssetFromKV(event, options); | |
} catch (e) { | |
// if an error is thrown try to serve the asset at 404.html | |
if (!DEBUG) { | |
try { | |
let notFoundResponse = await getAssetFromKV(event, { | |
mapRequestToAsset: (req) => | |
new Request(`${new URL(req.url).origin}/404.html`, req), | |
}); | |
return new Response(notFoundResponse.body, { | |
...notFoundResponse, | |
status: 404, | |
}); | |
} catch (e) {} | |
} | |
return new Response(e.message || e.toString(), { status: 500 }); | |
} | |
} | |
/** | |
* Basic settings | |
*/ | |
/** | |
* Advanced settings | |
*/ | |
// These are the user agents that the worker will look for to | |
// initiate prerendering of the site. | |
const BOT_AGENTS = [ | |
'googlebot', | |
'yahoo! slurp', | |
'bingbot', | |
'yandex', | |
'baiduspider', | |
'facebookexternalhit', | |
'twitterbot', | |
'rogerbot', | |
'linkedinbot', | |
'embedly', | |
'quora link preview', | |
'showyoubot', | |
'outbrain', | |
'pinterest/0.', | |
'developers.google.com/+/web/snippet', | |
'slackbot', | |
'vkshare', | |
'w3c_validator', | |
'redditbot', | |
'applebot', | |
'whatsapp', | |
'flipboard', | |
'tumblr', | |
'bitlybot', | |
'skypeuripreview', | |
'nuzzel', | |
'discordbot', | |
'google page speed', | |
'qwantify', | |
'pinterestbot', | |
'bitrix link preview', | |
'xing-contenttabreceiver', | |
'chrome-lighthouse' | |
]; | |
// These are the extensions that the worker will skip prerendering | |
// even if any other conditions pass. | |
const IGNORE_EXTENSIONS = [ | |
'.js', | |
'.css', | |
'.xml', | |
'.less', | |
'.png', | |
'.jpg', | |
'.jpeg', | |
'.gif', | |
'.pdf', | |
'.doc', | |
'.txt', | |
'.ico', | |
'.rss', | |
'.zip', | |
'.mp3', | |
'.rar', | |
'.exe', | |
'.wmv', | |
'.doc', | |
'.avi', | |
'.ppt', | |
'.mpg', | |
'.mpeg', | |
'.tif', | |
'.wav', | |
'.mov', | |
'.psd', | |
'.ai', | |
'.xls', | |
'.mp4', | |
'.m4a', | |
'.swf', | |
'.dat', | |
'.dmg', | |
'.iso', | |
'.flv', | |
'.m4v', | |
'.torrent', | |
'.woff', | |
'.ttf', | |
'.svg', | |
'.webmanifest' | |
]; | |
/** | |
* Helper function to check if an array contains an element or not. | |
* | |
* @param {string[]} array - The array to check. | |
* @param {string} element - The element to check if the array contains. | |
* @returns {boolean} | |
*/ | |
function isOneOfThem(array, element) { | |
return array.some(e => e === element); | |
} | |
// The Prerender.io API key | |
// Load this from .env file don't hard code it; just an example | |
const API_KEY = 'FOO'; | |
/** | |
* Function to request the prerendered version of a request. | |
* | |
* @param {Request} request - The request received by CloudFlare | |
* @returns {Promise<Response>} | |
*/ | |
async function prerenderRequest(request) { | |
const { url } = request; | |
const prerenderUrl = `https://service.prerender.io/${url}`; | |
const headers = new Headers({ | |
'X-Prerender-Token': API_KEY | |
}); | |
const prerenderRequest = new Request(prerenderUrl, { | |
headers | |
}); | |
const response = await fetch(prerenderRequest); | |
const results = await response.text(); | |
const init = { | |
headers: { | |
"content-type": "text/html;charset=UTF-8", | |
}, | |
} | |
return new Response(results, init) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"private": true, | |
"name": "worker", | |
"version": "1.0.0", | |
"description": "A template for kick starting a Cloudflare Workers project", | |
"main": "index.js", | |
"author": "Robert Roskam <raiderrobert@gmail.com>", | |
"license": "MIT", | |
"dependencies": { | |
"@cloudflare/kv-asset-handler": "^0.0.5" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment