feat(AWS HTTP API): Support timeout configuration

This commit is contained in:
Mariusz Nowak 2020-02-27 12:35:25 +13:00 committed by Mariusz Nowak
parent 2de15462e7
commit df9846d9af
6 changed files with 124 additions and 15 deletions

View File

@ -64,6 +64,32 @@ functions:
path: /get/for/any/{param}
```
### Endpoints timeout
By default HTTP API will timeout within 5 seconds. Timeout can be restricted to as low value as 50 milliseconds or lifted up to 29 seconds.
To adjust timeout for all configured endpoints, outline desired value in `provider` settings:
```yaml
provider:
httpApi:
timeout: 0.5 # Restrict endpoints to timeout in 500ms
```
To adjust timeout for specific endpoint, outline it at `httpApi` event configuration:
```yaml
functions:
withCustomTimeout:
handler: handler.withCustomTimeout
events:
- httpApi:
...
timeout: 29
```
**Note**: All `httpApi` events for same function should share same timeout setting.
### CORS Setup
With HTTP API we may configure CORS headers that'll be effective for all configured endpoints.

View File

@ -79,6 +79,7 @@ provider:
targetGroupPrefix: xxxxxxxxxx # Optional prefix to prepend when generating names for target groups
httpApi:
id: # If we want to attach to externally created HTTP API its id should be provided here
timeout: 5 # Timeout setting for all endpoints, defaults to 5s, can be set to values ranging from 0.05s to 29s
cors: true # Implies default behavior, can be fine tuned with specficic options
authorizers:
# JWT authorizers to back HTTP API endpoints
@ -239,6 +240,9 @@ functions:
- httpApi: # HTTP API endpoint
method: GET
path: /some-get-path/{param}
# Timeout setting for given endpoint. Defaults to 5s, can be set to values from 0.05s to 29s
# Note: All httpApi events for same function need to share same timeout setting
timeout: 5
authorizer: # Optional
name: someJwtAuthorizer # References by name authorizer defined in provider.httpApi.authorizers section
scopes: # Optional

View File

@ -258,8 +258,9 @@ Object.defineProperties(
let method;
let path;
let authorizer;
let timeout;
if (_.isObject(event.httpApi)) {
({ method, path, authorizer } = event.httpApi);
({ method, path, authorizer, timeout } = event.httpApi);
} else {
const methodPath = String(event.httpApi);
if (methodPath === '*') {
@ -353,6 +354,17 @@ Object.defineProperties(
routeConfig.authorizer = authorizers.get(name);
if (scopes) routeConfig.authorizationScopes = toSet(scopes);
}
if (!timeout) timeout = userConfig.timeout || null;
if (typeof routeTargetData.timeout !== 'undefined') {
if (routeTargetData.timeout !== timeout) {
throw new this.serverless.classes.Error(
`Inconsistent timeout settings for ${functionName} events`,
'INCONSISTENT_HTTP_API_TIMEOUT'
);
}
} else {
routeTargetData.timeout = timeout;
}
routes.set(routeKey, routeConfig);
if (shouldFillCorsMethods) {
if (event.resolvedMethod === 'ANY') {
@ -367,16 +379,20 @@ Object.defineProperties(
}
}),
compileIntegration: d(function(routeTargetData) {
const properties = {
ApiId: this.getApiIdConfig(),
IntegrationType: 'AWS_PROXY',
IntegrationUri: resolveTargetConfig(routeTargetData),
PayloadFormatVersion: '1.0',
};
if (routeTargetData.timeout) {
properties.TimeoutInMillis = Math.round(routeTargetData.timeout * 1000);
}
this.cfTemplate.Resources[
this.provider.naming.getHttpApiIntegrationLogicalId(routeTargetData.functionName)
] = {
Type: 'AWS::ApiGatewayV2::Integration',
Properties: {
ApiId: this.getApiIdConfig(),
IntegrationType: 'AWS_PROXY',
IntegrationUri: resolveTargetConfig(routeTargetData),
PayloadFormatVersion: '1.0',
},
Properties: properties,
};
}),
compileLambdaPermissions: d(function(routeTargetData) {

View File

@ -452,4 +452,47 @@ describe('HttpApiEvents', () => {
expect(resource.Properties.Action).to.equal('lambda:InvokeFunction');
});
});
describe('Timeout', () => {
let cfResources;
let naming;
before(() =>
fixtures
.extend('httpApi', {
provider: { httpApi: { timeout: 3 } },
functions: {
other: {
events: [
{
httpApi: {
timeout: 20.56,
},
},
],
},
},
})
.then(fixturePath =>
runServerless({
cwd: fixturePath,
cliArgs: ['package'],
}).then(serverless => {
({
Resources: cfResources,
} = serverless.service.provider.compiledCloudFormationTemplate);
naming = serverless.getProvider('aws').naming;
})
)
);
it('Should support timeout set at endpoint', () => {
const resource = cfResources[naming.getHttpApiIntegrationLogicalId('other')];
expect(resource.Properties.TimeoutInMillis).to.equal(20560);
});
it('Should support globally set timeout', () => {
const resource = cfResources[naming.getHttpApiIntegrationLogicalId('foo')];
expect(resource.Properties.TimeoutInMillis).to.equal(3000);
});
});
});

View File

@ -1,10 +1,14 @@
'use strict';
module.exports.handler = (event, context, callback) =>
callback(null, {
statusCode: 200,
body: JSON.stringify({
path: event.path,
method: event.httpMethod,
}),
});
module.exports.handler = (event, context, callback) => {
const resolve = () =>
callback(null, {
statusCode: 200,
body: JSON.stringify({
path: event.path,
method: event.httpMethod,
}),
});
if (event.path === '/bar/timeout') setTimeout(resolve, 2000);
else resolve();
};

View File

@ -89,6 +89,15 @@ describe('HTTP API Integration Test', function() {
},
],
},
other: {
events: [
{
httpApi: {
timeout: 1,
},
},
],
},
},
}),
});
@ -136,6 +145,13 @@ describe('HTTP API Integration Test', function() {
expect(response.status).to.equal(404);
});
it('should respect timeout settings', async () => {
const testEndpoint = `${endpoint}/bar/timeout`;
const response = await fetch(testEndpoint, { method: 'GET' });
expect(response.status).to.equal(503);
});
it('should support CORS when indicated', async () => {
const testEndpoint = `${endpoint}/bar/whatever`;