diff --git a/packages/prerender-fargate/lib/prerender/server.js b/packages/prerender-fargate/lib/prerender/server.js
index 84c32815..4e6bd0d4 100644
--- a/packages/prerender-fargate/lib/prerender/server.js
+++ b/packages/prerender-fargate/lib/prerender/server.js
@@ -9,230 +9,280 @@
* @requires aws-sdk
* @requires fs
*/
-'use strict';
+"use strict";
-const prerender = require('prerender');
-const util = require('prerender/lib/util');
-const crypto = require('crypto');
-const logger = require('./utils/logger')
+const prerender = require("prerender");
+const util = require("prerender/lib/util");
+const crypto = require("crypto");
+const logger = require("./utils/logger");
/**
* Replace prerender's util log function with our own which uses pino to log
*
- * @param {...any} args
- * @returns
+ * @param {...any} args
+ * @returns
*/
util.log = function (...args) {
- if (process.env.DISABLE_LOGGING) {
- return;
- }
+ if (process.env.DISABLE_LOGGING) {
+ return;
+ }
- logger.info(args.join(' '));
+ logger.info(args.join(" "));
};
const server = prerender({
- chromeFlags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-web-security', '--remote-debugging-port=9222', '--hide-scrollbars', '--disable-dev-shm-usage'],
- forwardHeaders: true,
- chromeLocation: '/usr/bin/chromium-browser'
+ chromeFlags: [
+ "--no-sandbox",
+ "--headless",
+ "--disable-gpu",
+ "--disable-web-security",
+ "--remote-debugging-port=9222",
+ "--hide-scrollbars",
+ "--disable-dev-shm-usage",
+ ],
+ forwardHeaders: true,
+ chromeLocation: "/usr/bin/chromium-browser",
});
// Healthcheck endpoint
server.use({
- beforeSend: (req, res, next) => {
- if (req.prerender.url === "health") {
- return res.send(200, 'OK');
- }
-
- return next();
+ beforeSend: (req, res, next) => {
+ if (req.prerender.url === "health") {
+ return res.send(200, "OK");
}
+
+ return next();
+ },
});
server.use({
- beforeSend: (req, res, next) => {
- const ms = new Date().getTime() - req.prerender.start.getTime();
-
- logger.render({
- time: ms,
- path: req.prerender.url,
- status: req.prerender.statusCode,
- ip: req.socket.remoteAddress,
- headers: req.prerender.headers,
- origin: req.headers
- });
-
- return next();
- },
+ beforeSend: (req, res, next) => {
+ const ms = new Date().getTime() - req.prerender.start.getTime();
+
+ logger.render({
+ time: ms,
+ path: req.prerender.url,
+ status: req.prerender.statusCode,
+ ip: req.socket.remoteAddress,
+ headers: req.prerender.headers,
+ origin: req.headers,
+ });
+
+ return next();
+ },
});
const tokenJson = JSON.parse(process.env.TOKEN_SECRET);
const tokens = Object.keys(tokenJson);
-const tokenAllowList = tokens.toString().split(',');
+const tokenAllowList = tokens.toString().split(",");
server.use({
- requestReceived: (req, res, next) => {
- // Log "x-prerender-user-agent" value forwarded from CloudFront/Lambda@edge that contains the original User-Agent value. If not present, e.g. requests from ELB, default to "user-agent" value.
- const userAgent = req.get('x-prerender-user-agent') || req.get('user-agent');
-
- logger.info(`${new Date().toISOString()} User-Agent: "${userAgent}" ${req.prerender.reqId} ${req.prerender.url}`)
- let auth = req.headers['x-prerender-token'];
-
- if (!auth) {
- logger.info(`${new Date().toISOString()} "${userAgent}" ${req.prerender.reqId} Authentication header not found.`);
-
- return res.send(401);
- }
-
- // compare credentials in header to list of allowed credentials and corresponding domains
- let authenticated = false;
- for (const token of tokenAllowList) {
- let domains = tokenJson[token].split(',')
- for (const domain of domains) {
- authenticated = (auth === token && req.url.replace("https%3A%2F%2F", "https://").startsWith(`/${domain}`) );
- if (authenticated) break;
- }
- if (authenticated) break;
- }
- if (!authenticated) {
- logger.info(`${new Date().toISOString()} "${userAgent}" ${req.prerender.reqId} Authentication Failed.`);
- return res.send(401);
- }
+ requestReceived: (req, res, next) => {
+ // Log "x-prerender-user-agent" value forwarded from CloudFront/Lambda@edge that contains the original User-Agent value. If not present, e.g. requests from ELB, default to "user-agent" value.
+ const userAgent =
+ req.get("x-prerender-user-agent") || req.get("user-agent");
+
+ logger.info(
+ `${new Date().toISOString()} User-Agent: "${userAgent}" ${req.prerender.reqId} ${req.prerender.url}`
+ );
+ let auth = req.headers["x-prerender-token"];
+
+ if (!auth) {
+ logger.info(
+ `${new Date().toISOString()} "${userAgent}" ${req.prerender.reqId} Authentication header not found.`
+ );
+
+ return res.send(401);
+ }
- return next();
- },
- // Append a custom header to indicate the response is from Prerender
- beforeSend: function(req, res, next) {
- res.setHeader('x-prerender-requestid', crypto.randomUUID());
- return next();
+ // compare credentials in header to list of allowed credentials and corresponding domains
+ let authenticated = false;
+ for (const token of tokenAllowList) {
+ let domains = tokenJson[token].split(",");
+ for (const domain of domains) {
+ authenticated =
+ auth === token &&
+ req.url
+ .replace("https%3A%2F%2F", "https://")
+ .startsWith(`/${domain}`);
+ if (authenticated) break;
+ }
+ if (authenticated) break;
}
+ if (!authenticated) {
+ logger.info(
+ `${new Date().toISOString()} "${userAgent}" ${req.prerender.reqId} Authentication Failed.`
+ );
+ return res.send(401);
+ }
+
+ return next();
+ },
+ // Append a custom header to indicate the response is from Prerender
+ beforeSend: function (req, res, next) {
+ res.setHeader("x-prerender-requestid", crypto.randomUUID());
+ return next();
+ },
});
server.use(prerender.blacklist());
-if (process.env.ENABLE_PRERENDER_HEADER.toLowerCase() === 'true'){
-
- // Let headless chrome send 'X-Prerender: 1' in the request for any specicial handling such as disabling geo-redirection.
- // Ensure that the "access-control-allow-headers" header of any backend systems allows "x-prerender" if CORS is configured.
+if (process.env.ENABLE_PRERENDER_HEADER.toLowerCase() === "true") {
+ // Let headless chrome send 'X-Prerender: 1' in the request for any specicial handling such as disabling geo-redirection.
+ // Ensure that the "access-control-allow-headers" header of any backend systems allows "x-prerender" if CORS is configured.
- server.use(prerender.sendPrerenderHeader());
+ server.use(prerender.sendPrerenderHeader());
}
-var he = require('he');
-var s3 = new (require('aws-sdk')).S3({params:{Bucket: process.env.S3_BUCKET_NAME}});
+var he = require("he");
+var s3 = new (require("aws-sdk").S3)({
+ params: { Bucket: process.env.S3_BUCKET_NAME },
+});
server.use({
- // The requestReceived and pageLoaded functions are a modified version of
- // httpHeader plugin - https://github.com/prerender/prerender/blob/478fa6d0a5196ea29c88c69e64e72eb5507b6d2c/lib/plugins/httpHeaders.js combined with
- // s3cache plugin - https://github.com/prerender/prerender-aws-s3-cache/blob/98707fa0f787de83aa41583682cd2c2d330a9cca/index.js
- requestReceived: function(req, res, next) {
- const fetchCachedObject = function (err, result) {
- if (!err && result) {
- logger.info(`Found cached object: ${key}`);
-
- if (result.Metadata.location){
- res.setHeader('Location', result.Metadata.location);
- }
- // default 200 for legacy objects that do not have Metadata.httpreturncode defined
- return res.send(result.Metadata.httpreturncode || 200, result.Body);
- } else {
- logger.error(`Fetching cached object from S3 bucket failed with error: ${err.code}`);
- }
- next();
- }
-
- if(req.method !== 'GET' && req.method !== 'HEAD') {
- return next();
- }
-
- var key = req.prerender.url;
-
- if (process.env.S3_PREFIX_KEY) {
- key = process.env.S3_PREFIX_KEY + '/' + key;
- }
-
- s3.getObject({
- Key: key
- }, fetchCachedObject);
- }});
+ // The requestReceived and pageLoaded functions are a modified version of
+ // httpHeader plugin - https://github.com/prerender/prerender/blob/478fa6d0a5196ea29c88c69e64e72eb5507b6d2c/lib/plugins/httpHeaders.js combined with
+ // s3cache plugin - https://github.com/prerender/prerender-aws-s3-cache/blob/98707fa0f787de83aa41583682cd2c2d330a9cca/index.js
+ requestReceived: function (req, res, next) {
+ const fetchCachedObject = function (err, result) {
+ if (!err && result) {
+ logger.info(`Found cached object: ${key}`);
+
+ if (result.Metadata.location) {
+ res.setHeader("Location", result.Metadata.location);
+ }
+ // default 200 for legacy objects that do not have Metadata.httpreturncode defined
+ return res.send(result.Metadata.httpreturncode || 200, result.Body);
+ } else {
+ logger.error(
+ `Fetching cached object from S3 bucket failed with error: ${err.code}`
+ );
+ }
+ next();
+ };
+
+ if (req.method !== "GET" && req.method !== "HEAD") {
+ return next();
+ }
+
+ var key = req.prerender.url;
+
+ if (process.env.S3_PREFIX_KEY) {
+ key = process.env.S3_PREFIX_KEY + "/" + key;
+ }
+
+ s3.getObject(
+ {
+ Key: key,
+ },
+ fetchCachedObject
+ );
+ },
+});
server.use(prerender.removeScriptTags());
server.use({
- pageLoaded: function(req, res, next) {
- const statusCodesToCache = ['200'];
+ pageLoaded: function (req, res, next) {
+ const statusCodesToCache = ["200"];
- if (process.env.ENABLE_REDIRECT_CACHE.toLowerCase() === 'true') {
- statusCodesToCache.push('301', '302', '308');
- }
-
- if (process.env.ENABLE_NOTFOUND_CACHE.toLowerCase() === 'true') {
- statusCodesToCache.push('404');
- }
+ if (process.env.ENABLE_REDIRECT_CACHE.toLowerCase() === "true") {
+ statusCodesToCache.push("301", "302", "308");
+ }
- var s3Metadata = {}
- const cacheObject = function (err, result) {
- if (!err && result) {
- logger.info(`Cached object ${key} already present. Skipping caching...`);
- } else {
- logger.info(`Caching the object ${req.prerender.url} with statusCode ${req.prerender.statusCode}`);
- s3.putObject({
- Key: key,
- ContentType: 'text/html;charset=UTF-8',
- StorageClass: 'REDUCED_REDUNDANCY',
- Body: req.prerender.content,
- Metadata: s3Metadata
- }, function(err, result) {
- logger.info(result);
- if (err) logger.error(err);
- });
- }
- }
+ if (process.env.ENABLE_NOTFOUND_CACHE.toLowerCase() === "true") {
+ statusCodesToCache.push("404");
+ }
- // Inspect prerender meta tags and update response accordingly
- if (req.prerender.content && req.prerender.renderType == 'html') {
- const statusMatchRegex = /]*(?:name=['"]prerender-status-code['"][^<>]*content=['"]([0-9]{3})['"]|content=['"]([0-9]{3})['"][^<>]*name=['"]prerender-status-code['"])[^<>]*>/i;
- const headerMatchRegex = /]*(?:name=['"]prerender-header['"][^<>]*content=['"]([^'"]*?): ?([^'"]*?)['"]|content=['"]([^'"]*?): ?([^'"]*?)['"][^<>]*name=['"]prerender-header['"])[^<>]*>/gi
- const head = req.prerender.content.toString().split('', 1).pop()
-
- const statusMatch = statusMatchRegex.exec(head)
- if (statusMatch) {
- req.prerender.statusCode = statusMatch[1] || statusMatch[2];
- req.prerender.content = req.prerender.content.toString().replace(statusMatch[0], '');
- }
-
- let headerMatch = headerMatchRegex.exec(head)
- while (headerMatch) {
- s3Metadata.location = headerMatch[1].toLowerCase() == 'location' ? he.decode(headerMatch[2] || headerMatch[4]) : '';
- res.setHeader(headerMatch[1] || headerMatch[3], s3Metadata.location);
- req.prerender.content = req.prerender.content.toString().replace(headerMatch[0], '');
- headerMatch = headerMatchRegex.exec(head)
- }
-
- if (['301', '302', '307', '308'].includes(req.prerender.statusCode)) {
- const permanentlyOrTemporarily = ['301', '308'].includes(req.prerender.statusCode) ? 'permanently': 'temporarily';
- req.prerender.content = `This page has ${permanentlyOrTemporarily} moved, redirecting to ${s3Metadata.location}...`;
- }
-
- if ( statusCodesToCache.includes(req.prerender.statusCode.toString()) ){
- s3Metadata.httpreturncode = req.prerender.statusCode.toString()
-
- var key = req.prerender.url;
-
- if (process.env.S3_PREFIX_KEY) {
- key = process.env.S3_PREFIX_KEY + '/' + key;
- }
- s3.getObject({
- Key: key
- }, cacheObject);
- } else {
- // Skip caching for the http response codes not in the list, such as 404
- logger.info(`StatusCode ${req.prerender.statusCode} for ${req.prerender.url} is not in the cachable code list. Returning without caching the result.`);
- }
-
- next();
+ var s3Metadata = {};
+ const cacheObject = function (err, result) {
+ if (!err && result) {
+ logger.info(
+ `Cached object ${key} already present. Skipping caching...`
+ );
+ } else {
+ logger.info(
+ `Caching the object ${req.prerender.url} with statusCode ${req.prerender.statusCode}`
+ );
+ s3.putObject(
+ {
+ Key: key,
+ ContentType: "text/html;charset=UTF-8",
+ StorageClass: "REDUCED_REDUNDANCY",
+ Body: req.prerender.content,
+ Metadata: s3Metadata,
+ },
+ function (err, result) {
+ logger.info(result);
+ if (err) logger.error(err);
+ }
+ );
+ }
+ };
+
+ // Inspect prerender meta tags and update response accordingly
+ if (req.prerender.content && req.prerender.renderType == "html") {
+ const statusMatchRegex =
+ /]*(?:name=['"]prerender-status-code['"][^<>]*content=['"]([0-9]{3})['"]|content=['"]([0-9]{3})['"][^<>]*name=['"]prerender-status-code['"])[^<>]*>/i;
+ const headerMatchRegex =
+ /]*(?:name=['"]prerender-header['"][^<>]*content=['"]([^'"]*?): ?([^'"]*?)['"]|content=['"]([^'"]*?): ?([^'"]*?)['"][^<>]*name=['"]prerender-header['"])[^<>]*>/gi;
+ const head = req.prerender.content.toString().split("", 1).pop();
+
+ const statusMatch = statusMatchRegex.exec(head);
+ if (statusMatch) {
+ req.prerender.statusCode = statusMatch[1] || statusMatch[2];
+ req.prerender.content = req.prerender.content
+ .toString()
+ .replace(statusMatch[0], "");
+ }
+
+ let headerMatch = headerMatchRegex.exec(head);
+ while (headerMatch) {
+ s3Metadata.location =
+ headerMatch[1].toLowerCase() == "location"
+ ? he.decode(headerMatch[2] || headerMatch[4])
+ : "";
+ res.setHeader(headerMatch[1] || headerMatch[3], s3Metadata.location);
+ req.prerender.content = req.prerender.content
+ .toString()
+ .replace(headerMatch[0], "");
+ headerMatch = headerMatchRegex.exec(head);
+ }
+
+ if (["301", "302", "307", "308"].includes(req.prerender.statusCode)) {
+ const permanentlyOrTemporarily = ["301", "308"].includes(
+ req.prerender.statusCode
+ )
+ ? "permanently"
+ : "temporarily";
+ req.prerender.content = `This page has ${permanentlyOrTemporarily} moved, redirecting to ${s3Metadata.location}...`;
+ }
+
+ if (statusCodesToCache.includes(req.prerender.statusCode.toString())) {
+ s3Metadata.httpreturncode = req.prerender.statusCode.toString();
+
+ var key = req.prerender.url;
+
+ if (process.env.S3_PREFIX_KEY) {
+ key = process.env.S3_PREFIX_KEY + "/" + key;
}
+ s3.getObject(
+ {
+ Key: key,
+ },
+ cacheObject
+ );
+ } else {
+ // Skip caching for the http response codes not in the list, such as 404
+ logger.info(
+ `StatusCode ${req.prerender.statusCode} for ${req.prerender.url} is not in the cachable code list. Returning without caching the result.`
+ );
+ }
+
+ next();
}
+ },
});
-
server.start();
diff --git a/packages/prerender-fargate/lib/prerender/utils/logger.js b/packages/prerender-fargate/lib/prerender/utils/logger.js
index 61e3d599..72e9488d 100644
--- a/packages/prerender-fargate/lib/prerender/utils/logger.js
+++ b/packages/prerender-fargate/lib/prerender/utils/logger.js
@@ -3,18 +3,18 @@
const pino = require("pino");
module.exports = pino({
- base: undefined,
- timestamp: false,
- messageKey: "message",
- customLevels: {
- render: 35,
- },
- level: process.env.LOG_LEVEL || "info",
- formatters: {
- level: label => {
- return {
- level: label,
- };
- },
+ base: undefined,
+ timestamp: false,
+ messageKey: "message",
+ customLevels: {
+ render: 35,
+ },
+ level: process.env.LOG_LEVEL || "info",
+ formatters: {
+ level: label => {
+ return {
+ level: label,
+ };
},
+ },
});