Skip to content

Commit

Permalink
Merge branch 'release/1.9.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Diana Ionita committed Sep 21, 2022
2 parents e7a8adc + 4c6a390 commit 511e73b
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 49 deletions.
55 changes: 53 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ functions:
- name: request.header.Accept-Language
```
## Only supports REST API
This plugin only supports REST API, because HTTP API does not support API Gateway Caching at the time of this writing. See [docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html).
## Time-to-live, encryption, cache invalidation settings
You can use the `apiGatewayCaching` section ("global settings") to quickly configure cache time-to-live, data encryption and per-key cache invalidation for all endpoints. The settings are inherited by each endpoint for which caching is enabled.
Expand Down Expand Up @@ -147,8 +151,8 @@ functions:

When the endpoint is hit, API Gateway will create cache entries based on the `pawId` path parameter and the `catName` query string parameter. For instance:
- `GET /cats/4` will create a cache entry for `pawId=4` and `catName` as `undefined`.
- `GET /cats/34?catName=Toby` will create a cache entry for `pawId=34` and `catName=Toby`.
- `GET /cats/72?catName=Dixon&furColour=white` will create a cache entry for `pawId=72` and `catName=Dixon`, but will ignore the `furColour` query string parameter. That means that a subsequent request to `GET /cats/72?catName=Dixon&furColour=black` will return the cached response for `pawId=72` and `catName=Dixon`.
- `GET /cats/34?catName=Dixon` will create a cache entry for `pawId=34` and `catName=Dixon`.
- `GET /cats/72?catName=Tsunami&furColour=white` will create a cache entry for `pawId=72` and `catName=Tsunami`, but will ignore the `furColour` query string parameter. That means that a subsequent request to `GET /cats/72?catName=Tsunami&furColour=black` will return the cached response for `pawId=72` and `catName=Tsunami`.

### Cache key parameters from the path, query string and header
When an endpoint varies its responses based on values found in the `path`, `query string` or `header`, you can specify all the parameter names as cache key parameters:
Expand Down Expand Up @@ -387,6 +391,53 @@ custom:
dataEncrypted: true # if not set, inherited from global settings
```

## Configuring caching when the endpoint bypasses lambda and talks to a service like DynamoDb

This example uses the `serverless-apigateway-service-proxy` plugin which creates the path `/dynamodb?id=cat_id`.
Caching can be configured using the `additionalEndpoints` feature. The method and path must match the ones defined as a service proxy. It also supports cache key parameters.

```yml
plugins:
- serverless-api-gateway-caching
- serverless-apigateway-service-proxy
custom:
apiGatewayCaching:
enabled: true
additionalEndpoints:
- method: GET
path: /dynamodb
caching:
enabled: true
ttlInSeconds: 120
cacheKeyParameters:
- name: request.querystring.id
apiGatewayServiceProxies:
- dynamodb:
path: /dynamodb
method: get
tableName: { Ref: 'MyDynamoCatsTable' }
hashKey:
queryStringParam: id # use query string parameter
attributeType: S
action: GetItem
cors: true
resources:
Resources:
MyDynamoCatsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: my-dynamo-cats
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
```

## More Examples

A function with several endpoints:
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-api-gateway-caching",
"version": "1.8.1",
"version": "1.9.0",
"description": "A plugin for the serverless framework which helps with configuring caching for API Gateway endpoints.",
"main": "src/apiGatewayCachingPlugin.js",
"scripts": {
Expand Down
35 changes: 24 additions & 11 deletions src/ApiGatewayCachingSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ApiGatewayEndpointCachingSettings {
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
}
this.pathWithoutGlobalBasePath = this.path;
this.path = basePath.concat(this.path);
}

Expand All @@ -113,11 +114,24 @@ class ApiGatewayAdditionalEndpointCachingSettings {
constructor(method, path, caching, globalSettings) {
this.method = method;
this.path = path;
this.cachingEnabled = globalSettings.cachingEnabled ? get(caching, 'enabled', false) : false;
if (caching) {
this.cacheTtlInSeconds = caching.ttlInSeconds >= 0 ? caching.ttlInSeconds : globalSettings.cacheTtlInSeconds;

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);
}
this.dataEncrypted = get(caching, 'dataEncrypted', globalSettings.dataEncrypted);
}
}

