scratch-www/bin/configure-fastly.js

260 lines
9.8 KiB
JavaScript
Raw Normal View History

var async = require('async');
var defaults = require('lodash.defaults');
var glob = require('glob');
var path = require('path');
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);
var extraAppRoutes = [
// Homepage with querystring.
// TODO: Should this be added for every route?
'/\\?',
// View html
'/[^\/]*\.html$'
];
2016-04-16 13:46:03 -04:00
/*
* 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) ? '' : '/');
});
2016-04-18 14:07:27 -04:00
};
2016-04-16 13:46:03 -04:00
/*
* 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;
}, []);
2016-04-18 14:07:27 -04:00
};
/*
* Translate an express-style pattern e.g. /path/:arg/ to a regex
* all :arguments become .+?
*/
var expressPatternToRegex = function (pattern) {
return pattern.replace(/(:[^/]+)/gi, '.+?');
};
2016-04-16 13:46:03 -04:00
/*
* Given a list of patterns for paths, OR all of them together into one
* string suitable for a Fastly condition
*/
var pathsToCondition = function (paths) {
2016-07-22 12:45:18 -04:00
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + ')"';
2016-04-18 14:07:27 -04:00
};
2016-04-19 10:47:33 -04:00
/*
* Helper method to NOT a condition statement
*/
var negateConditionStatement = function (statement) {
return '!(' + statement + ')';
};
2016-04-16 13:46:03 -04:00
/*
* 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);
2016-04-18 14:07:27 -04:00
};
2016-04-19 18:42:03 -04:00
var getConditionNameForRoute = function (route, type) {
return 'routes/' + route.pattern + ' (' + type + ')';
};
2016-04-19 18:42:03 -04:00
var getHeaderNameForRoute = function (route) {
if (route.name) return 'rewrites/' + route.name;
if (route.redirect) return 'redirects/' + route.pattern;
2016-04-19 18:42:03 -04:00
};
var getResponseNameForRoute = function (route) {
return 'redirects/' + route.pattern;
};
var routes = route_json.map(function (route) {
return defaults({}, {pattern: expressPatternToRegex(route.pattern)}, route);
});
async.auto({
2016-04-18 14:07:27 -04:00
version: function (cb) {
fastly.getLatestVersion(function (err, response) {
if (err) return cb(err);
// Validate latest version before continuing
2016-04-19 13:04:45 -04:00
if (response.active || response.locked) {
fastly.cloneVersion(response.number, function (err, response) {
if (err) return cb('Failed to clone latest version: ' + err);
cb(null, response.number);
});
} else {
cb(null, response.number);
}
});
},
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);
// 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);
// For a non-pass condition, point backend at s3
var backendCondition = fastly.setBackend(
'F_s3',
S3_BUCKET_NAME,
notPassStatement
);
// For a pass condition, set forwarding headers
var forwardCondition = fastly.setForwardHeaders(passStatement);
fastly.setCustomVCL(
results.version,
'recv-condition',
backendCondition + forwardCondition,
cb
);
}],
fetchCustomVCL: ['version', function (cb, results) {
var passStatement = negateConditionStatement(getAppRouteCondition('../build/*', routes, extraAppRoutes));
var ttlCondition = fastly.setResponseTTL(passStatement);
fastly.setCustomVCL(results.version, 'fetch-condition', ttlCondition, cb);
}],
setBucketNameHeader: ['version', 'notPassRequestCondition', function (cb, results) {
var header = {
name: BUCKET_NAME_HEADER_NAME,
action: 'set',
ignore_if_set: 0,
type: 'REQUEST',
dst: 'http.host',
src: '"' + S3_BUCKET_NAME + '"',
request_condition: results.notPassRequestCondition.name,
priority: 1
};
fastly.setFastlyHeader(results.version, header, cb);
}],
appRouteRequestConditions: ['version', function (cb, results) {
var conditions = {};
async.forEachOf(routes, function (route, id, cb2) {
var condition = {
2016-04-19 18:42:03 -04:00
name: getConditionNameForRoute(route, 'request'),
statement: 'req.url ~ "' + route.pattern + '"',
type: 'REQUEST',
// Priority needs to be > 1 to not interact with http->https redirect
priority: 10 + id
};
fastly.setCondition(results.version, condition, function (err, response) {
if (err) return cb2(err);
conditions[id] = response;
cb2(null, response);
});
}, function (err) {
if (err) return cb(err);
cb(null, conditions);
});
}],
appRouteHeaders: ['version', 'appRouteRequestConditions', function (cb, results) {
var headers = {};
async.forEachOf(routes, function (route, id, cb2) {
2016-04-19 18:42:03 -04:00
if (route.redirect) {
async.auto({
responseCondition: function (cb3) {
var condition = {
name: getConditionNameForRoute(route, 'response'),
statement: 'req.url ~ "' + route.pattern + '"',
type: 'RESPONSE',
priority: id
};
fastly.setCondition(results.version, condition, cb3);
},
responseObject: function (cb3) {
var responseObject = {
name: getResponseNameForRoute(route),
status: 301,
response: 'Moved Permanently',
request_condition: getConditionNameForRoute(route, 'request')
};
fastly.setResponseObject(results.version, responseObject, cb3);
},
redirectHeader: ['responseCondition', function (cb3, redirectResults) {
var header = {
name: getHeaderNameForRoute(route),
action: 'set',
ignore_if_set: 0,
type: 'RESPONSE',
dst: 'http.Location',
src: '"' + route.redirect + '"',
response_condition: redirectResults.responseCondition.name
};
fastly.setFastlyHeader(results.version, header, cb3);
}]
}, function (err, redirectResults) {
if (err) return cb2(err);
headers[id] = redirectResults.redirectHeader;
cb2(null, redirectResults);
});
} else {
var header = {
name: getHeaderNameForRoute(route, 'request'),
action: 'set',
ignore_if_set: 0,
type: 'REQUEST',
dst: 'url',
src: '"/' + route.name + '.html"',
2016-04-19 18:42:03 -04:00
request_condition: results.appRouteRequestConditions[id].name,
priority: 10
};
fastly.setFastlyHeader(results.version, header, function (err, response) {
if (err) return cb2(err);
headers[id] = response;
cb2(null, response);
});
}
}, function (err) {
if (err) return cb(err);
cb(null, headers);
2016-04-18 14:07:27 -04:00
});
}]},
2016-04-19 13:04:45 -04:00
function (err, results) {
if (err) throw new Error(err);
2016-04-19 13:04:45 -04:00
if (process.env.FASTLY_ACTIVATE_CHANGES) {
fastly.activateVersion(results.version, function (err, response) {
if (err) throw new Error(err);
process.stdout.write('Successfully configured and activated version ' + response.number + '\n');
2016-04-29 15:40:44 -04:00
fastly.purgeAll(FASTLY_SERVICE_ID, function (err) {
if (err) throw new Error(err);
process.stdout.write('Purged all.\n');
});
2016-04-19 13:04:45 -04:00
});
}
}
);