mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-03-25 20:29:45 -04:00
Move string set
methods out of fastly-extended
This also moves all helper methods in `configure-fastly` into a separate utility file, and adds some additional unit tests for those utilities now that they are separated.
This commit is contained in:
parent
ca067fdc5e
commit
d563535ba5
5 changed files with 258 additions and 192 deletions
|
@ -1,13 +1,11 @@
|
|||
var async = require('async');
|
||||
var defaults = require('lodash.defaults');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
var fastlyConfig = require('./lib/fastly-config-methods');
|
||||
|
||||
var route_json = require('../src/routes.json');
|
||||
|
||||
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
|
||||
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
|
||||
|
||||
const BUCKET_NAME_HEADER_NAME = 'Bucket name';
|
||||
|
||||
var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY_SERVICE_ID);
|
||||
|
@ -20,87 +18,8 @@ var extraAppRoutes = [
|
|||
'/[^\/]*\.html$'
|
||||
];
|
||||
|
||||
/*
|
||||
* Given the relative path to the static directory, return an array of
|
||||
* patterns matching the files and directories there.
|
||||
*/
|
||||
var getStaticPaths = function (pathToStatic) {
|
||||
var staticPaths = glob.sync(path.resolve(__dirname, pathToStatic));
|
||||
return staticPaths.filter(function (pathName) {
|
||||
// Exclude view html, resolve everything else in the build
|
||||
return path.extname(pathName) !== '.html';
|
||||
}).map(function (pathName) {
|
||||
// Reduce absolute path to relative paths like '/js'
|
||||
var base = path.dirname(path.resolve(__dirname, pathToStatic));
|
||||
return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Given a list of express routes, return a list of patterns to match
|
||||
* the express route and a static view file associated with the route
|
||||
*/
|
||||
var getViewPaths = function (routes) {
|
||||
return routes.reduce(function (paths, route) {
|
||||
var path = route.routeAlias || route.pattern;
|
||||
if (paths.indexOf(path) === -1) {
|
||||
paths.push(path);
|
||||
}
|
||||
return paths;
|
||||
}, []);
|
||||
};
|
||||
|
||||
/*
|
||||
* Translate an express-style pattern e.g. /path/:arg/ to a regex
|
||||
* all :arguments become .+?
|
||||
*/
|
||||
var expressPatternToRegex = function (pattern) {
|
||||
return pattern.replace(/(:[^/]+)/gi, '.+?');
|
||||
};
|
||||
|
||||
/*
|
||||
* Given a list of patterns for paths, OR all of them together into one
|
||||
* string suitable for a Fastly condition
|
||||
*/
|
||||
var pathsToCondition = function (paths) {
|
||||
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
|
||||
return conditionString + (conditionString ? '|' : '') + pattern;
|
||||
}, '') + ')"';
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper method to NOT a condition statement
|
||||
*/
|
||||
var negateConditionStatement = function (statement) {
|
||||
return '!(' + statement + ')';
|
||||
};
|
||||
|
||||
/*
|
||||
* Combine static paths, routes, and any additional paths to a single
|
||||
* fastly condition to match req.url
|
||||
*/
|
||||
var getAppRouteCondition = function (pathToStatic, routes, additionalPaths) {
|
||||
var staticPaths = getStaticPaths(pathToStatic);
|
||||
var viewPaths = getViewPaths(routes);
|
||||
var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
|
||||
return pathsToCondition(allPaths);
|
||||
};
|
||||
|
||||
var getConditionNameForRoute = function (route, type) {
|
||||
return 'routes/' + route.pattern + ' (' + type + ')';
|
||||
};
|
||||
|
||||
var getHeaderNameForRoute = function (route) {
|
||||
if (route.name) return 'rewrites/' + route.name;
|
||||
if (route.redirect) return 'redirects/' + route.pattern;
|
||||
};
|
||||
|
||||
var getResponseNameForRoute = function (route) {
|
||||
return 'redirects/' + route.pattern;
|
||||
};
|
||||
|
||||
var routes = route_json.map(function (route) {
|
||||
return defaults({}, {pattern: expressPatternToRegex(route.pattern)}, route);
|
||||
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
|
||||
});
|
||||
|
||||
async.auto({
|
||||
|
@ -121,20 +40,20 @@ async.auto({
|
|||
recvCustomVCL: ['version', function (cb, results) {
|
||||
// For all the routes in routes.json, construct a varnish-style regex that matches
|
||||
// on any of those route conditions.
|
||||
var notPassStatement = getAppRouteCondition('../build/*', routes, extraAppRoutes);
|
||||
var notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
|
||||
|
||||
// For all the routes in routes.json, construct a varnish-style regex that matches
|
||||
// only if NONE of those routes are matched.
|
||||
var passStatement = negateConditionStatement(notPassStatement);
|
||||
var passStatement = fastlyConfig.negateConditionStatement(notPassStatement);
|
||||
|
||||
// For a non-pass condition, point backend at s3
|
||||
var backendCondition = fastly.setBackend(
|
||||
var backendCondition = fastlyConfig.setBackend(
|
||||
'F_s3',
|
||||
S3_BUCKET_NAME,
|
||||
notPassStatement
|
||||
);
|
||||
// For a pass condition, set forwarding headers
|
||||
var forwardCondition = fastly.setForwardHeaders(passStatement);
|
||||
var forwardCondition = fastlyConfig.setForwardHeaders(passStatement);
|
||||
|
||||
fastly.setCustomVCL(
|
||||
results.version,
|
||||
|
@ -144,8 +63,10 @@ async.auto({
|
|||
);
|
||||
}],
|
||||
fetchCustomVCL: ['version', function (cb, results) {
|
||||
var passStatement = negateConditionStatement(getAppRouteCondition('../build/*', routes, extraAppRoutes));
|
||||
var ttlCondition = fastly.setResponseTTL(passStatement);
|
||||
var passStatement = fastlyConfig.negateConditionStatement(
|
||||
fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname)
|
||||
);
|
||||
var ttlCondition = fastlyConfig.setResponseTTL(passStatement);
|
||||
fastly.setCustomVCL(results.version, 'fetch-condition', ttlCondition, cb);
|
||||
}],
|
||||
setBucketNameHeader: ['version', 'notPassRequestCondition', function (cb, results) {
|
||||
|
@ -165,7 +86,7 @@ async.auto({
|
|||
var conditions = {};
|
||||
async.forEachOf(routes, function (route, id, cb2) {
|
||||
var condition = {
|
||||
name: getConditionNameForRoute(route, 'request'),
|
||||
name: fastlyConfig.getConditionNameForRoute(route, 'request'),
|
||||
statement: 'req.url ~ "' + route.pattern + '"',
|
||||
type: 'REQUEST',
|
||||
// Priority needs to be > 1 to not interact with http->https redirect
|
||||
|
@ -188,7 +109,7 @@ async.auto({
|
|||
async.auto({
|
||||
responseCondition: function (cb3) {
|
||||
var condition = {
|
||||
name: getConditionNameForRoute(route, 'response'),
|
||||
name: fastlyConfig.getConditionNameForRoute(route, 'response'),
|
||||
statement: 'req.url ~ "' + route.pattern + '"',
|
||||
type: 'RESPONSE',
|
||||
priority: id
|
||||
|
@ -197,16 +118,16 @@ async.auto({
|
|||
},
|
||||
responseObject: function (cb3) {
|
||||
var responseObject = {
|
||||
name: getResponseNameForRoute(route),
|
||||
name: fastlyConfig.getResponseNameForRoute(route),
|
||||
status: 301,
|
||||
response: 'Moved Permanently',
|
||||
request_condition: getConditionNameForRoute(route, 'request')
|
||||
request_condition: fastlyConfig.getConditionNameForRoute(route, 'request')
|
||||
};
|
||||
fastly.setResponseObject(results.version, responseObject, cb3);
|
||||
},
|
||||
redirectHeader: ['responseCondition', function (cb3, redirectResults) {
|
||||
var header = {
|
||||
name: getHeaderNameForRoute(route),
|
||||
name: fastlyConfig.getHeaderNameForRoute(route),
|
||||
action: 'set',
|
||||
ignore_if_set: 0,
|
||||
type: 'RESPONSE',
|
||||
|
@ -223,7 +144,7 @@ async.auto({
|
|||
});
|
||||
} else {
|
||||
var header = {
|
||||
name: getHeaderNameForRoute(route, 'request'),
|
||||
name: fastlyConfig.getHeaderNameForRoute(route, 'request'),
|
||||
action: 'set',
|
||||
ignore_if_set: 0,
|
||||
type: 'REQUEST',
|
||||
|
|
139
bin/lib/fastly-config-methods.js
Normal file
139
bin/lib/fastly-config-methods.js
Normal file
|
@ -0,0 +1,139 @@
|
|||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
|
||||
var FastlyConfigMethods = {
|
||||
/*
|
||||
* Given the relative path to the static directory, return an array of
|
||||
* patterns matching the files and directories there.
|
||||
*/
|
||||
getStaticPaths: function (rootDir, pathToStatic) {
|
||||
var staticPaths = glob.sync(path.resolve(rootDir, pathToStatic));
|
||||
return staticPaths.filter(function (pathName) {
|
||||
// Exclude view html, resolve everything else in the build
|
||||
return path.extname(pathName) !== '.html';
|
||||
}).map(function (pathName) {
|
||||
// Reduce absolute path to relative paths like '/js'
|
||||
var base = path.dirname(path.resolve(rootDir, pathToStatic));
|
||||
return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Given a list of express routes, return a list of patterns to match
|
||||
* the express route and a static view file associated with the route
|
||||
*/
|
||||
getViewPaths: function (routes) {
|
||||
return routes.reduce(function (paths, route) {
|
||||
var path = route.routeAlias || route.pattern;
|
||||
if (paths.indexOf(path) === -1) {
|
||||
paths.push(path);
|
||||
}
|
||||
return paths;
|
||||
}, []);
|
||||
},
|
||||
|
||||
/*
|
||||
* Translate an express-style pattern e.g. /path/:arg/ to a regex
|
||||
* all :arguments become .+?
|
||||
*/
|
||||
expressPatternToRegex: function (pattern) {
|
||||
return pattern.replace(/(:[^/]+)/gi, '.+?');
|
||||
},
|
||||
|
||||
/*
|
||||
* Given a list of patterns for paths, OR all of them together into one
|
||||
* string suitable for a Fastly condition
|
||||
*/
|
||||
pathsToCondition: function (paths) {
|
||||
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
|
||||
return conditionString + (conditionString ? '|' : '') + pattern;
|
||||
}, '') + ')"';
|
||||
},
|
||||
|
||||
/*
|
||||
* Helper method to NOT a condition statement
|
||||
*/
|
||||
negateConditionStatement: function (statement) {
|
||||
return '!(' + statement + ')';
|
||||
},
|
||||
|
||||
/*
|
||||
* Combine static paths, routes, and any additional paths to a single
|
||||
* fastly condition to match req.url
|
||||
*/
|
||||
getAppRouteCondition: function (pathToStatic, routes, additionalPaths, rootDir) {
|
||||
var staticPaths = FastlyConfigMethods.getStaticPaths(rootDir, pathToStatic);
|
||||
var viewPaths = FastlyConfigMethods.getViewPaths(routes);
|
||||
var allPaths = [].concat(staticPaths, viewPaths, additionalPaths);
|
||||
return FastlyConfigMethods.pathsToCondition(allPaths);
|
||||
},
|
||||
|
||||
getConditionNameForRoute: function (route, type) {
|
||||
return 'routes/' + route.pattern + ' (' + type + ')';
|
||||
},
|
||||
|
||||
getHeaderNameForRoute: function (route) {
|
||||
if (route.name) return 'rewrites/' + route.name;
|
||||
if (route.redirect) return 'redirects/' + route.pattern;
|
||||
},
|
||||
|
||||
getResponseNameForRoute: function (route) {
|
||||
return 'redirects/' + route.pattern;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns custom vcl configuration as a string for setting the backend
|
||||
* of a request to the given backend/host.
|
||||
*
|
||||
* @param {string} backend name of the backend declared in fastly
|
||||
* @param {string} host name of the s3 bucket to be set as the host
|
||||
* @param {string} condition condition under which backend should be set
|
||||
*/
|
||||
setBackend: function (backend, host, condition) {
|
||||
return '' +
|
||||
'if (' + condition + ') {\n' +
|
||||
' set req.backend = ' + backend + ';\n' +
|
||||
' set req.http.host = \"' + host + '\";\n' +
|
||||
'}\n';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns custom vcl configuration as a string for headers that
|
||||
* should be added for the condition in which a request is forwarded.
|
||||
*
|
||||
* @param {string} condition condition under which to set pass headers
|
||||
*/
|
||||
setForwardHeaders: function (condition) {
|
||||
return '' +
|
||||
'if (' + condition + ') {\n' +
|
||||
' if (!req.http.Fastly-FF) {\n' +
|
||||
' if (req.http.X-Forwarded-For) {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
|
||||
' }\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
|
||||
' }\n' +
|
||||
' set req.grace = 60s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns custom vcl configuration as a string that sets the varnish
|
||||
* Time to Live (TTL) for responses that come from s3.
|
||||
*
|
||||
* @param {string} condition condition under which the response should be set
|
||||
*/
|
||||
setResponseTTL: function (condition) {
|
||||
return '' +
|
||||
'if (' + condition + ') {\n' +
|
||||
' set beresp.ttl = 0s;\n' +
|
||||
' set beresp.grace = 0s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n';
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FastlyConfigMethods;
|
|
@ -201,59 +201,5 @@ module.exports = function (apiKey, serviceId) {
|
|||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns custom vcl configuration as a string for setting the backend
|
||||
* of a request to the given backend/host.
|
||||
*
|
||||
* @param {string} backend name of the backend declared in fastly
|
||||
* @param {string} host name of the s3 bucket to be set as the host
|
||||
* @param {string} condition condition under which backend should be set
|
||||
*/
|
||||
fastly.setBackend = function (backend, host, condition) {
|
||||
return '' +
|
||||
'if (' + condition + ') {\n' +
|
||||
' set req.backend = ' + backend + ';\n' +
|
||||
' set req.http.host = \"' + host + '\";\n' +
|
||||
'}\n';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns custom vcl configuration as a string for headers that
|
||||
* should be added for the condition in which a request is forwarded.
|
||||
*
|
||||
* @param {string} condition condition under which to set pass headers
|
||||
*/
|
||||
fastly.setForwardHeaders = function (condition) {
|
||||
return '' +
|
||||
'if (' + condition + ') {\n' +
|
||||
' if (!req.http.Fastly-FF) {\n' +
|
||||
' if (req.http.X-Forwarded-For) {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
|
||||
' }\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
|
||||
' }\n' +
|
||||
' set req.grace = 60s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns custom vcl configuration as a string that sets the varnish
|
||||
* Time to Live (TTL) for responses that come from s3.
|
||||
*
|
||||
* @param {string} condition condition under which the response should be set
|
||||
*/
|
||||
fastly.setResponseTTL = function (condition) {
|
||||
return '' +
|
||||
'if (' + condition + ') {\n' +
|
||||
' set beresp.ttl = 0s;\n' +
|
||||
' set beresp.grace = 0s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n';
|
||||
};
|
||||
|
||||
return fastly;
|
||||
};
|
||||
|
|
103
test/unit/test_fastly_config_methods.js
Normal file
103
test/unit/test_fastly_config_methods.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
var defaults = require('lodash.defaults');
|
||||
var fastlyConfig = require('../../bin/lib/fastly-config-methods');
|
||||
var route_json = require('../../src/routes.json');
|
||||
var tap = require('tap');
|
||||
|
||||
var testRoutes = [
|
||||
{
|
||||
name: 'less-traveled',
|
||||
pattern: '^/?$',
|
||||
routeAlias: '/?$',
|
||||
view: 'less-traveled/less-traveled',
|
||||
title: 'Robert Frost Goes Here'
|
||||
},
|
||||
{
|
||||
name: 'more-traveled',
|
||||
pattern: '^/more?$',
|
||||
routeAlias: '/more?$',
|
||||
view: 'more-traveled/more-traveled',
|
||||
title: 'Robert Frost Does Not Go Here'
|
||||
}
|
||||
];
|
||||
|
||||
var routes = route_json.map(function (route) {
|
||||
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
|
||||
});
|
||||
var extraAppRoutes = [
|
||||
// Homepage with querystring.
|
||||
// TODO: Should this be added for every route?
|
||||
'/\\?',
|
||||
// View html
|
||||
'/[^\/]*\.html$'
|
||||
];
|
||||
|
||||
|
||||
tap.test('getStaticPaths', function (t) {
|
||||
var staticPaths = fastlyConfig.getStaticPaths(__dirname, '../../build/*');
|
||||
t.type(staticPaths, 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('getViewPaths', function (t) {
|
||||
var viewPaths = fastlyConfig.getViewPaths(testRoutes);
|
||||
t.type(viewPaths, 'object');
|
||||
t.equal(viewPaths[0], '/?$');
|
||||
t.equal(viewPaths[1], '/more?$');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('pathsToCondition', function (t) {
|
||||
var condition = fastlyConfig.pathsToCondition(['/?$', '/more?$']);
|
||||
t.type(condition, 'string');
|
||||
t.equal(condition, 'req.url~"^(/?$|/more?$)"');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('getAppRouteCondition', function (t) {
|
||||
var condition = fastlyConfig.getAppRouteCondition('../../build/*', routes, extraAppRoutes, __dirname);
|
||||
t.type(condition, 'string');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('testSetBackend', function (t) {
|
||||
var backend = fastlyConfig.setBackend('wemust', 'goback', 'marty');
|
||||
t.equal(backend, '' +
|
||||
'if (marty) {\n' +
|
||||
' set req.backend = wemust;\n' +
|
||||
' set req.http.host = \"goback\";\n' +
|
||||
'}\n'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('testSetForward', function (t) {
|
||||
var forward = fastlyConfig.setForwardHeaders('alwaysforward');
|
||||
t.equal(forward, '' +
|
||||
'if (alwaysforward) {\n' +
|
||||
' if (!req.http.Fastly-FF) {\n' +
|
||||
' if (req.http.X-Forwarded-For) {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
|
||||
' }\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
|
||||
' }\n' +
|
||||
' set req.grace = 60s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('testSetTTL', function (t) {
|
||||
var ttl = fastlyConfig.setResponseTTL('itsactuallyttyl');
|
||||
t.equal(ttl, '' +
|
||||
'if (itsactuallyttyl) {\n' +
|
||||
' set beresp.ttl = 0s;\n' +
|
||||
' set beresp.grace = 0s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n'
|
||||
);
|
||||
t.end();
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
var fastly = require('../../bin/lib/fastly-extended');
|
||||
var tap = require('tap');
|
||||
|
||||
tap.test('testSetBackend', function (t) {
|
||||
var backend = fastly.setBackend('wemust', 'goback', 'marty');
|
||||
t.equal(backend, '' +
|
||||
'if (marty) {\n' +
|
||||
' set req.backend = wemust;\n' +
|
||||
' set req.http.host = \"goback\";\n' +
|
||||
'}\n'
|
||||
);
|
||||
});
|
||||
|
||||
tap.test('testSetForward', function (t) {
|
||||
var forward = fastly.setForwardHeaders('alwaysforward');
|
||||
t.equal(forward, '' +
|
||||
'if (alwaysforward) {\n' +
|
||||
' if (!req.http.Fastly-FF) {\n' +
|
||||
' if (req.http.X-Forwarded-For) {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
|
||||
' }\n' +
|
||||
' } else {\n' +
|
||||
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
|
||||
' }\n' +
|
||||
' set req.grace = 60s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('testSetTTL', function (t) {
|
||||
var ttl = fastly.setResponseTTL('itsactuallyttyl');
|
||||
t.equal(ttl, '' +
|
||||
'if (itsactuallyttyl) {\n' +
|
||||
' set beresp.ttl = 0s;\n' +
|
||||
' set beresp.grace = 0s;\n' +
|
||||
' return(pass);\n' +
|
||||
'}\n'
|
||||
);
|
||||
});
|
Loading…
Add table
Reference in a new issue