Expand Down Expand Up @@ -147,13 +161,6 @@ class ApiGatewayCachingSettings {
this.cacheTtlInSeconds = cachingSettings.ttlInSeconds >= 0 ? cachingSettings.ttlInSeconds : DEFAULT_TTL;
this.dataEncrypted = cachingSettings.dataEncrypted || DEFAULT_DATA_ENCRYPTED;

const additionalEndpoints = cachingSettings.additionalEndpoints || [];
for (let additionalEndpoint of additionalEndpoints) {
const { method, path, caching } = additionalEndpoint;

this.additionalEndpointSettings.push(new ApiGatewayAdditionalEndpointCachingSettings(method, path, caching, this))
}

this.perKeyInvalidation = new PerKeyInvalidationSettings(cachingSettings);

for (let functionName in serverless.service.functions) {
Expand All @@ -164,6 +171,12 @@ class ApiGatewayCachingSettings {
}
}
}

const additionalEndpoints = cachingSettings.additionalEndpoints || [];
for (let additionalEndpoint of additionalEndpoints) {
const { method, path, caching } = additionalEndpoint;
this.additionalEndpointSettings.push(new ApiGatewayAdditionalEndpointCachingSettings(method, path, caching, this))
}
}
}

Expand Down
24 changes: 22 additions & 2 deletions src/apiGatewayCachingPlugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const ApiGatewayCachingSettings = require('./ApiGatewayCachingSettings');
const pathParametersCache = require('./pathParametersCache');
const cacheKeyParameters = require('./cacheKeyParameters');
const updateStageCacheSettings = require('./stageCache');
const { restApiExists, outputRestApiIdTo } = require('./restApiId');

Expand Down Expand Up @@ -37,7 +37,7 @@ class ApiGatewayCachingPlugin {
return;
}

return pathParametersCache.addPathParametersCacheConfig(this.settings, this.serverless);
return cacheKeyParameters.addCacheKeyParametersConfig(this.settings, this.serverless);
}

async updateStage() {
Expand Down Expand Up @@ -141,6 +141,26 @@ class ApiGatewayCachingPlugin {
enabled: { type: 'boolean' },
ttlInSeconds: { type: 'number' },
dataEncrypted: { type: 'boolean' },
perKeyInvalidation: {
type: 'object',
properties: {
requireAuthorization: { type: 'boolean' },
handleUnauthorizedRequests: {
type: 'string',
enum: ['Ignore', 'IgnoreWithWarning', 'Fail']
}
}
},
cacheKeyParameters: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'string' }
}
}
}
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/pathParametersCache.js → src/cacheKeyParameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ const getResourcesByName = (name, serverless) => {
}
}

