mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
758 lines
25 KiB
JavaScript
758 lines
25 KiB
JavaScript
import _ from 'lodash'
|
|
import d from 'd'
|
|
import memoizee from 'memoizee'
|
|
import memoizeeMethods from 'memoizee/methods.js'
|
|
import utils from '@serverlessinc/sf-core/src/utils.js'
|
|
import ServerlessError from '../../../../../serverless-error.js'
|
|
import resolveLambdaTarget from '../../../utils/resolve-lambda-target.js'
|
|
|
|
const { log } = utils
|
|
|
|
const allowedMethods = new Set([
|
|
'ANY',
|
|
'GET',
|
|
'POST',
|
|
'PUT',
|
|
'PATCH',
|
|
'OPTIONS',
|
|
'HEAD',
|
|
'DELETE',
|
|
])
|
|
const methodPattern = new RegExp(
|
|
`^(?:\\*|${Array.from(allowedMethods).join('|')})$`,
|
|
'i',
|
|
)
|
|
const methodPathPattern = new RegExp(
|
|
`^(?:\\*|(${Array.from(allowedMethods).join('|')}) (\\/\\S*))$`,
|
|
'i',
|
|
)
|
|
|
|
const resolveTargetConfig = memoizee(({ functionLogicalId, functionAlias }) => {
|
|
const functionArnGetter = { 'Fn::GetAtt': [functionLogicalId, 'Arn'] }
|
|
if (!functionAlias) return functionArnGetter
|
|
return { 'Fn::Join': [':', [functionArnGetter, functionAlias.name]] }
|
|
})
|
|
|
|
const defaultCors = {
|
|
allowedOrigins: new Set(['*']),
|
|
allowedHeaders: new Set([
|
|
'Content-Type',
|
|
'X-Amz-Date',
|
|
'Authorization',
|
|
'X-Api-Key',
|
|
'X-Amz-Security-Token',
|
|
'X-Amz-User-Agent',
|
|
'X-Amzn-Trace-Id',
|
|
]),
|
|
}
|
|
|
|
const toSet = (item) => new Set(Array.isArray(item) ? item : [item])
|
|
|
|
class HttpApiEvents {
|
|
constructor(serverless) {
|
|
this.serverless = serverless
|
|
this.provider = this.serverless.getProvider('aws')
|
|
serverless.httpApiEventsPlugin = this
|
|
|
|
this.hooks = {
|
|
initialize: () => {
|
|
if (
|
|
this.serverless.service.provider.name === 'aws' &&
|
|
_.get(this.serverless.service.provider.httpApi, 'useProviderTags')
|
|
) {
|
|
this.serverless._logDeprecation(
|
|
'AWS_HTTP_API_USE_PROVIDER_TAGS_PROPERTY',
|
|
'Property "provider.httpApi.useProviderTags" is no longer effective as provider tags are applied to Http Api Gateway by default. You can safely remove this property from your configuration.',
|
|
)
|
|
}
|
|
},
|
|
'package:compileEvents': () => {
|
|
this.resolveConfiguration()
|
|
if (!this.config.routes.size) return
|
|
this.cfTemplate =
|
|
this.serverless.service.provider.compiledCloudFormationTemplate
|
|
this.compileApi()
|
|
this.compileLogGroup()
|
|
this.compileStage()
|
|
this.compileAuthorizers()
|
|
this.compileEndpoints()
|
|
},
|
|
}
|
|
|
|
this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'httpApi', {
|
|
anyOf: [
|
|
{ type: 'string', regexp: methodPathPattern.toString() },
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
authorizer: {
|
|
anyOf: [
|
|
{ type: 'string' },
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
id: {
|
|
anyOf: [
|
|
{ type: 'string' },
|
|
{ $ref: '#/definitions/awsCfFunction' },
|
|
],
|
|
},
|
|
name: { type: 'string' },
|
|
scopes: { type: 'array', items: { type: 'string' } },
|
|
type: {
|
|
type: 'string',
|
|
enum: ['request', 'jwt', 'aws_iam'],
|
|
},
|
|
},
|
|
anyOf: [
|
|
{ required: ['id'] },
|
|
{ required: ['name'] },
|
|
{ required: ['type'] },
|
|
],
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
},
|
|
method: { type: 'string', regexp: methodPattern.toString() },
|
|
path: { type: 'string', regexp: /^(?:\*|\/\S*)$/.toString() },
|
|
},
|
|
required: ['path'],
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
})
|
|
}
|
|
getApiIdConfig() {
|
|
return this.config.id || { Ref: this.provider.naming.getHttpApiLogicalId() }
|
|
}
|
|
compileApi() {
|
|
if (this.config.id) return
|
|
const properties = {
|
|
Name: this.provider.naming.getHttpApiName(),
|
|
ProtocolType: 'HTTP',
|
|
DisableExecuteApiEndpoint:
|
|
this.config.disableDefaultEndpoint == null
|
|
? undefined
|
|
: this.config.disableDefaultEndpoint,
|
|
}
|
|
if (this.serverless.service.provider.tags) {
|
|
const tags = Object.assign({}, this.serverless.service.provider.tags)
|
|
properties.Tags = tags
|
|
}
|
|
const cors = this.config.cors
|
|
if (cors) {
|
|
properties.CorsConfiguration = {
|
|
AllowCredentials: cors.allowCredentials,
|
|
AllowHeaders: Array.from(cors.allowedHeaders),
|
|
AllowMethods: Array.from(cors.allowedMethods),
|
|
AllowOrigins: Array.from(cors.allowedOrigins),
|
|
ExposeHeaders:
|
|
cors.exposedResponseHeaders &&
|
|
Array.from(cors.exposedResponseHeaders),
|
|
MaxAge: cors.maxAge,
|
|
}
|
|
}
|
|
this.cfTemplate.Resources[this.provider.naming.getHttpApiLogicalId()] = {
|
|
Type: 'AWS::ApiGatewayV2::Api',
|
|
Properties: properties,
|
|
}
|
|
}
|
|
compileLogGroup() {
|
|
if (!this.config.accessLogFormat) return
|
|
|
|
const resource = {
|
|
Type: 'AWS::Logs::LogGroup',
|
|
Properties: {
|
|
LogGroupName: this.provider.naming.getHttpApiLogGroupName(),
|
|
},
|
|
}
|
|
|
|
const logRetentionInDays = this.provider.getLogRetentionInDays()
|
|
if (logRetentionInDays) {
|
|
resource.Properties.RetentionInDays = logRetentionInDays
|
|
}
|
|
|
|
const logDataProtectionPolicy = this.provider.getLogDataProtectionPolicy()
|
|
if (logDataProtectionPolicy) {
|
|
resource.Properties.DataProtectionPolicy = logDataProtectionPolicy
|
|
}
|
|
|
|
this.cfTemplate.Resources[
|
|
this.provider.naming.getHttpApiLogGroupLogicalId()
|
|
] = resource
|
|
}
|
|
compileStage() {
|
|
if (this.config.id) return
|
|
const properties = {
|
|
ApiId: { Ref: this.provider.naming.getHttpApiLogicalId() },
|
|
StageName: '$default',
|
|
AutoDeploy: true,
|
|
DefaultRouteSettings: {
|
|
DetailedMetricsEnabled: this.config.metrics,
|
|
},
|
|
}
|
|
|
|
if (this.serverless.service.provider.tags) {
|
|
properties.Tags = Object.assign({}, this.serverless.service.provider.tags)
|
|
}
|
|
|
|
const resource = (this.cfTemplate.Resources[
|
|
this.provider.naming.getHttpApiStageLogicalId()
|
|
] = {
|
|
Type: 'AWS::ApiGatewayV2::Stage',
|
|
Properties: properties,
|
|
})
|
|
if (this.config.accessLogFormat) {
|
|
properties.AccessLogSettings = {
|
|
DestinationArn: {
|
|
'Fn::GetAtt': [
|
|
this.provider.naming.getHttpApiLogGroupLogicalId(),
|
|
'Arn',
|
|
],
|
|
},
|
|
Format: this.config.accessLogFormat,
|
|
}
|
|
resource.DependsOn = this.provider.naming.getHttpApiLogGroupLogicalId()
|
|
}
|
|
this.cfTemplate.Outputs.HttpApiId = {
|
|
Description: 'Id of the HTTP API',
|
|
Value: { Ref: this.provider.naming.getHttpApiLogicalId() },
|
|
}
|
|
this.cfTemplate.Outputs.HttpApiUrl = {
|
|
Description: 'URL of the HTTP API',
|
|
Value: {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'https://',
|
|
{ Ref: this.provider.naming.getHttpApiLogicalId() },
|
|
'.execute-api.',
|
|
{ Ref: 'AWS::Region' },
|
|
'.',
|
|
{ Ref: 'AWS::URLSuffix' },
|
|
],
|
|
],
|
|
},
|
|
}
|
|
}
|
|
compileAuthorizers() {
|
|
for (const authorizer of this.config.authorizers.values()) {
|
|
const authorizerLogicalId =
|
|
this.provider.naming.getHttpApiAuthorizerLogicalId(authorizer.name)
|
|
|
|
const authorizerResource = {
|
|
Type: 'AWS::ApiGatewayV2::Authorizer',
|
|
Properties: {
|
|
ApiId: this.getApiIdConfig(),
|
|
Name: authorizer.name,
|
|
IdentitySource: Array.isArray(authorizer.identitySource)
|
|
? authorizer.identitySource
|
|
: [authorizer.identitySource],
|
|
},
|
|
}
|
|
|
|
if (authorizer.type === 'request') {
|
|
// Compile custom (request) authorizer
|
|
authorizerResource.Properties.AuthorizerType = 'REQUEST'
|
|
authorizerResource.Properties.EnableSimpleResponses =
|
|
authorizer.enableSimpleResponses
|
|
authorizerResource.Properties.AuthorizerResultTtlInSeconds =
|
|
authorizer.resultTtlInSeconds
|
|
authorizerResource.Properties.AuthorizerPayloadFormatVersion =
|
|
authorizer.payloadVersion
|
|
authorizerResource.Properties.AuthorizerUri = {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':apigateway:',
|
|
{ Ref: 'AWS::Region' },
|
|
':lambda:path/2015-03-31/functions/',
|
|
authorizer.functionArn ||
|
|
resolveLambdaTarget(
|
|
authorizer.functionName,
|
|
authorizer.functionObject,
|
|
),
|
|
'/invocations',
|
|
],
|
|
],
|
|
}
|
|
authorizerResource.DependsOn = _.get(
|
|
_.get(authorizer.functionObject, 'targetAlias'),
|
|
'logicalId',
|
|
)
|
|
|
|
// If authorizer is not managed externally, we need to make sure the correct permission is created that
|
|
// allows API Gateway to invoke authorizer function
|
|
if (!authorizer.managedExternally) {
|
|
this.compileAuthorizerLambdaPermission(authorizer)
|
|
}
|
|
} else {
|
|
// Compile JWT Authorizer
|
|
authorizerResource.Properties.AuthorizerType = 'JWT'
|
|
authorizerResource.Properties.JwtConfiguration = {
|
|
Audience: Array.from(authorizer.audience),
|
|
Issuer: authorizer.issuerUrl,
|
|
}
|
|
}
|
|
|
|
this.cfTemplate.Resources[authorizerLogicalId] = authorizerResource
|
|
}
|
|
}
|
|
|
|
compileAuthorizerLambdaPermission({
|
|
functionName,
|
|
functionArn,
|
|
name,
|
|
functionObject,
|
|
}) {
|
|
const authorizerPermissionLogicalId =
|
|
this.provider.naming.getLambdaAuthorizerHttpApiPermissionLogicalId(name)
|
|
const permissionResource = {
|
|
Type: 'AWS::Lambda::Permission',
|
|
Properties: {
|
|
FunctionName:
|
|
functionArn || resolveLambdaTarget(functionName, functionObject),
|
|
Action: 'lambda:InvokeFunction',
|
|
Principal: 'apigateway.amazonaws.com',
|
|
SourceArn: {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':execute-api:',
|
|
{ Ref: 'AWS::Region' },
|
|
':',
|
|
{ Ref: 'AWS::AccountId' },
|
|
':',
|
|
this.getApiIdConfig(),
|
|
'/*',
|
|
],
|
|
],
|
|
},
|
|
},
|
|
}
|
|
|
|
if (functionObject && functionObject.targetAlias) {
|
|
permissionResource.DependsOn = functionObject.targetAlias.logicalId
|
|
}
|
|
this.cfTemplate.Resources[authorizerPermissionLogicalId] =
|
|
permissionResource
|
|
}
|
|
|
|
compileEndpoints() {
|
|
for (const [
|
|
routeKey,
|
|
{ targetData, authorizer, authorizationScopes },
|
|
] of this.config.routes) {
|
|
this.compileLambdaPermissions(targetData)
|
|
this.compileIntegration(targetData)
|
|
const resource = (this.cfTemplate.Resources[
|
|
this.provider.naming.getHttpApiRouteLogicalId(routeKey)
|
|
] = {
|
|
Type: 'AWS::ApiGatewayV2::Route',
|
|
Properties: {
|
|
ApiId: this.getApiIdConfig(),
|
|
RouteKey: routeKey === '*' ? '$default' : routeKey,
|
|
Target: {
|
|
'Fn::Join': [
|
|
'/',
|
|
[
|
|
'integrations',
|
|
{
|
|
Ref: this.provider.naming.getHttpApiIntegrationLogicalId(
|
|
targetData.functionName,
|
|
),
|
|
},
|
|
],
|
|
],
|
|
},
|
|
},
|
|
DependsOn: this.provider.naming.getHttpApiIntegrationLogicalId(
|
|
targetData.functionName,
|
|
),
|
|
})
|
|
if (authorizer) {
|
|
const { id, type } = authorizer
|
|
|
|
const authorizationType = (() => {
|
|
if (type === 'request') {
|
|
return 'CUSTOM'
|
|
}
|
|
|
|
if (type === 'aws_iam') {
|
|
return 'AWS_IAM'
|
|
}
|
|
|
|
return 'JWT'
|
|
})()
|
|
|
|
resource.Properties.AuthorizationType = authorizationType
|
|
|
|
if (type !== 'aws_iam') {
|
|
Object.assign(resource.Properties, {
|
|
AuthorizerId: id || {
|
|
Ref: this.provider.naming.getHttpApiAuthorizerLogicalId(
|
|
authorizer.name,
|
|
),
|
|
},
|
|
AuthorizationScopes:
|
|
authorizationScopes && Array.from(authorizationScopes),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Object.defineProperties(
|
|
HttpApiEvents.prototype,
|
|
memoizeeMethods({
|
|
resolveConfiguration: d(function () {
|
|
const routes = new Map()
|
|
const providerConfig = this.serverless.service.provider
|
|
const userConfig = providerConfig.httpApi || {}
|
|
this.config = {
|
|
routes,
|
|
id: userConfig.id,
|
|
metrics: userConfig.metrics || false,
|
|
disableDefaultEndpoint: userConfig.disableDefaultEndpoint,
|
|
}
|
|
let cors = null
|
|
let shouldFillCorsMethods = false
|
|
const userCors = userConfig.cors
|
|
if (userCors) {
|
|
if (userConfig.id) {
|
|
throw new ServerlessError(
|
|
'Cannot setup CORS rules for externally configured HTTP API',
|
|
'EXTERNAL_HTTP_API_CORS_CONFIG',
|
|
)
|
|
}
|
|
cors = this.config.cors = {}
|
|
if (userConfig.cors === true) {
|
|
Object.assign(cors, defaultCors)
|
|
shouldFillCorsMethods = true
|
|
} else {
|
|
cors.allowedOrigins = userCors.allowedOrigins
|
|
? toSet(userCors.allowedOrigins)
|
|
: defaultCors.allowedOrigins
|
|
cors.allowedHeaders = userCors.allowedHeaders
|
|
? toSet(userCors.allowedHeaders)
|
|
: defaultCors.allowedHeaders
|
|
if (userCors.allowedMethods)
|
|
cors.allowedMethods = toSet(userCors.allowedMethods)
|
|
else shouldFillCorsMethods = true
|
|
if (userCors.allowCredentials) cors.allowCredentials = true
|
|
if (userCors.exposedResponseHeaders) {
|
|
cors.exposedResponseHeaders = toSet(userCors.exposedResponseHeaders)
|
|
}
|
|
cors.maxAge = userCors.maxAge
|
|
}
|
|
if (shouldFillCorsMethods) cors.allowedMethods = new Set(['OPTIONS'])
|
|
}
|
|
|
|
const userAuthorizers = userConfig.authorizers
|
|
const authorizers = (this.config.authorizers = new Map())
|
|
if (userAuthorizers) {
|
|
if (userConfig.id) {
|
|
throw new ServerlessError(
|
|
'Cannot setup authorizers for externally configured HTTP API',
|
|
'EXTERNAL_HTTP_API_AUTHORIZERS_CONFIG',
|
|
)
|
|
}
|
|
for (const [name, authorizerConfig] of Object.entries(
|
|
userAuthorizers,
|
|
)) {
|
|
let authorizerFunctionObject
|
|
|
|
if (authorizerConfig.type === 'request') {
|
|
if (
|
|
!authorizerConfig.functionArn &&
|
|
!authorizerConfig.functionName
|
|
) {
|
|
throw new ServerlessError(
|
|
`Either "functionArn" or "functionName" property needs to be set on authorizer "${name}"`,
|
|
'HTTP_API_CUSTOM_AUTHORIZER_NEITHER_FUNCTION_ARN_NOR_FUNCTION_NAME_DEFINED',
|
|
)
|
|
}
|
|
|
|
if (authorizerConfig.functionArn && authorizerConfig.functionName) {
|
|
throw new ServerlessError(
|
|
`Either "functionArn" or "functionName" (not both) property needs to be set on authorizer "${name}"`,
|
|
'HTTP_API_CUSTOM_AUTHORIZER_BOTH_FUNCTION_ARN_AND_FUNCTION_NAME_DEFINED',
|
|
)
|
|
}
|
|
|
|
if (authorizerConfig.functionName) {
|
|
try {
|
|
authorizerFunctionObject = this.serverless.service.getFunction(
|
|
authorizerConfig.functionName,
|
|
)
|
|
} catch {
|
|
throw new ServerlessError(
|
|
`Function "${authorizerConfig.functionName}" for HTTP API authorizer "${name}" not found in service.`,
|
|
'HTTP_API_CUSTOM_AUTHORIZER_FUNCTION_NOT_FOUND_IN_SERVICE',
|
|
)
|
|
}
|
|
}
|
|
|
|
if (
|
|
authorizerConfig.resultTtlInSeconds &&
|
|
!authorizerConfig.identitySource
|
|
) {
|
|
throw new ServerlessError(
|
|
`Property "identitySource" has to be set on authorizer "${name}" when "resultTtlInSeconds" is set to non-zero value.`,
|
|
'HTTP_API_CUSTOM_AUTHORIZER_IDENTITY_SOURCE_MISSING_WHEN_CACHING_ENABLED',
|
|
)
|
|
}
|
|
}
|
|
|
|
authorizers.set(name, {
|
|
name: authorizerConfig.name || name,
|
|
identitySource: authorizerConfig.identitySource || [],
|
|
issuerUrl: authorizerConfig.issuerUrl,
|
|
audience: toSet(authorizerConfig.audience),
|
|
type: authorizerConfig.type,
|
|
functionName: authorizerConfig.functionName,
|
|
functionArn: authorizerConfig.functionArn,
|
|
managedExternally: authorizerConfig.managedExternally,
|
|
resultTtlInSeconds: authorizerConfig.resultTtlInSeconds,
|
|
enableSimpleResponses: authorizerConfig.enableSimpleResponses,
|
|
payloadVersion: authorizerConfig.payloadVersion || '2.0',
|
|
functionObject: authorizerFunctionObject,
|
|
})
|
|
}
|
|
}
|
|
|
|
const userLogsConfig = providerConfig.logs && providerConfig.logs.httpApi
|
|
if (userLogsConfig) {
|
|
if (userConfig.id) {
|
|
throw new ServerlessError(
|
|
'Cannot setup access logs for externally configured HTTP API',
|
|
'EXTERNAL_HTTP_API_LOGS_CONFIG',
|
|
)
|
|
}
|
|
this.config.accessLogFormat =
|
|
userLogsConfig.format ||
|
|
`${JSON.stringify({
|
|
requestId: '$context.requestId',
|
|
ip: '$context.identity.sourceIp',
|
|
requestTime: '$context.requestTime',
|
|
httpMethod: '$context.httpMethod',
|
|
routeKey: '$context.routeKey',
|
|
status: '$context.status',
|
|
protocol: '$context.protocol',
|
|
responseLength: '$context.responseLength',
|
|
})}`
|
|
}
|
|
|
|
for (const [functionName, functionData] of Object.entries(
|
|
this.serverless.service.functions,
|
|
)) {
|
|
const routeTargetData = {
|
|
functionName,
|
|
functionAlias: functionData.targetAlias,
|
|
functionLogicalId:
|
|
this.provider.naming.getLambdaLogicalId(functionName),
|
|
}
|
|
let hasHttpApiEvents = false
|
|
for (const event of functionData.events) {
|
|
if (!event.httpApi) continue
|
|
hasHttpApiEvents = true
|
|
let method
|
|
let path
|
|
let authorizer
|
|
if (_.isObject(event.httpApi)) {
|
|
;({ method, path, authorizer } = event.httpApi)
|
|
} else {
|
|
const methodPath = String(event.httpApi)
|
|
if (methodPath === '*') {
|
|
path = '*'
|
|
} else {
|
|
;[, method, path] = methodPath.match(methodPathPattern)
|
|
}
|
|
}
|
|
path = String(path)
|
|
let routeKey
|
|
if (path === '*') {
|
|
if (method && method !== '*') {
|
|
throw new ServerlessError(
|
|
`Invalid "path" property in function ${functionName} for httpApi event in serverless.yml`,
|
|
'INVALID_HTTP_API_PATH',
|
|
)
|
|
}
|
|
routeKey = '*'
|
|
event.resolvedMethod = 'ANY'
|
|
} else {
|
|
if (!method) {
|
|
throw new ServerlessError(
|
|
`Missing "method" property in function ${functionName} for httpApi event in serverless.yml`,
|
|
'MISSING_HTTP_API_METHOD',
|
|
)
|
|
}
|
|
method = String(method).toUpperCase()
|
|
if (method === '*') {
|
|
method = 'ANY'
|
|
} else if (!allowedMethods.has(method)) {
|
|
throw new ServerlessError(
|
|
`Invalid "method" property in function ${functionName} for httpApi event in serverless.yml`,
|
|
'INVALID_HTTP_API_METHOD',
|
|
)
|
|
}
|
|
event.resolvedMethod = method
|
|
event.resolvedPath = path
|
|
routeKey = `${method} ${path}`
|
|
|
|
if (routes.has(routeKey)) {
|
|
throw new ServerlessError(
|
|
`Duplicate route '${routeKey}' configuration in function ${functionName} for httpApi event in serverless.yml`,
|
|
'DUPLICATE_HTTP_API_ROUTE',
|
|
)
|
|
}
|
|
}
|
|
const routeConfig = { targetData: routeTargetData }
|
|
if (authorizer) {
|
|
const { name, scopes, id, type } = (() => {
|
|
if (_.isObject(authorizer)) return authorizer
|
|
return { name: authorizer }
|
|
})()
|
|
|
|
if (type !== 'aws_iam' && !id && !name) {
|
|
throw new ServerlessError(
|
|
`When configuring an authorizer with type: "${
|
|
type || 'jwt'
|
|
}", property "id" or "name" has to be specified.`,
|
|
'HTTP_API_AUTHORIZER_MISSING_ID_OR_NAME',
|
|
)
|
|
}
|
|
|
|
if (type === 'aws_iam' && (name || id || scopes)) {
|
|
throw new ServerlessError(
|
|
'When configuring authorizer with type: "aws_iam", all other properties are not supported.',
|
|
'HTTP_API_AUTHORIZER_AWS_IAM_UNEXPECTED_PROPERTIES',
|
|
)
|
|
}
|
|
|
|
if (id) {
|
|
if (!userConfig.id) {
|
|
throw new ServerlessError(
|
|
`Event references external authorizer '${id}', but httpApi is part of the current stack.`,
|
|
'EXTERNAL_HTTP_API_AUTHORIZER_WITHOUT_EXTERNAL_HTTP_API',
|
|
)
|
|
}
|
|
routeConfig.authorizer = { id, type }
|
|
} else if (type === 'aws_iam') {
|
|
routeConfig.authorizer = authorizer
|
|
} else if (!authorizers.has(name)) {
|
|
throw new ServerlessError(
|
|
`Event references not configured authorizer '${name}'`,
|
|
'UNRECOGNIZED_HTTP_API_AUTHORIZER',
|
|
)
|
|
} else {
|
|
routeConfig.authorizer = authorizers.get(name)
|
|
}
|
|
if (scopes) routeConfig.authorizationScopes = toSet(scopes)
|
|
}
|
|
routes.set(routeKey, routeConfig)
|
|
if (shouldFillCorsMethods) {
|
|
if (event.resolvedMethod === 'ANY') {
|
|
for (const allowedMethod of allowedMethods) {
|
|
if (allowedMethod === 'ANY') {
|
|
continue
|
|
}
|
|
cors.allowedMethods.add(allowedMethod)
|
|
}
|
|
} else {
|
|
cors.allowedMethods.add(event.resolvedMethod)
|
|
}
|
|
}
|
|
}
|
|
if (!hasHttpApiEvents) continue
|
|
const functionTimeout =
|
|
Number(functionData.timeout) ||
|
|
Number(this.serverless.service.provider.timeout) ||
|
|
6
|
|
|
|
if (functionTimeout > 30) {
|
|
log.warning(
|
|
`Function (${functionName}) timeout setting (${functionTimeout}) is greater than ` +
|
|
'maximum allowed timeout for HTTP API endpoint (30s). ' +
|
|
'This may introduce a situation where endpoint times out ' +
|
|
'for a successful lambda invocation.',
|
|
)
|
|
} else if (functionTimeout === 30) {
|
|
log.warning(
|
|
`Function (${functionName}) timeout setting (${functionTimeout}) may not provide ` +
|
|
'enough room to process an HTTP API request (of which timeout is limited to 30s). ' +
|
|
'This may introduce a situation where endpoint times out ' +
|
|
'for a successful lambda invocation.',
|
|
)
|
|
}
|
|
}
|
|
}),
|
|
compileIntegration: d(function (routeTargetData) {
|
|
const functionConfig = this.serverless.service.getFunction(
|
|
routeTargetData.functionName,
|
|
)
|
|
const funcHttpApi = functionConfig.httpApi || {}
|
|
const providerConfig = this.serverless.service.provider
|
|
const providerHttpApi = providerConfig.httpApi || {}
|
|
|
|
const properties = {
|
|
ApiId: this.getApiIdConfig(),
|
|
IntegrationType: 'AWS_PROXY',
|
|
IntegrationUri: resolveTargetConfig(routeTargetData),
|
|
PayloadFormatVersion:
|
|
funcHttpApi.payload || providerHttpApi.payload || '2.0',
|
|
TimeoutInMillis: 30000,
|
|
}
|
|
this.cfTemplate.Resources[
|
|
this.provider.naming.getHttpApiIntegrationLogicalId(
|
|
routeTargetData.functionName,
|
|
)
|
|
] = {
|
|
Type: 'AWS::ApiGatewayV2::Integration',
|
|
DependsOn: _.get(routeTargetData.functionAlias, 'logicalId'),
|
|
Properties: properties,
|
|
}
|
|
}),
|
|
compileLambdaPermissions: d(function (routeTargetData) {
|
|
this.cfTemplate.Resources[
|
|
this.provider.naming.getLambdaHttpApiPermissionLogicalId(
|
|
routeTargetData.functionName,
|
|
)
|
|
] = {
|
|
Type: 'AWS::Lambda::Permission',
|
|
Properties: {
|
|
FunctionName: resolveTargetConfig(routeTargetData),
|
|
Action: 'lambda:InvokeFunction',
|
|
Principal: 'apigateway.amazonaws.com',
|
|
SourceArn: {
|
|
'Fn::Join': [
|
|
'',
|
|
[
|
|
'arn:',
|
|
{ Ref: 'AWS::Partition' },
|
|
':execute-api:',
|
|
{ Ref: 'AWS::Region' },
|
|
':',
|
|
{ Ref: 'AWS::AccountId' },
|
|
':',
|
|
this.getApiIdConfig(),
|
|
'/*',
|
|
],
|
|
],
|
|
},
|
|
},
|
|
DependsOn: routeTargetData.functionAlias
|
|
? routeTargetData.functionAlias.logicalId
|
|
: undefined,
|
|
}
|
|
}),
|
|
}),
|
|
)
|
|
|
|
export default HttpApiEvents
|