Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS HTTP API: Ensure function timeout setting is respected #7420

Merged
merged 1 commit into from Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 1 addition & 23 deletions docs/providers/aws/events/http-api.md
Expand Up @@ -66,29 +66,7 @@ functions:

### 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.
Framework ensures that function timeout setting (which defaults to 6 seconds) is respected in HTTP API endpoint configuration. Still note that maximum possible timeout for an endpoint is 29 seconds. Ensure to keep function timeout below that. Otherwise you may observe successful lambda invocations reported with `503` status code.

### CORS Setup

Expand Down
4 changes: 0 additions & 4 deletions docs/providers/aws/guide/serverless.yml.md
Expand Up @@ -79,7 +79,6 @@ 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
Expand Down Expand Up @@ -240,9 +239,6 @@ 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
Expand Down
38 changes: 38 additions & 0 deletions lib/plugins/aws/package/compile/events/httpApi/index.js
Expand Up @@ -4,6 +4,7 @@ const _ = require('lodash');
const d = require('d');
const memoizee = require('memoizee');
const memoizeeMethods = require('memoizee/methods');
const { logWarning } = require('../../../../../../classes/Error');

const allowedMethods = new Set(['GET', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'HEAD', 'DELETE']);
const methodPathPattern = /^([a-zA-Z]+) (.+)$/;
Expand Down Expand Up @@ -247,6 +248,13 @@ Object.defineProperties(
responseLength: '$context.responseLength',
})}}`;
}

if (userConfig.timeout) {
logWarning(
'provider.httpApi.timeout is deprecated. ' +
'HTTP API endpoints are configured to follow timeout setting as set for functions.'
);
}
for (const [functionName, functionData] of _.entries(this.serverless.service.functions)) {
const routeTargetData = {
functionName,
Expand Down Expand Up @@ -376,6 +384,36 @@ Object.defineProperties(
}
}
}
const functionTimeout =
Number(functionData.timeout) || Number(this.serverless.service.provider.timeout) || 6;
if (routeTargetData.timeout) {
logWarning(
`httpApi.timeout is deprecated (found one defined for '${functionName}'s endpoint). ` +
'HTTP API endpoints are configured to follow timeout setting as set for functions.'
);
if (functionTimeout >= routeTargetData.timeout) {
logWarning(
`HTTP API endpoint timeout setting (${routeTargetData.timeout}s) is ` +
`lower or equal to function timeout (${functionTimeout}s). ` +
'This may introduce situations where endpoint times out ' +
'for succesful lambda invocation.'
);
}
} else {
if (functionTimeout >= 29) {
logWarning(
`Function timeout setting (${functionTimeout}) is greater than ` +
'maximum allowed timeout for HTTP API endpoint (29s). ' +
'This may introduce situation where endpoint times out ' +
'for succesful lambda invocation.'
);
}
// Ensure endpoint has slightly larger timeout than a function,
// It's a margin needed for some side processing time on AWS side.
// Otherwise there's a risk of observing 503 status for successfully resolved invocation
// (which just fit function timeout setting)
routeTargetData.timeout = Math.min(functionTimeout + 0.5, 29);
}
}
}),
compileIntegration: d(function(routeTargetData) {
Expand Down
5 changes: 5 additions & 0 deletions lib/plugins/aws/package/compile/events/httpApi/index.test.js
Expand Up @@ -77,6 +77,11 @@ describe('HttpApiEvents', () => {
expect(resource.Type).to.equal('AWS::ApiGatewayV2::Integration');
expect(resource.Properties.IntegrationType).to.equal('AWS_PROXY');
});
it('Should ensure higher timeout than function', () => {
const resource = cfResources[naming.getHttpApiIntegrationLogicalId('foo')];
expect(resource.Properties.TimeoutInMillis).to.equal(6500);
});

it('Should configure lambda permissions', () => {
const resource = cfResources[naming.getLambdaHttpApiPermissionLogicalId('foo')];
expect(resource.Type).to.equal('AWS::Lambda::Permission');
Expand Down