Skip to content

Instantly share code, notes, and snippets.

@klafosse
Last active December 15, 2022 13:11
// Configure this
const swAdnToken = "XXXXXXX"; // Set the ADN token provided by Botify
const swAllowedUrls = []; // Set something like https://mysubdomain.mydomain.com/mypages (in lower case) to restrict SW to urls starting with these values
const swRewriteOrigin = ""; // Set something like https://www.mydomain.com
const swDomain = "XXXXXXXX.sw.adn.cloud"; // Set the SpeedWorkers domain name provided by Botify (no "https://" or trailing "/" here, only the domain)
const swPort = 443;
const swProtocol = "https";
const swSslProtocols = ["TLSv1", "TLSv1.1", "TLSv1.2"];
const swReadTimeoutSec = 15;
const swKeepaliveTimeoutSec = 60;
const swCustomHeaders = {}; // Use this format: "user-agent": [ { "value": "ExampleCustomUserAgent/1.X.0"}], ... // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#request-event-fields-request-headers
// Don't touch this
const swAgent = "CloudFrontInterceptor/1.1";
const swIgnored = ["/css/", "/fonts/", "/images/", "/js/", "/scss/", ".bmp", ".css", ".csv", ".doc", ".docx", ".eot", ".gif", ".ico", ".ief", ".jpe", ".jpeg", ".jpg", ".js", ".json", ".less", ".map", ".m1v", ".mov", ".mp2", ".mp3", ".mp4", ".mpa", ".mpe", ".mpeg", ".mpg", ".otf", ".ott", ".ogv", ".pbm", ".pdf", ".pgm", ".png", ".ppm", ".pnm", ".ppt", ".pps", ".ps", ".qt", ".ras", ".rdf", ".rgb", ".rss", ".svg", ".swf", ".tiff", ".tif", ".tsv", ".ttf", ".txt", ".vcf", ".wav", ".webm", ".woff", ".woff2", ".xbm", ".xlm", ".xls", ".xml", ".xpdl", ".xpm", ".xwd"]; // in lower case
// Set to true if you want debug logs
const debug = true
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
// Ignore web sockets
// Ignore web sockets
if (request.headers["Upgrade"] === "websocket") {
log("websocket, ignoring request")
callback(null, request);
return;
}
// Only filter GETs and HEADs
const requestMethod = request.method
if (requestMethod !== "GET" && requestMethod !== "HEAD") {
log("not a GET/HEAD method, ignoring");
callback(null, request);
return;
}
// We need a way to know if the request comes from a bot or a user
// If the request doesn't come from a bot, let it reach the fallback origin without SW
if (
request.headers["x-sw-request-type"] === undefined
|| (request.headers["x-sw-request-type"] !== undefined && !request.headers["x-sw-request-type"][0].value.startsWith("bot"))
)
{
delete request.headers["x-sw-request-type"];
log("not a bot (or missing x-sw-request-type header), ignoring");
callback(null, request);
return;
}
delete request.headers["x-sw-request-type"];
// We need the host from the Viewer Request Lambda, or at least a rewrite origin value!
if (request.headers["x-sw-host"] === undefined && (swRewriteOrigin === undefined || swRewriteOrigin === ""))
{
log("missing x-sw-host, no viewer request lambda?");
callback(null, request);
return;
}
// Retrieve original host set by the viewer request lambda
let originalHost = "";
if (request.headers["x-sw-host"] !== undefined) {
originalHost = request.headers["x-sw-host"][0].value;
delete request.headers["x-sw-host"];
}
// Rebuild the complete uri
let url = "https://" + originalHost;
if (swRewriteOrigin !== undefined && swRewriteOrigin !== "") {
url = swRewriteOrigin;
}
url += request.uri;
if (request.querystring !== "")
{
url += "?" + request.querystring;
}
// Ignore requests which url doesn't start with one of the url SW is enabled
if (!isUrlAllowed(url))
{
log("url (" + url + ") not allowed, ignoring");
callback(null, request);
return;
}
// Ignore resources that are not handled by SW
if (ignorePath(url))
{
log("path " + url + " ignored");
callback(null, request);
return;
}
// The request comes from a bot, send it to SW
log("calling SW to get " + url);
// Restore user-agent header (to replace Amazon CloudFront user-agent...)
// If you don't use the viewer request, please make sure you whitelisted the User-Agent header in the CloudFront conf
if (request.headers["x-sw-user-agent"] !== undefined) {
request.headers['user-agent'] = [{ key: 'User-Agent', value: request.headers["x-sw-user-agent"][0].value }];
delete request.headers["x-sw-user-agent"];
}
request.headers["host"] = [{ key: "host", value: swDomain }];
request.origin = {
custom: {
domainName: swDomain,
port: swPort,
protocol: swProtocol,
sslProtocols: swSslProtocols,
readTimeout: swReadTimeoutSec,
keepaliveTimeout: swKeepaliveTimeoutSec,
customHeaders: swCustomHeaders
}
};
request.origin.custom.customHeaders["x-sw-agent"] = [{ key: "x-sw-agent", value: swAgent }];
request.origin.custom.customHeaders["x-sw-client-ip"] = [{ key: "x-sw-client-ip", value: request.clientIp }];
request.origin.custom.customHeaders["x-sw-uri"] = [{ key: "x-sw-uri", value: url }];
request.origin.custom.customHeaders["x-sw-adn-token"] = [{ key: "x-sw-adn-token", value: swAdnToken }];
callback(null, request);
};
function log(msg) {
if (debug) {
console.log(msg)
}
}
// Returns true if the url contains a path sw should ignore
function ignorePath(url)
{
for (let i = 0; i < swIgnored.length; i++)
{
if (url.toLowerCase().includes(swIgnored[i]))
return true;
}
return false;
}
// Returns true if the url belongs to (starts with) an allowed url
function isUrlAllowed(url) {
if (swAllowedUrls.length === 0)
return true;
for (let i = 0; i < swAllowedUrls.length; i++)
{
if (url.toLowerCase().startsWith(swAllowedUrls[i]))
return true;
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment