serverless-api-gateway-caching
Version:
A plugin for the serverless framework which helps with configuring caching for API Gateway endpoints.
195 lines (167 loc) • 7.28 kB
JavaScript
const get = require('lodash.get');
const { Ignore, IgnoreWithWarning, Fail } = require('./UnauthorizedCacheControlHeaderStrategy');
const DEFAULT_CACHE_CLUSTER_SIZE = '0.5';
const DEFAULT_DATA_ENCRYPTED = false;
const DEFAULT_TTL = 3600;
const DEFAULT_UNAUTHORIZED_INVALIDATION_REQUEST_STRATEGY = IgnoreWithWarning;
const DEFAULT_INHERIT_CLOUDWATCH_SETTINGS_FROM_STAGE = true;
const mapUnauthorizedRequestStrategy = strategy => {
if (!strategy) {
return DEFAULT_UNAUTHORIZED_INVALIDATION_REQUEST_STRATEGY;
}
switch (strategy.toLowerCase()) {
case 'ignore': return Ignore;
case 'ignorewithwarning': return IgnoreWithWarning;
case 'fail': return Fail;
default: return DEFAULT_UNAUTHORIZED_INVALIDATION_REQUEST_STRATEGY;
}
}
const isApiGatewayEndpoint = event => {
return event.http ? true : false;
}
const getApiGatewayResourceNameFor = (path, httpMethod) => {
const pathElements = path.split('/');
pathElements.push(httpMethod.toLowerCase());
let gatewayResourceName = pathElements
.map(element => {
element = element.toLowerCase();
element = element.replaceAll('+', '');
element = element.replaceAll('_', '');
element = element.replaceAll('.', '');
element = element.replaceAll('-', 'Dash');
if (element.startsWith('{')) {
element = element.substring(element.indexOf('{') + 1, element.indexOf('}')) + "Var";
}
//capitalize first letter
return element.charAt(0).toUpperCase() + element.slice(1);
}).reduce((a, b) => a + b);
gatewayResourceName = "ApiGatewayMethod" + gatewayResourceName;
return gatewayResourceName;
}
class PerKeyInvalidationSettings {
constructor(cachingSettings) {
let { perKeyInvalidation } = cachingSettings;
if (!perKeyInvalidation) {
this.requireAuthorization = true;
this.handleUnauthorizedRequests = DEFAULT_UNAUTHORIZED_INVALIDATION_REQUEST_STRATEGY;
}
else {
this.requireAuthorization = perKeyInvalidation.requireAuthorization
if (perKeyInvalidation.requireAuthorization) {
this.handleUnauthorizedRequests =
mapUnauthorizedRequestStrategy(perKeyInvalidation.handleUnauthorizedRequests);
}
}
}
}
class ApiGatewayEndpointCachingSettings {
constructor(functionName, event, globalSettings) {
this.functionName = functionName;
if (typeof (event.http) === 'string') {
let parts = event.http.split(' ');
this.method = parts[0];
this.path = parts[1];
}
else {
this.path = event.http.path;
this.method = event.http.method;
}
if (this.path.endsWith('/') && this.path.length > 1) {
this.path = this.path.slice(0, -1);
}
this.gatewayResourceName = getApiGatewayResourceNameFor(this.path, this.method);
let { basePath } = globalSettings;
if (basePath) {
if (!basePath.startsWith('/')) {
basePath = '/'.concat(basePath);
}
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
}
this.pathWithoutGlobalBasePath = this.path;
this.path = basePath.concat(this.path);
}
if (event.http.caching && event.http.caching.inheritCloudWatchSettingsFromStage != undefined) {
this.inheritCloudWatchSettingsFromStage = event.http.caching.inheritCloudWatchSettingsFromStage;
}
else {
this.inheritCloudWatchSettingsFromStage = globalSettings.endpointsInheritCloudWatchSettingsFromStage;
}
if (!event.http.caching) {
this.cachingEnabled = false;
return;
}
let cachingConfig = event.http.caching;
this.cachingEnabled = globalSettings.cachingEnabled ? cachingConfig.enabled : false;
this.dataEncrypted = cachingConfig.dataEncrypted || globalSettings.dataEncrypted;
this.cacheTtlInSeconds = cachingConfig.ttlInSeconds >= 0 ? cachingConfig.ttlInSeconds : globalSettings.cacheTtlInSeconds;
this.cacheKeyParameters = cachingConfig.cacheKeyParameters;
if (!cachingConfig.perKeyInvalidation) {
this.perKeyInvalidation = globalSettings.perKeyInvalidation;
} else {
this.perKeyInvalidation = new PerKeyInvalidationSettings(cachingConfig);
}
}
}
class ApiGatewayAdditionalEndpointCachingSettings {
constructor(method, path, caching, globalSettings) {
this.method = method;
this.path = path;
this.gatewayResourceName = getApiGatewayResourceNameFor(this.path, this.method);
if (!caching) {
this.cachingEnabled = false;
return;
}
const cachingConfig = caching;
this.cachingEnabled = globalSettings.cachingEnabled ? cachingConfig.enabled : false;
this.dataEncrypted = cachingConfig.dataEncrypted || globalSettings.dataEncrypted;
this.cacheTtlInSeconds = caching.ttlInSeconds >= 0 ? caching.ttlInSeconds : globalSettings.cacheTtlInSeconds;
this.cacheKeyParameters = cachingConfig.cacheKeyParameters;
if (!cachingConfig.perKeyInvalidation) {
this.perKeyInvalidation = globalSettings.perKeyInvalidation;
} else {
this.perKeyInvalidation = new PerKeyInvalidationSettings(cachingConfig);
}
}
}
class ApiGatewayCachingSettings {
constructor(serverless, options) {
if (!get(serverless, 'service.custom.apiGatewayCaching')) {
return;
}
const cachingSettings = serverless.service.custom.apiGatewayCaching;
this.cachingEnabled = cachingSettings.enabled;
this.apiGatewayIsShared = cachingSettings.apiGatewayIsShared;
this.restApiId = cachingSettings.restApiId;
this.basePath = cachingSettings.basePath;
if (options) {
this.stage = options.stage || serverless.service.provider.stage;
this.region = options.region || serverless.service.provider.region;
} else {
this.stage = serverless.service.provider.stage;
this.region = serverless.service.provider.region;
}
this.endpointSettings = [];
this.additionalEndpointSettings = [];
this.cacheClusterSize = cachingSettings.clusterSize || DEFAULT_CACHE_CLUSTER_SIZE;
this.cacheTtlInSeconds = cachingSettings.ttlInSeconds >= 0 ? cachingSettings.ttlInSeconds : DEFAULT_TTL;
this.dataEncrypted = cachingSettings.dataEncrypted || DEFAULT_DATA_ENCRYPTED;
this.perKeyInvalidation = new PerKeyInvalidationSettings(cachingSettings);
this.endpointsInheritCloudWatchSettingsFromStage = cachingSettings.endpointsInheritCloudWatchSettingsFromStage == undefined ?
DEFAULT_INHERIT_CLOUDWATCH_SETTINGS_FROM_STAGE : cachingSettings.endpointsInheritCloudWatchSettingsFromStage;
for (let functionName in serverless.service.functions) {
let functionSettings = serverless.service.functions[functionName];
for (let event in functionSettings.events) {
if (isApiGatewayEndpoint(functionSettings.events[event])) {
this.endpointSettings.push(new ApiGatewayEndpointCachingSettings(functionName, functionSettings.events[event], this))
}
}
}
const additionalEndpoints = cachingSettings.additionalEndpoints || [];
for (let additionalEndpoint of additionalEndpoints) {
const { method, path, caching } = additionalEndpoint;
this.additionalEndpointSettings.push(new ApiGatewayAdditionalEndpointCachingSettings(method, path, caching, this))
}
}
}
module.exports = ApiGatewayCachingSettings