This is the SpeedWorkers origin-request Lambda@Edge:
// SpeedWorkers origin-request Lambda@Edge
//
// The cache behavior targets an origin group (primary = SpeedWorkers,
// secondary = your true origin). This lambda runs on cache misses and decides
// whether the request may reach SpeedWorkers:
// - bot GET/HEAD page request => leave the origin group untouched, so
// SpeedWorkers serves it and CloudFront fails over to your origin on error
// - anything else (users, POST/PUT/DELETE/..., websockets, ignored paths)
// => rewrite the origin to your true origin, bypassing SpeedWorkers
//
// The behavior itself should only allow GET/HEAD (CloudFront doesn't offer
// the all-methods option on origin-group behaviors); the non-GET/HEAD
// rewrite below is defense in depth.
//
// Requirements: a currently supported Node.js runtime, deployed in us-east-1,
// timeout 1 second (the code only manipulates headers).
// Configure this
const swPrimaryOriginDomain = "xxx.speedworkers.com"; // Set the same domain as in the primary origin of the origin group
// Optional
const swAllowedUrls = []; // Optional. Set something like https://mysubdomain.mydomain.com/mypages (in lower case) to restrict SW to urls starting with these values
const swRewriteOrigin = ""; // Optional. Set something like https://www.mydomain.com
// Configure the Fallback (your true origin)
const swFallbackDomainName = "XXX"; // url only, no ip addresses
const swFallbackPort = 443;
const swFallbackProtocol = "https";
const swFallbackPath = "";
const swFallbackSslProtocols = ["TLSv1.2"];
const swFallbackReadTimeoutSec = 30;
const swFallbackKeepaliveTimeoutSec = 5;
const swFallbackCustomHeaders = {}; // 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/2.0";
const swIgnored = [
"/graphql",
"/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
export const handler = async (event) => {
const request = event.Records[0].cf.request;
try {
return route(request);
} catch (e) {
// Never fail a request because of a routing bug: send it to your origin
return redirectToFallback(request);
}
};
function route(request) {
// If the request origin is not the SW custom origin, then this is the
// second lambda call targeting the secondary origin after a native
// failover. We don't have anything to do
if (
request.origin.custom === undefined ||
request.origin.custom.domainName !== swPrimaryOriginDomain
) {
return request;
}
// Restore user-agent header (to replace Amazon CloudFront user-agent...)
if (request.headers["x-sw-user-agent"] !== undefined) {
request.headers["user-agent"] = [
{ key: "User-Agent", value: request.headers["x-sw-user-agent"][0].value },
];
}
// Ignore web sockets
if (
request.headers["upgrade"] !== undefined &&
request.headers["upgrade"][0].value.toLowerCase() === "websocket"
) {
return redirectToFallback(request);
}
// SpeedWorkers only handles GET and HEAD: everything else goes straight
// to your origin (defense in depth: the behavior should only allow GET/HEAD)
if (request.method !== "GET" && request.method !== "HEAD") {
return redirectToFallback(request);
}
// The viewer-request lambda classifies every request; its decision travels
// in x-sw-request-type, which the origin request policy must forward.
// If the header is missing (viewer lambda not associated, or policy not
// forwarding it) or doesn't mark a verified bot, never let the request
// reach SpeedWorkers: the bot verification only happens in the viewer
// lambda, so an unverified request must go to the fallback origin.
if (
request.headers["x-sw-request-type"] === undefined ||
!request.headers["x-sw-request-type"][0].value.startsWith("bot")
) {
return redirectToFallback(request);
}
// 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 === "")
) {
return redirectToFallback(request);
}
// 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;
}
// 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)) {
return redirectToFallback(request);
}
// Ignore resources that are not handled by SW
if (ignorePath(url)) {
return redirectToFallback(request);
}
// The request comes from a bot, let it go to the origin group with SW as primary origin
request.origin.custom.customHeaders["x-sw-uri"] = [
{ key: "x-sw-uri", value: url },
];
request.headers["x-sw-agent"] = [{ key: "x-sw-agent", value: swAgent }];
request.headers["x-sw-client-ip"] = [
{ key: "x-sw-client-ip", value: request.clientIp },
];
request.headers["host"] = [{ key: "host", value: swPrimaryOriginDomain }];
return request;
}
// Rewrite the origin to the fallback (your true origin), bypassing the origin group
function redirectToFallback(request) {
request.origin = {
custom: {
domainName: swFallbackDomainName,
port: swFallbackPort,
protocol: swFallbackProtocol,
path: swFallbackPath,
sslProtocols: swFallbackSslProtocols,
readTimeout: swFallbackReadTimeoutSec,
keepaliveTimeout: swFallbackKeepaliveTimeoutSec,
customHeaders: swFallbackCustomHeaders,
},
};
request.headers["host"] = [{ key: "host", value: swFallbackDomainName }];
return request;
}
// 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;
}
