如何通过 API 网关和 CloudFront 使用 AWS_PROXY 模式通过 AWS Lambda 传输一个 return 二进制内容?
How can one return binary content via AWS Lambda through API Gateway and CloudFront using AWS_PROXY mode?
我如何使用 CloudFormation 使用 AWS_PROXY
集成类型配置一个 AWS API 网关,前面是 CloudFront(以便我可以将 HTTP 重定向到 HTTPS)并由 AWS Lambda 函数支持?
下面是一个 CloudFormation 模板,展示了我的尝试。它包括
- 一个简单的 Lambda 函数,returns expected output format 用于
AWS_PROXY
模式下的 Lambda。
- 这
isBase64Encoded
设置为 True。
- 一个
AWS::ApiGateway::RestApi
CloudFormation 资源,其中包含一个 BinaryMediaTypes
属性,其值为 *~1*
。
- 在this AWS doc page and the CloudFormation docs中建议设置
*/*
的二进制媒体类型解释Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list
我已经通读了这个 AWS 论坛 post,AWS_PROXY and binary responses 但还没有弄清楚我做错了什么。论坛 post 中有人 post 讨论 AWS_PROXY
模式和其他模式,所以有点混乱。
此 AWS 文档页面,Support Binary Payloads in API Gateway
, is, I believe, talking about modes other than AWS_PROXY
as it talks about setting the IntegrationResponses property which requires using a StatusCode which matches a MethodResponse StatusCode。
这是一个展示问题的 CloudFormation 模板。您可以通过以下步骤重现它
- 为您帐户中现有 Route53 区域中的 DNS 域名配置 ACM 证书
- 将域名、区域名称(以“.”字符结尾)和 ACM ARN 作为参数提供给 CloudFormation 堆栈
- 使用下面的模板启动 CloudFormation 堆栈(因为它使用 CloudFront,这可能需要 30 分钟)
curl
API网关URL
如果这一切正常,你会得到一个二进制 png HTTP 响应,而不是你得到一个 base64 响应。
AWSTemplateFormatVersion: 2010-09-09
Description: Test binary responses with AWS_PROXY mode
Parameters:
CustomDomainName:
Type: String
Description: The custom domain name to use for the API
Default: ''
# AWS::ApiGateway::DomainName can not contain any uppercase characters
AllowedPattern: '^[^A-Z]*$'
ConstraintDescription: must not contain any uppercase characters
DomainNameZone:
Type: String
Description: The Route53 DNS zone containing the custom domain name
Default: ''
CertificateArn:
Type: String
Description: The ARN of the AWS ACM Certificate for your custom domain name
Default: ''
Resources:
TestFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: AllowLambdaLogging
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
TestFunction:
Type: AWS::Lambda::Function
Properties:
Description: Test Function
Code:
ZipFile: |
def lambda_handler(event, context):
body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
return {
'headers': {'Content-Type': 'image/png'},
'statusCode': 200,
'isBase64Encoded': True,
'body': body}
Handler: index.lambda_handler
Runtime: python3.7
Role: !GetAtt TestFunctionRole.Arn
Timeout: 900
TestFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
# Let's hope that the Lambda function doesn't execute before this LogGroup
# resource is created, creating the LogGroup with no expiration and
# preventing this resource from creating
LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction' ] ]
RetentionInDays: 1
TestRoute53RecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt TestCloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
HostedZoneName: !Ref DomainNameZone
Name: !Ref CustomDomainName
Type: A
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*~1*'
Description: Test API
FailOnWarnings: true
EndpointConfiguration:
Types:
- REGIONAL
TestApiGatewayDomainName:
# The ApiGateway requires a custom domain name, despite sitting behind
# CloudFront. This is because we want to pass all ( * ) HTTP headers
# through CloudFront and onto API Gateway. If we didn't set a custom domain
# name on the API Gateway, the "Host" header passed through from CloudFront
# to API Gateway would be for the custom domain, but API Gateway, which uses
# SNI, wouldn't know which TLS certificate to use in the handshake because
# API Gateway would have no record of that Host header. This would result in
# API Gateway being unable to setup a TLS connection with the inbound
# CloudFront connection attempt, API Gateway writing no logs about this
# fact, and CloudFront returning to the user an error of
# {"message":"Forbidden"}
# If we weren't passing the "Host" header from CloudFront to API Gateway
# this resource wouldn't be needed
Type: AWS::ApiGateway::DomainName
Properties:
# Uppercase letters are not supported in DomainName
DomainName: !Ref CustomDomainName
EndpointConfiguration:
Types:
- REGIONAL
RegionalCertificateArn: !Ref CertificateArn
SecurityPolicy: TLS_1_2
TestBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
# BasePath: # Not specifying this so that we have no base path
DomainName: !Ref TestApiGatewayDomainName
RestApiId: !Ref TestApi
Stage: !Ref TestApiStage
TestLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt TestFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join [ '', [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref 'TestApi', '/*/*' ] ]
TestApiStage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !Ref TestApiDeployment
MethodSettings:
- DataTraceEnabled: true
HttpMethod: '*'
ResourcePath: /*
RestApiId: !Ref TestApi
TestApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- TestRequest
Properties:
RestApiId: !Ref TestApi
StageName: DummyStage
# Deployment with an Empty Embedded Stage
# The following instructional text is no longer present in the AWS
# documentation for AWS::ApiGateway::Deployment StageName and it's not
# clear if it still applies.
#
# "Note This property is required by API Gateway. We recommend that you
# specify a name using any value (see Examples) and that you don’t use
# this stage. We recommend not using this stage because it is tied to
# this deployment, which means you can’t delete one without deleting the
# other. For example, if you delete this deployment, API Gateway also
# deletes this stage, which you might want to keep. Instead, use the
# AWS::ApiGateway::Stage resource to create and associate a stage with
# this deployment."
TestResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApi
ParentId: !GetAtt TestApi.RootResourceId
PathPart: '{proxy+}'
TestRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestPOSTRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: POST
Integration:
Type: AWS_PROXY
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestRootRequest:
# This resource is necessary to get API Gateway to respond to requests for the '/' path
# Without it API Gateway will respond to requests for '/' with the error
# {"message":"Missing Authentication Token"}
#
#
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
# ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
#
ResourceId: !GetAtt TestApi.RootResourceId
RestApiId: !Ref TestApi
TestCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: !Join [ ':', [!Ref 'AWS::StackName', 'Test']]
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- POST
- DELETE
- OPTIONS
- PUT
- PATCH
Compress: true
DefaultTTL: 0
MinTTL: 0
MaxTTL: 0
ForwardedValues:
Cookies:
Forward: all
QueryString: true
Headers:
- '*'
TargetOriginId: TestCloudFrontOriginId
ViewerProtocolPolicy: redirect-to-https
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
DefaultRootObject: '' # "If you don't want to specify a default root object when you create a distribution, include an empty DefaultRootObject element."
Enabled: true
Aliases:
- !Ref CustomDomainName
HttpVersion: http2
IPV6Enabled: true
#Logging:
# Logging
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2
DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
Id: TestCloudFrontOriginId
# OriginPath: !Join [ '', [ '/', !Ref 'TestApiStage' ] ]
PriceClass: PriceClass_100 # US, Canada, Europe, Israel
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: TLSv1.2_2018
SslSupportMethod: sni-only
我联系了 AWS Support,来回多次后发现问题出在 AWS 文档中。
AWS::ApiGateway::RestApi
CloudFormation resource type 上的文档页面有错误的表述
Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list.
事实证明这不是真的,您应该在 BinaryMediaTypes
字段中输入的值是 */*
而不是 *~1*
。这使得字段看起来像这样
Resources:
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*/*'
因此,通过对问题中的模板进行更改,生成的堆栈可以正确地提供二进制资源。
我已经确认此 AWS 文档页面 Support Binary Payloads in API Gateway
确实在谈论 AWS_PROXY
以外的模式,并且不适用于我的问题。
我确认有效的修复模板是这个
AWSTemplateFormatVersion: 2010-09-09
Description: Test binary responses with AWS_PROXY mode
Parameters:
CustomDomainName:
Type: String
Description: The custom domain name to use for the API
Default: ''
# AWS::ApiGateway::DomainName can not contain any uppercase characters
AllowedPattern: '^[^A-Z]*$'
ConstraintDescription: must not contain any uppercase characters
DomainNameZone:
Type: String
Description: The Route53 DNS zone containing the custom domain name
Default: ''
CertificateArn:
Type: String
Description: The ARN of the AWS ACM Certificate for your custom domain name
Default: ''
Resources:
TestFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: AllowLambdaLogging
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
TestFunction:
Type: AWS::Lambda::Function
Properties:
Description: Test Function
Code:
ZipFile: |
def lambda_handler(event, context):
body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
return {
'headers': {'Content-Type': 'image/png'},
'statusCode': 200,
'isBase64Encoded': True,
'body': body}
Handler: index.lambda_handler
Runtime: python3.7
Role: !GetAtt TestFunctionRole.Arn
Timeout: 900
TestFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
# Let's hope that the Lambda function doesn't execute before this LogGroup
# resource is created, creating the LogGroup with no expiration and
# preventing this resource from creating
LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction' ] ]
RetentionInDays: 1
TestRoute53RecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt TestCloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
HostedZoneName: !Ref DomainNameZone
Name: !Ref CustomDomainName
Type: A
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*/*'
Description: Test API
FailOnWarnings: true
EndpointConfiguration:
Types:
- REGIONAL
TestApiGatewayDomainName:
# The ApiGateway requires a custom domain name, despite sitting behind
# CloudFront. This is because we want to pass all ( * ) HTTP headers
# through CloudFront and onto API Gateway. If we didn't set a custom domain
# name on the API Gateway, the "Host" header passed through from CloudFront
# to API Gateway would be for the custom domain, but API Gateway, which uses
# SNI, wouldn't know which TLS certificate to use in the handshake because
# API Gateway would have no record of that Host header. This would result in
# API Gateway being unable to setup a TLS connection with the inbound
# CloudFront connection attempt, API Gateway writing no logs about this
# fact, and CloudFront returning to the user an error of
# {"message":"Forbidden"}
# If we weren't passing the "Host" header from CloudFront to API Gateway
# this resource wouldn't be needed
Type: AWS::ApiGateway::DomainName
Properties:
# Uppercase letters are not supported in DomainName
DomainName: !Ref CustomDomainName
EndpointConfiguration:
Types:
- REGIONAL
RegionalCertificateArn: !Ref CertificateArn
SecurityPolicy: TLS_1_2
TestBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
# BasePath: # Not specifying this so that we have no base path
DomainName: !Ref TestApiGatewayDomainName
RestApiId: !Ref TestApi
Stage: !Ref TestApiStage
TestLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt TestFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join [ '', [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref 'TestApi', '/*/*' ] ]
TestApiStage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !Ref TestApiDeployment
MethodSettings:
- DataTraceEnabled: true
HttpMethod: '*'
ResourcePath: /*
RestApiId: !Ref TestApi
TestApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- TestRequest
Properties:
RestApiId: !Ref TestApi
StageName: DummyStage
# Deployment with an Empty Embedded Stage
# The following instructional text is no longer present in the AWS
# documentation for AWS::ApiGateway::Deployment StageName and it's not
# clear if it still applies.
#
# "Note This property is required by API Gateway. We recommend that you
# specify a name using any value (see Examples) and that you don’t use
# this stage. We recommend not using this stage because it is tied to
# this deployment, which means you can’t delete one without deleting the
# other. For example, if you delete this deployment, API Gateway also
# deletes this stage, which you might want to keep. Instead, use the
# AWS::ApiGateway::Stage resource to create and associate a stage with
# this deployment."
TestResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApi
ParentId: !GetAtt TestApi.RootResourceId
PathPart: '{proxy+}'
TestRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestPOSTRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: POST
Integration:
Type: AWS_PROXY
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestRootRequest:
# This resource is necessary to get API Gateway to respond to requests for the '/' path
# Without it API Gateway will respond to requests for '/' with the error
# {"message":"Missing Authentication Token"}
#
#
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
# ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
#
ResourceId: !GetAtt TestApi.RootResourceId
RestApiId: !Ref TestApi
TestCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: !Join [ ':', [!Ref 'AWS::StackName', 'Test']]
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- POST
- DELETE
- OPTIONS
- PUT
- PATCH
Compress: true
DefaultTTL: 0
MinTTL: 0
MaxTTL: 0
ForwardedValues:
Cookies:
Forward: all
QueryString: true
Headers:
- '*'
TargetOriginId: TestCloudFrontOriginId
ViewerProtocolPolicy: redirect-to-https
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
DefaultRootObject: '' # "If you don't want to specify a default root object when you create a distribution, include an empty DefaultRootObject element."
Enabled: true
Aliases:
- !Ref CustomDomainName
HttpVersion: http2
IPV6Enabled: true
#Logging:
# Logging
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2
DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
Id: TestCloudFrontOriginId
# OriginPath: !Join [ '', [ '/', !Ref 'TestApiStage' ] ]
PriceClass: PriceClass_100 # US, Canada, Europe, Israel
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: TLSv1.2_2018
SslSupportMethod: sni-only
如果 AWS 更新此文档页面以解决此问题,您可以查看原始页面 here
我如何使用 CloudFormation 使用 AWS_PROXY
集成类型配置一个 AWS API 网关,前面是 CloudFront(以便我可以将 HTTP 重定向到 HTTPS)并由 AWS Lambda 函数支持?
下面是一个 CloudFormation 模板,展示了我的尝试。它包括
- 一个简单的 Lambda 函数,returns expected output format 用于
AWS_PROXY
模式下的 Lambda。- 这
isBase64Encoded
设置为 True。
- 这
- 一个
AWS::ApiGateway::RestApi
CloudFormation 资源,其中包含一个BinaryMediaTypes
属性,其值为*~1*
。- 在this AWS doc page and the CloudFormation docs中建议设置
*/*
的二进制媒体类型解释Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list
- 在this AWS doc page and the CloudFormation docs中建议设置
我已经通读了这个 AWS 论坛 post,AWS_PROXY and binary responses 但还没有弄清楚我做错了什么。论坛 post 中有人 post 讨论 AWS_PROXY
模式和其他模式,所以有点混乱。
此 AWS 文档页面,Support Binary Payloads in API Gateway
, is, I believe, talking about modes other than AWS_PROXY
as it talks about setting the IntegrationResponses property which requires using a StatusCode which matches a MethodResponse StatusCode。
这是一个展示问题的 CloudFormation 模板。您可以通过以下步骤重现它
- 为您帐户中现有 Route53 区域中的 DNS 域名配置 ACM 证书
- 将域名、区域名称(以“.”字符结尾)和 ACM ARN 作为参数提供给 CloudFormation 堆栈
- 使用下面的模板启动 CloudFormation 堆栈(因为它使用 CloudFront,这可能需要 30 分钟)
curl
API网关URL
如果这一切正常,你会得到一个二进制 png HTTP 响应,而不是你得到一个 base64 响应。
AWSTemplateFormatVersion: 2010-09-09
Description: Test binary responses with AWS_PROXY mode
Parameters:
CustomDomainName:
Type: String
Description: The custom domain name to use for the API
Default: ''
# AWS::ApiGateway::DomainName can not contain any uppercase characters
AllowedPattern: '^[^A-Z]*$'
ConstraintDescription: must not contain any uppercase characters
DomainNameZone:
Type: String
Description: The Route53 DNS zone containing the custom domain name
Default: ''
CertificateArn:
Type: String
Description: The ARN of the AWS ACM Certificate for your custom domain name
Default: ''
Resources:
TestFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: AllowLambdaLogging
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
TestFunction:
Type: AWS::Lambda::Function
Properties:
Description: Test Function
Code:
ZipFile: |
def lambda_handler(event, context):
body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
return {
'headers': {'Content-Type': 'image/png'},
'statusCode': 200,
'isBase64Encoded': True,
'body': body}
Handler: index.lambda_handler
Runtime: python3.7
Role: !GetAtt TestFunctionRole.Arn
Timeout: 900
TestFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
# Let's hope that the Lambda function doesn't execute before this LogGroup
# resource is created, creating the LogGroup with no expiration and
# preventing this resource from creating
LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction' ] ]
RetentionInDays: 1
TestRoute53RecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt TestCloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
HostedZoneName: !Ref DomainNameZone
Name: !Ref CustomDomainName
Type: A
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*~1*'
Description: Test API
FailOnWarnings: true
EndpointConfiguration:
Types:
- REGIONAL
TestApiGatewayDomainName:
# The ApiGateway requires a custom domain name, despite sitting behind
# CloudFront. This is because we want to pass all ( * ) HTTP headers
# through CloudFront and onto API Gateway. If we didn't set a custom domain
# name on the API Gateway, the "Host" header passed through from CloudFront
# to API Gateway would be for the custom domain, but API Gateway, which uses
# SNI, wouldn't know which TLS certificate to use in the handshake because
# API Gateway would have no record of that Host header. This would result in
# API Gateway being unable to setup a TLS connection with the inbound
# CloudFront connection attempt, API Gateway writing no logs about this
# fact, and CloudFront returning to the user an error of
# {"message":"Forbidden"}
# If we weren't passing the "Host" header from CloudFront to API Gateway
# this resource wouldn't be needed
Type: AWS::ApiGateway::DomainName
Properties:
# Uppercase letters are not supported in DomainName
DomainName: !Ref CustomDomainName
EndpointConfiguration:
Types:
- REGIONAL
RegionalCertificateArn: !Ref CertificateArn
SecurityPolicy: TLS_1_2
TestBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
# BasePath: # Not specifying this so that we have no base path
DomainName: !Ref TestApiGatewayDomainName
RestApiId: !Ref TestApi
Stage: !Ref TestApiStage
TestLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt TestFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join [ '', [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref 'TestApi', '/*/*' ] ]
TestApiStage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !Ref TestApiDeployment
MethodSettings:
- DataTraceEnabled: true
HttpMethod: '*'
ResourcePath: /*
RestApiId: !Ref TestApi
TestApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- TestRequest
Properties:
RestApiId: !Ref TestApi
StageName: DummyStage
# Deployment with an Empty Embedded Stage
# The following instructional text is no longer present in the AWS
# documentation for AWS::ApiGateway::Deployment StageName and it's not
# clear if it still applies.
#
# "Note This property is required by API Gateway. We recommend that you
# specify a name using any value (see Examples) and that you don’t use
# this stage. We recommend not using this stage because it is tied to
# this deployment, which means you can’t delete one without deleting the
# other. For example, if you delete this deployment, API Gateway also
# deletes this stage, which you might want to keep. Instead, use the
# AWS::ApiGateway::Stage resource to create and associate a stage with
# this deployment."
TestResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApi
ParentId: !GetAtt TestApi.RootResourceId
PathPart: '{proxy+}'
TestRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestPOSTRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: POST
Integration:
Type: AWS_PROXY
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestRootRequest:
# This resource is necessary to get API Gateway to respond to requests for the '/' path
# Without it API Gateway will respond to requests for '/' with the error
# {"message":"Missing Authentication Token"}
#
#
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
# ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
#
ResourceId: !GetAtt TestApi.RootResourceId
RestApiId: !Ref TestApi
TestCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: !Join [ ':', [!Ref 'AWS::StackName', 'Test']]
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- POST
- DELETE
- OPTIONS
- PUT
- PATCH
Compress: true
DefaultTTL: 0
MinTTL: 0
MaxTTL: 0
ForwardedValues:
Cookies:
Forward: all
QueryString: true
Headers:
- '*'
TargetOriginId: TestCloudFrontOriginId
ViewerProtocolPolicy: redirect-to-https
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
DefaultRootObject: '' # "If you don't want to specify a default root object when you create a distribution, include an empty DefaultRootObject element."
Enabled: true
Aliases:
- !Ref CustomDomainName
HttpVersion: http2
IPV6Enabled: true
#Logging:
# Logging
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2
DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
Id: TestCloudFrontOriginId
# OriginPath: !Join [ '', [ '/', !Ref 'TestApiStage' ] ]
PriceClass: PriceClass_100 # US, Canada, Europe, Israel
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: TLSv1.2_2018
SslSupportMethod: sni-only
我联系了 AWS Support,来回多次后发现问题出在 AWS 文档中。
AWS::ApiGateway::RestApi
CloudFormation resource type 上的文档页面有错误的表述
Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list.
事实证明这不是真的,您应该在 BinaryMediaTypes
字段中输入的值是 */*
而不是 *~1*
。这使得字段看起来像这样
Resources:
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*/*'
因此,通过对问题中的模板进行更改,生成的堆栈可以正确地提供二进制资源。
我已经确认此 AWS 文档页面 Support Binary Payloads in API Gateway
确实在谈论 AWS_PROXY
以外的模式,并且不适用于我的问题。
我确认有效的修复模板是这个
AWSTemplateFormatVersion: 2010-09-09
Description: Test binary responses with AWS_PROXY mode
Parameters:
CustomDomainName:
Type: String
Description: The custom domain name to use for the API
Default: ''
# AWS::ApiGateway::DomainName can not contain any uppercase characters
AllowedPattern: '^[^A-Z]*$'
ConstraintDescription: must not contain any uppercase characters
DomainNameZone:
Type: String
Description: The Route53 DNS zone containing the custom domain name
Default: ''
CertificateArn:
Type: String
Description: The ARN of the AWS ACM Certificate for your custom domain name
Default: ''
Resources:
TestFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: AllowLambdaLogging
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
TestFunction:
Type: AWS::Lambda::Function
Properties:
Description: Test Function
Code:
ZipFile: |
def lambda_handler(event, context):
body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
return {
'headers': {'Content-Type': 'image/png'},
'statusCode': 200,
'isBase64Encoded': True,
'body': body}
Handler: index.lambda_handler
Runtime: python3.7
Role: !GetAtt TestFunctionRole.Arn
Timeout: 900
TestFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
# Let's hope that the Lambda function doesn't execute before this LogGroup
# resource is created, creating the LogGroup with no expiration and
# preventing this resource from creating
LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction' ] ]
RetentionInDays: 1
TestRoute53RecordSet:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt TestCloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
HostedZoneName: !Ref DomainNameZone
Name: !Ref CustomDomainName
Type: A
TestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: Test
BinaryMediaTypes:
- '*/*'
Description: Test API
FailOnWarnings: true
EndpointConfiguration:
Types:
- REGIONAL
TestApiGatewayDomainName:
# The ApiGateway requires a custom domain name, despite sitting behind
# CloudFront. This is because we want to pass all ( * ) HTTP headers
# through CloudFront and onto API Gateway. If we didn't set a custom domain
# name on the API Gateway, the "Host" header passed through from CloudFront
# to API Gateway would be for the custom domain, but API Gateway, which uses
# SNI, wouldn't know which TLS certificate to use in the handshake because
# API Gateway would have no record of that Host header. This would result in
# API Gateway being unable to setup a TLS connection with the inbound
# CloudFront connection attempt, API Gateway writing no logs about this
# fact, and CloudFront returning to the user an error of
# {"message":"Forbidden"}
# If we weren't passing the "Host" header from CloudFront to API Gateway
# this resource wouldn't be needed
Type: AWS::ApiGateway::DomainName
Properties:
# Uppercase letters are not supported in DomainName
DomainName: !Ref CustomDomainName
EndpointConfiguration:
Types:
- REGIONAL
RegionalCertificateArn: !Ref CertificateArn
SecurityPolicy: TLS_1_2
TestBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
# BasePath: # Not specifying this so that we have no base path
DomainName: !Ref TestApiGatewayDomainName
RestApiId: !Ref TestApi
Stage: !Ref TestApiStage
TestLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName: !GetAtt TestFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn: !Join [ '', [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref 'TestApi', '/*/*' ] ]
TestApiStage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !Ref TestApiDeployment
MethodSettings:
- DataTraceEnabled: true
HttpMethod: '*'
ResourcePath: /*
RestApiId: !Ref TestApi
TestApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- TestRequest
Properties:
RestApiId: !Ref TestApi
StageName: DummyStage
# Deployment with an Empty Embedded Stage
# The following instructional text is no longer present in the AWS
# documentation for AWS::ApiGateway::Deployment StageName and it's not
# clear if it still applies.
#
# "Note This property is required by API Gateway. We recommend that you
# specify a name using any value (see Examples) and that you don’t use
# this stage. We recommend not using this stage because it is tied to
# this deployment, which means you can’t delete one without deleting the
# other. For example, if you delete this deployment, API Gateway also
# deletes this stage, which you might want to keep. Instead, use the
# AWS::ApiGateway::Stage resource to create and associate a stage with
# this deployment."
TestResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApi
ParentId: !GetAtt TestApi.RootResourceId
PathPart: '{proxy+}'
TestRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestPOSTRequest:
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: POST
Integration:
Type: AWS_PROXY
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
ResourceId: !Ref TestResource
RestApiId: !Ref TestApi
TestRootRequest:
# This resource is necessary to get API Gateway to respond to requests for the '/' path
# Without it API Gateway will respond to requests for '/' with the error
# {"message":"Missing Authentication Token"}
#
#
DependsOn: TestLambdaPermission
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
# IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
# https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# "For Lambda integrations, you must use the HTTP method of POST for the
# integration request, according to the specification of the Lambda service
# action for function invocations."
IntegrationHttpMethod: POST
Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
# ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
#
ResourceId: !GetAtt TestApi.RootResourceId
RestApiId: !Ref TestApi
TestCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: !Join [ ':', [!Ref 'AWS::StackName', 'Test']]
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- POST
- DELETE
- OPTIONS
- PUT
- PATCH
Compress: true
DefaultTTL: 0
MinTTL: 0
MaxTTL: 0
ForwardedValues:
Cookies:
Forward: all
QueryString: true
Headers:
- '*'
TargetOriginId: TestCloudFrontOriginId
ViewerProtocolPolicy: redirect-to-https
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
DefaultRootObject: '' # "If you don't want to specify a default root object when you create a distribution, include an empty DefaultRootObject element."
Enabled: true
Aliases:
- !Ref CustomDomainName
HttpVersion: http2
IPV6Enabled: true
#Logging:
# Logging
Origins:
- CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2
DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
Id: TestCloudFrontOriginId
# OriginPath: !Join [ '', [ '/', !Ref 'TestApiStage' ] ]
PriceClass: PriceClass_100 # US, Canada, Europe, Israel
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: TLSv1.2_2018
SslSupportMethod: sni-only
如果 AWS 更新此文档页面以解决此问题,您可以查看原始页面 here