const addPathParametersCacheConfig = (settings, serverless) => {
for (let endpointSettings of settings.endpointSettings) {
const applyCacheKeyParameterSettings = (settings, serverless) => {
for (let endpointSettings of settings) {
if (!endpointSettings.cacheKeyParameters) {
continue;
}
const method = getResourcesByName(endpointSettings.gatewayResourceName, serverless);
if (!method) {
serverless.cli.log(`[serverless-api-gateway-caching] The method ${endpointSettings.gatewayResourceName} couldn't be found in the
compiled CloudFormation template. Caching settings will not be updated for this endpoint.`);
compiled CloudFormation template. Caching settings will not be updated for this endpoint.`);
continue;
}
if (!method.Properties.Integration.CacheKeyParameters) {
Expand Down Expand Up @@ -58,7 +58,11 @@ const addPathParametersCacheConfig = (settings, serverless) => {
method.Properties.Integration.CacheNamespace = `${endpointSettings.gatewayResourceName}CacheNS`;
}
}
const addCacheKeyParametersConfig = (settings, serverless) => {
applyCacheKeyParameterSettings(settings.endpointSettings, serverless);
applyCacheKeyParameterSettings(settings.additionalEndpointSettings, serverless);
}

module.exports = {
addPathParametersCacheConfig: addPathParametersCacheConfig
addCacheKeyParametersConfig: addCacheKeyParametersConfig
}
9 changes: 7 additions & 2 deletions src/stageCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,13 @@ const httpEventOf = (lambda, endpointSettings) => {
}
});

return httpEvents.filter(e => (e.path === endpointSettings.path) || (`/${e.path}` === endpointSettings.path))
const event = httpEvents.filter(e =>
(e.path === endpointSettings.path) ||
(`/${e.path}` === endpointSettings.path) ||
(e.path == endpointSettings.pathWithoutGlobalBasePath) ||
(`/${e.path}` == endpointSettings.pathWithoutGlobalBasePath))
.filter(e => e.method.toUpperCase() == endpointSettings.method.toUpperCase());
return event;
}

const createPatchForEndpoint = (endpointSettings, serverless) => {
Expand All @@ -121,7 +126,7 @@ const createPatchForEndpoint = (endpointSettings, serverless) => {
serverless.cli.log(`[serverless-api-gateway-caching] Lambda ${endpointSettings.functionName} has not defined any HTTP events.`);
return;
}
let { path, method } = httpEvents[0];
const { path, method } = endpointSettings;

let patch = [];
if (method.toUpperCase() == 'ANY') {
Expand Down
87 changes: 87 additions & 0 deletions test/configuring-cache-key-parameters-for-additional-endpoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const given = require('../test/steps/given');
const ApiGatewayCachingSettings = require('../src/ApiGatewayCachingSettings');
const cacheKeyParams = require('../src/cacheKeyParameters');
const expect = require('chai').expect;

describe('Configuring path parameters for additional endpoints defined as CloudFormation', () => {
let serverless;
let serviceName = 'cat-api', stage = 'dev';

describe('when there are no additional endpoints', () => {
before(() => {
serverless = given.a_serverless_instance(serviceName)
.withApiGatewayCachingConfig()
.forStage(stage);
});

it('should do nothing to the serverless instance', () => {
let stringified = JSON.stringify(serverless);
when_configuring_cache_key_parameters(serverless);
let stringifiedAfter = JSON.stringify(serverless);
expect(stringified).to.equal(stringifiedAfter);
});
});

describe('when one of the additional endpoints has cache key parameters', () => {
let cacheKeyParameters, apiGatewayMethod;
before(() => {
cacheKeyParameters = [
{ name: 'request.path.pawId' },
{ name: 'request.header.Accept-Language' }]
const additionalEndpointWithCaching = given.an_additional_endpoint({
method: 'GET', path: '/items',
caching: {
enabled: true, ttlInSeconds: 120,
cacheKeyParameters
}
})
const additionalEndpointWithoutCaching = given.an_additional_endpoint({
method: 'POST', path: '/blue-items',
caching: { enabled: true }
});

serverless = given.a_serverless_instance(serviceName)
.withApiGatewayCachingConfig()
.withAdditionalEndpoints([additionalEndpointWithCaching, additionalEndpointWithoutCaching])
.forStage('somestage');

when_configuring_cache_key_parameters(serverless);

apiGatewayMethod = serverless.getMethodResourceForAdditionalEndpoint(additionalEndpointWithCaching);
});

it('should configure the method\'s request parameters', () => {
for (let parameter of cacheKeyParameters) {
expect(apiGatewayMethod.Properties.RequestParameters)
.to.deep.include({
[`method.${parameter.name}`]: {}
});
}
});

it('should not set integration request parameters', () => {
for (let parameter of cacheKeyParameters) {
expect(apiGatewayMethod.Properties.Integration.RequestParameters)
.to.not.include({
[`integration.${parameter.name}`]: `method.${parameter.name}`
});
}
});

it('should set the method\'s integration cache key parameters', () => {
for (let parameter of cacheKeyParameters) {
expect(apiGatewayMethod.Properties.Integration.CacheKeyParameters)
.to.include(`method.${parameter.name}`);
}
});

it('should set a cache namespace', () => {
expect(apiGatewayMethod.Properties.Integration.CacheNamespace).to.exist;
});
});
});

const when_configuring_cache_key_parameters = (serverless) => {
let cacheSettings = new ApiGatewayCachingSettings(serverless);
return cacheKeyParams.addCacheKeyParametersConfig(cacheSettings, serverless);
}
Loading

0 comments on commit 511e73b

Please sign in to comment.