如何通过AWS Lambda通过AWS_PROXY模式通过API Gateway和CloudFront返回二进制内容?

huangapple go评论195阅读模式
英文:

How can one return binary content via AWS Lambda through API Gateway and CloudFront using AWS_PROXY mode?

问题

如何使用CloudFormation创建一个由CloudFront前端和AWS Lambda函数后端支持的AWS API Gateway(以便进行HTTP到HTTPS重定向),并使用AWS_PROXY集成类型?

以下是一个CloudFormation模板示例,展示了我尝试过的内容。它包括:

  • 一个简单的Lambda函数,该函数以AWS_PROXY模式返回Lambda的预期输出格式。这个输出格式中isBase64Encoded设置为True。
  • 一个AWS::ApiGateway::RestApi CloudFormation资源,其中包含一个BinaryMediaTypes属性,其值为*~1*。在此AWS文档页面CloudFormation文档中建议将二进制媒体类型设置为*/*,并解释了Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list

我已阅读了这篇AWS论坛帖子,AWS_PROXY和二进制响应,但尚未弄清楚我的问题所在。该论坛帖子包括有关AWS_PROXY模式以及其他模式的帖子,因此有点令人困惑。

这篇AWS文档页面,在API Gateway中支持二进制负载,我认为是在讨论AWS_PROXY以外的模式,因为它提到设置IntegrationResponses属性,该属性要求使用与MethodResponse StatusCode匹配的StatusCode。

以下是一个展示问题的CloudFormation模板。您可以按照以下步骤重现它:

  1. 为您的帐户中现有的Route53区域中的DNS域名创建ACM证书。
  2. 将域名、以“.”字符结尾的区域名称以及ACM ARN作为CloudFormation堆栈的参数。
  3. 使用下面的模板启动CloudFormation堆栈(因为它使用了CloudFront,这可能需要30分钟)。
  4. 使用API Gateway的URL进行curl请求。

如果这个过程正确执行,您应该会收到一个二进制png的HTTP响应,而不是一个base64响应。

  1. # 在这里是您的CloudFormation模板,包括Lambda函数、API Gateway和CloudFront的定义

希望这有助于您创建所需的AWS架构。

英文:

How can I provision an AWS API Gateway, fronted by CloudFront (so that I can have HTTP to HTTPS redirects) and backed by an AWS Lambda function using the AWS_PROXY integration type using CloudFormation?

Below is a CloudFormation template showing what I've tried. It includes

  • A simple Lambda function which returns the expected output format for Lambda in AWS_PROXY mode.
    • This has isBase64Encoded set to True.
  • A AWS::ApiGateway::RestApi CloudFormation resource that includes a BinaryMediaTypes property containing a value of *~1*.
    • Setting the Binary Media Type of */* is suggested in this AWS doc page and the CloudFormation docs explain that Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list

I've read through this AWS Forum post, AWS_PROXY and binary responses but haven't figured out what I'm doing wrong. The forum post includes people posting both about AWS_PROXY mode as well as other modes so it gets a bit confusing.

This AWS doc page, 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.

Here is a CloudFormation template that exhibits the problem. You can reproduce it with these steps

  1. Provision an ACM certificate for a DNS domain name in an existing Route53 zone in your account
  2. Provide the domain name, the zone name (ending in a "." character) and the ACM ARN as parameters to the CloudFormation stack
  3. Spin up the CloudFormation stack using the template below (because it uses CloudFront this can take 30 minutes)
  4. curl the URL of the API Gateway

If this worked correctly you'd get back a binary png HTTP response, instead you get back a base64 response.

  1. AWSTemplateFormatVersion: 2010-09-09
  2. Description: Test binary responses with AWS_PROXY mode
  3. Parameters:
  4. CustomDomainName:
  5. Type: String
  6. Description: The custom domain name to use for the API
  7. Default: ''
  8. # AWS::ApiGateway::DomainName can not contain any uppercase characters
  9. AllowedPattern: '^[^A-Z]*$'
  10. ConstraintDescription: must not contain any uppercase characters
  11. DomainNameZone:
  12. Type: String
  13. Description: The Route53 DNS zone containing the custom domain name
  14. Default: ''
  15. CertificateArn:
  16. Type: String
  17. Description: The ARN of the AWS ACM Certificate for your custom domain name
  18. Default: ''
  19. Resources:
  20. TestFunctionRole:
  21. Type: AWS::IAM::Role
  22. Properties:
  23. AssumeRolePolicyDocument:
  24. Version: 2012-10-17
  25. Statement:
  26. - Effect: Allow
  27. Principal:
  28. Service:
  29. - lambda.amazonaws.com
  30. Action:
  31. - sts:AssumeRole
  32. Policies:
  33. - PolicyName: AllowLambdaLogging
  34. PolicyDocument:
  35. Version: 2012-10-17
  36. Statement:
  37. - Effect: Allow
  38. Action:
  39. - logs:CreateLogGroup
  40. - logs:CreateLogStream
  41. - logs:PutLogEvents
  42. Resource: '*'
  43. TestFunction:
  44. Type: AWS::Lambda::Function
  45. Properties:
  46. Description: Test Function
  47. Code:
  48. ZipFile: |
  49. def lambda_handler(event, context):
  50. body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
  51. return {
  52. 'headers': {'Content-Type': 'image/png'},
  53. 'statusCode': 200,
  54. 'isBase64Encoded': True,
  55. 'body': body}
  56. Handler: index.lambda_handler
  57. Runtime: python3.7
  58. Role: !GetAtt TestFunctionRole.Arn
  59. Timeout: 900
  60. TestFunctionLogGroup:
  61. Type: AWS::Logs::LogGroup
  62. Properties:
  63. # Let's hope that the Lambda function doesn't execute before this LogGroup
  64. # resource is created, creating the LogGroup with no expiration and
  65. # preventing this resource from creating
  66. LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction' ] ]
  67. RetentionInDays: 1
  68. TestRoute53RecordSet:
  69. Type: AWS::Route53::RecordSet
  70. Properties:
  71. AliasTarget:
  72. DNSName: !GetAtt TestCloudFrontDistribution.DomainName
  73. HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
  74. Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
  75. HostedZoneName: !Ref DomainNameZone
  76. Name: !Ref CustomDomainName
  77. Type: A
  78. TestApi:
  79. Type: AWS::ApiGateway::RestApi
  80. Properties:
  81. Name: Test
  82. BinaryMediaTypes:
  83. - '*~1*'
  84. Description: Test API
  85. FailOnWarnings: true
  86. EndpointConfiguration:
  87. Types:
  88. - REGIONAL
  89. TestApiGatewayDomainName:
  90. # The ApiGateway requires a custom domain name, despite sitting behind
  91. # CloudFront. This is because we want to pass all ( * ) HTTP headers
  92. # through CloudFront and onto API Gateway. If we didn't set a custom domain
  93. # name on the API Gateway, the "Host" header passed through from CloudFront
  94. # to API Gateway would be for the custom domain, but API Gateway, which uses
  95. # SNI, wouldn't know which TLS certificate to use in the handshake because
  96. # API Gateway would have no record of that Host header. This would result in
  97. # API Gateway being unable to setup a TLS connection with the inbound
  98. # CloudFront connection attempt, API Gateway writing no logs about this
  99. # fact, and CloudFront returning to the user an error of
  100. # {"message":"Forbidden"}
  101. # If we weren't passing the "Host" header from CloudFront to API Gateway
  102. # this resource wouldn't be needed
  103. Type: AWS::ApiGateway::DomainName
  104. Properties:
  105. # Uppercase letters are not supported in DomainName
  106. DomainName: !Ref CustomDomainName
  107. EndpointConfiguration:
  108. Types:
  109. - REGIONAL
  110. RegionalCertificateArn: !Ref CertificateArn
  111. SecurityPolicy: TLS_1_2
  112. TestBasePathMapping:
  113. Type: AWS::ApiGateway::BasePathMapping
  114. Properties:
  115. # BasePath: # Not specifying this so that we have no base path
  116. DomainName: !Ref TestApiGatewayDomainName
  117. RestApiId: !Ref TestApi
  118. Stage: !Ref TestApiStage
  119. TestLambdaPermission:
  120. Type: AWS::Lambda::Permission
  121. Properties:
  122. Action: lambda:invokeFunction
  123. FunctionName: !GetAtt TestFunction.Arn
  124. Principal: apigateway.amazonaws.com
  125. SourceArn: !Join [ '', [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref 'TestApi', '/*/*' ] ]
  126. TestApiStage:
  127. Type: AWS::ApiGateway::Stage
  128. Properties:
  129. DeploymentId: !Ref TestApiDeployment
  130. MethodSettings:
  131. - DataTraceEnabled: true
  132. HttpMethod: '*'
  133. ResourcePath: /*
  134. RestApiId: !Ref TestApi
  135. TestApiDeployment:
  136. Type: AWS::ApiGateway::Deployment
  137. DependsOn:
  138. - TestRequest
  139. Properties:
  140. RestApiId: !Ref TestApi
  141. StageName: DummyStage
  142. # Deployment with an Empty Embedded Stage
  143. # The following instructional text is no longer present in the AWS
  144. # documentation for AWS::ApiGateway::Deployment StageName and it's not
  145. # clear if it still applies.
  146. #
  147. # "Note This property is required by API Gateway. We recommend that you
  148. # specify a name using any value (see Examples) and that you don’t use
  149. # this stage. We recommend not using this stage because it is tied to
  150. # this deployment, which means you can’t delete one without deleting the
  151. # other. For example, if you delete this deployment, API Gateway also
  152. # deletes this stage, which you might want to keep. Instead, use the
  153. # AWS::ApiGateway::Stage resource to create and associate a stage with
  154. # this deployment."
  155. TestResource:
  156. Type: AWS::ApiGateway::Resource
  157. Properties:
  158. RestApiId: !Ref TestApi
  159. ParentId: !GetAtt TestApi.RootResourceId
  160. PathPart: '{proxy+}'
  161. TestRequest:
  162. DependsOn: TestLambdaPermission
  163. Type: AWS::ApiGateway::Method
  164. Properties:
  165. AuthorizationType: NONE
  166. HttpMethod: GET
  167. Integration:
  168. Type: AWS_PROXY
  169. # IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
  170. # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
  171. # "For Lambda integrations, you must use the HTTP method of POST for the
  172. # integration request, according to the specification of the Lambda service
  173. # action for function invocations."
  174. IntegrationHttpMethod: POST
  175. Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
  176. ResourceId: !Ref TestResource
  177. RestApiId: !Ref TestApi
  178. TestPOSTRequest:
  179. DependsOn: TestLambdaPermission
  180. Type: AWS::ApiGateway::Method
  181. Properties:
  182. AuthorizationType: NONE
  183. HttpMethod: POST
  184. Integration:
  185. Type: AWS_PROXY
  186. # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
  187. # "For Lambda integrations, you must use the HTTP method of POST for the
  188. # integration request, according to the specification of the Lambda service
  189. # action for function invocations."
  190. IntegrationHttpMethod: POST
  191. Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
  192. ResourceId: !Ref TestResource
  193. RestApiId: !Ref TestApi
  194. TestRootRequest:
  195. # This resource is necessary to get API Gateway to respond to requests for the '/' path
  196. # Without it API Gateway will respond to requests for '/' with the error
  197. # {"message":"Missing Authentication Token"}
  198. # https://stackoverflow.com/q/46578615/168874
  199. # https://stackoverflow.com/q/52909329/168874
  200. DependsOn: TestLambdaPermission
  201. Type: AWS::ApiGateway::Method
  202. Properties:
  203. AuthorizationType: NONE
  204. HttpMethod: GET
  205. Integration:
  206. Type: AWS_PROXY
  207. # IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
  208. # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
  209. # "For Lambda integrations, you must use the HTTP method of POST for the
  210. # integration request, according to the specification of the Lambda service
  211. # action for function invocations."
  212. IntegrationHttpMethod: POST
  213. Uri: !Join [ '', [ 'arn:aws:apigateway:', !Ref 'AWS::Region', ':lambda:path/2015-03-31/functions/', !GetAtt 'TestFunction.Arn', '/invocations' ] ]
  214. # ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
  215. # https://stackoverflow.com/a/56121914/168874
  216. ResourceId: !GetAtt TestApi.RootResourceId
  217. RestApiId: !Ref TestApi
  218. TestCloudFrontDistribution:
  219. Type: AWS::CloudFront::Distribution
  220. Properties:
  221. DistributionConfig:
  222. Comment: !Join [ ':', [!Ref 'AWS::StackName', 'Test']]
  223. DefaultCacheBehavior:
  224. AllowedMethods:
  225. - GET
  226. - HEAD
  227. - POST
  228. - DELETE
  229. - OPTIONS
  230. - PUT
  231. - PATCH
  232. Compress: true
  233. DefaultTTL: 0
  234. MinTTL: 0
  235. MaxTTL: 0
  236. ForwardedValues:
  237. Cookies:
  238. Forward: all
  239. QueryString: true
  240. Headers:
  241. - '*'
  242. TargetOriginId: TestCloudFrontOriginId
  243. ViewerProtocolPolicy: redirect-to-https
  244. # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
  245. DefaultRootObject: '' # "If you don't want to specify a default root object when you create a distribution, include an empty DefaultRootObject element."
  246. Enabled: true
  247. Aliases:
  248. - !Ref CustomDomainName
  249. HttpVersion: http2
  250. IPV6Enabled: true
  251. #Logging:
  252. # Logging
  253. Origins:
  254. - CustomOriginConfig:
  255. OriginProtocolPolicy: https-only
  256. OriginSSLProtocols:
  257. - TLSv1.2
  258. DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
  259. Id: TestCloudFrontOriginId
  260. # OriginPath: !Join [ '', [ '/', !Ref 'TestApiStage' ] ]
  261. PriceClass: PriceClass_100 # US, Canada, Europe, Israel
  262. ViewerCertificate:
  263. AcmCertificateArn: !Ref CertificateArn
  264. MinimumProtocolVersion: TLSv1.2_2018
  265. SslSupportMethod: sni-only

答案1

得分: 2

我联系了AWS支持,经过多次来回沟通,发现问题出在AWS文档中。

AWS::ApiGateway::RestApi CloudFormation资源类型的文档页面错误地说明:

> 斜杠必须用~1进行转义。例如,image/png应该在BinaryMediaTypes列表中写为image~1png。

事实证明这是不正确的,您应该在BinaryMediaTypes字段中放置的值是*/*,而不是*~1*。这使得字段看起来像这样:

  1. Resources:
  2. TestApi:
  3. Type: AWS::ApiGateway::RestApi
  4. Properties:
  5. Name: Test
  6. BinaryMediaTypes:
  7. - '*/*'

所以,通过对问题中的模板进行这个更改,生成的堆栈将正确地提供二进制资源。

我已确认此AWS文档页面 Support Binary Payloads in API Gateway 确实讨论的是AWS_PROXY以外的模式,并且不适用于我的问题。

已确认有效的带有修复的模板如下:

  1. AWSTemplateFormatVersion: 2010-09-09
  2. Description: Test binary responses with AWS_PROXY mode
  3. Parameters:
  4. CustomDomainName:
  5. Type: String
  6. Description: The custom domain name to use for the API
  7. Default: ''
  8. # AWS::ApiGateway::DomainName can not contain any uppercase characters
  9. AllowedPattern: '^[^A-Z]*$'
  10. ConstraintDescription: must not contain any uppercase characters
  11. DomainNameZone:
  12. Type: String
  13. Description: The Route53 DNS zone containing the custom domain name
  14. Default: ''
  15. CertificateArn:
  16. Type: String
  17. Description: The ARN of the AWS ACM Certificate for your custom domain name
  18. Default: ''
  19. Resources:
  20. TestFunctionRole:
  21. Type: AWS::IAM::Role
  22. Properties:
  23. AssumeRolePolicyDocument:
  24. Version: 2012-10-17
  25. Statement:
  26. - Effect: Allow
  27. Principal:
  28. Service:
  29. - lambda.amazonaws.com
  30. Action:
  31. - sts:AssumeRole
  32. Policies:
  33. - PolicyName: AllowLambdaLogging
  34. PolicyDocument:
  35. Version: 2012-10-17
  36. Statement:
  37. - Effect: Allow
  38. Action:
  39. - logs:CreateLogGroup
  40. - logs:CreateLogStream
  41. - logs:PutLogEvents
  42. Resource: '*'
  43. TestFunction:
  44. Type: AWS::Lambda::Function
  45. Properties:
  46. Description: Test Function
  47. Code:
  48. ZipFile: |
  49. def lambda_handler(event, context):
  50. body = 'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII='
  51. return {
  52. 'headers': {'Content-Type': 'image/png'},
  53. 'statusCode': 200,
  54. 'isBase64Encoded': True,
  55. 'body': body}
  56. Handler: index.lambda_handler
  57. Runtime: python3.7
  58. Role: !GetAtt TestFunctionRole.Arn
  59. Timeout: 900
  60. TestFunctionLogGroup:
  61. Type: AWS::Logs::LogGroup
  62. Properties:
  63. # Let's hope that the Lambda function doesn't execute before this LogGroup
  64. # resource is created, creating the LogGroup with no expiration and
  65. # preventing this resource from creating
  66. LogGroupName: !Join [ '/', ['/aws/lambda', !Ref 'TestFunction'] ]
  67. RetentionInDays: 1
  68. TestRoute53RecordSet:
  69. Type: AWS::Route53::RecordSet
  70. Properties:
  71. AliasTarget:
  72. DNSName: !GetAtt TestCloudFrontDistribution.DomainName
  73. HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
  74. Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
  75. HostedZoneName: !Ref DomainNameZone
  76. Name: !Ref CustomDomainName
  77. Type: A
  78. TestApi:
  79. Type: AWS::ApiGateway::RestApi
  80. Properties:
  81. Name: Test
  82. Binary
  83. <details>
  84. <summary>英文:</summary>
  85. I contacted AWS Support and after many back and forths found that the problem is in the AWS documentation.
  86. The documentation page on the [`AWS::ApiGateway::RestApi` CloudFormation resource type](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-binarymediatypes) incorrectly states
  87. &gt; Slashes must be escaped with ~1. For example, image/png would be image~1png in the BinaryMediaTypes list.
  88. It turns out this is not true and the value that you should put in the `BinaryMediaTypes` field is `*/*` not `*~1*`. This makes the field look like this
  89. ```yaml
  90. Resources:
  91. TestApi:
  92. Type: AWS::ApiGateway::RestApi
  93. Properties:
  94. Name: Test
  95. BinaryMediaTypes:
  96. - &#39;*/*&#39;

So with that change to the template in the question, the resulting stack correctly serves up binary resources.

I've confirmed that this AWS doc page, Support Binary Payloads in API Gateway , is indeed talking about modes other than AWS_PROXY and doesn't apply to my question.

The template with the fix that I've confirmed works is this

  1. AWSTemplateFormatVersion: 2010-09-09
  2. Description: Test binary responses with AWS_PROXY mode
  3. Parameters:
  4. CustomDomainName:
  5. Type: String
  6. Description: The custom domain name to use for the API
  7. Default: &#39;&#39;
  8. # AWS::ApiGateway::DomainName can not contain any uppercase characters
  9. AllowedPattern: &#39;^[^A-Z]*$&#39;
  10. ConstraintDescription: must not contain any uppercase characters
  11. DomainNameZone:
  12. Type: String
  13. Description: The Route53 DNS zone containing the custom domain name
  14. Default: &#39;&#39;
  15. CertificateArn:
  16. Type: String
  17. Description: The ARN of the AWS ACM Certificate for your custom domain name
  18. Default: &#39;&#39;
  19. Resources:
  20. TestFunctionRole:
  21. Type: AWS::IAM::Role
  22. Properties:
  23. AssumeRolePolicyDocument:
  24. Version: 2012-10-17
  25. Statement:
  26. - Effect: Allow
  27. Principal:
  28. Service:
  29. - lambda.amazonaws.com
  30. Action:
  31. - sts:AssumeRole
  32. Policies:
  33. - PolicyName: AllowLambdaLogging
  34. PolicyDocument:
  35. Version: 2012-10-17
  36. Statement:
  37. - Effect: Allow
  38. Action:
  39. - logs:CreateLogGroup
  40. - logs:CreateLogStream
  41. - logs:PutLogEvents
  42. Resource: &#39;*&#39;
  43. TestFunction:
  44. Type: AWS::Lambda::Function
  45. Properties:
  46. Description: Test Function
  47. Code:
  48. ZipFile: |
  49. def lambda_handler(event, context):
  50. body = &#39;iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAYAAABSfLWiAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA0gAAANIBBp0MHQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAHgSURBVCiRlY47aFNxGEfP97/p05SWYhXfEHMjNZobuChYk1iwUCKKiqSjj0XpIM46uDgUQdxqk0lUHJwsiEPtoEmtgxhMIx2StFJBhA4tOCTVPO7n0C5uesbDj8NPAEJO4oXCLqDHU3PbktYJhM/lwty07SRmEHlQKWRn7Uh8VlRvqDFpoEdgo7yQO+0DqP80V1ZW3v0KOcMxI95dMFOqnD8YGfoAckCUZMCNlWhKvxoGxaNWLuZGAQUQwNhOfEJFjhqPugo7u7RzZEN+50HvgO4R5KKKPkVlb9VXfbit5X+Cp2FBn5WLc/dNyBkeAkksFXJnWurdA6xi8U0VqIBc89R6q0hVPLmgtF7+yOdrlUI2ZdXb4hhzKRQ95frENL6qZ+2zo/FHqHQAA6RSlpZWp0WYWC5mF4NO4j3C1aWF+UXbiZ0VZKxFo4pitTcbywAE3JHeQDRhAxIOh9vZxITDw34A13Xbdrtu95Yn4Mb2HzoSjwSDyQ4A0SlOyjjz/Af6mE7q3AQGgW4D1DTDc01zWTP0/lPlG02ULxgmUfoEQCfx4+MWMI5SQvi0NVpDWcejC6EfsBGOA4cR0vh4RZNz8tfNzVgSYRTlGLADGADWge/AR4QZ+ngtY9Q1w3aus/YHPCW0c1bW92YAAAAASUVORK5CYII=&#39;
  51. return {
  52. &#39;headers&#39;: {&#39;Content-Type&#39;: &#39;image/png&#39;},
  53. &#39;statusCode&#39;: 200,
  54. &#39;isBase64Encoded&#39;: True,
  55. &#39;body&#39;: body}
  56. Handler: index.lambda_handler
  57. Runtime: python3.7
  58. Role: !GetAtt TestFunctionRole.Arn
  59. Timeout: 900
  60. TestFunctionLogGroup:
  61. Type: AWS::Logs::LogGroup
  62. Properties:
  63. # Let&#39;s hope that the Lambda function doesn&#39;t execute before this LogGroup
  64. # resource is created, creating the LogGroup with no expiration and
  65. # preventing this resource from creating
  66. LogGroupName: !Join [ &#39;/&#39;, [&#39;/aws/lambda&#39;, !Ref &#39;TestFunction&#39; ] ]
  67. RetentionInDays: 1
  68. TestRoute53RecordSet:
  69. Type: AWS::Route53::RecordSet
  70. Properties:
  71. AliasTarget:
  72. DNSName: !GetAtt TestCloudFrontDistribution.DomainName
  73. HostedZoneId: Z2FDTNDATAQYW2 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget-1.html
  74. Comment: Bind the custom domain name to the Test CloudFront fronted API Gateway
  75. HostedZoneName: !Ref DomainNameZone
  76. Name: !Ref CustomDomainName
  77. Type: A
  78. TestApi:
  79. Type: AWS::ApiGateway::RestApi
  80. Properties:
  81. Name: Test
  82. BinaryMediaTypes:
  83. - &#39;*/*&#39;
  84. Description: Test API
  85. FailOnWarnings: true
  86. EndpointConfiguration:
  87. Types:
  88. - REGIONAL
  89. TestApiGatewayDomainName:
  90. # The ApiGateway requires a custom domain name, despite sitting behind
  91. # CloudFront. This is because we want to pass all ( * ) HTTP headers
  92. # through CloudFront and onto API Gateway. If we didn&#39;t set a custom domain
  93. # name on the API Gateway, the &quot;Host&quot; header passed through from CloudFront
  94. # to API Gateway would be for the custom domain, but API Gateway, which uses
  95. # SNI, wouldn&#39;t know which TLS certificate to use in the handshake because
  96. # API Gateway would have no record of that Host header. This would result in
  97. # API Gateway being unable to setup a TLS connection with the inbound
  98. # CloudFront connection attempt, API Gateway writing no logs about this
  99. # fact, and CloudFront returning to the user an error of
  100. # {&quot;message&quot;:&quot;Forbidden&quot;}
  101. # If we weren&#39;t passing the &quot;Host&quot; header from CloudFront to API Gateway
  102. # this resource wouldn&#39;t be needed
  103. Type: AWS::ApiGateway::DomainName
  104. Properties:
  105. # Uppercase letters are not supported in DomainName
  106. DomainName: !Ref CustomDomainName
  107. EndpointConfiguration:
  108. Types:
  109. - REGIONAL
  110. RegionalCertificateArn: !Ref CertificateArn
  111. SecurityPolicy: TLS_1_2
  112. TestBasePathMapping:
  113. Type: AWS::ApiGateway::BasePathMapping
  114. Properties:
  115. # BasePath: # Not specifying this so that we have no base path
  116. DomainName: !Ref TestApiGatewayDomainName
  117. RestApiId: !Ref TestApi
  118. Stage: !Ref TestApiStage
  119. TestLambdaPermission:
  120. Type: AWS::Lambda::Permission
  121. Properties:
  122. Action: lambda:invokeFunction
  123. FunctionName: !GetAtt TestFunction.Arn
  124. Principal: apigateway.amazonaws.com
  125. SourceArn: !Join [ &#39;&#39;, [ &#39;arn:aws:execute-api:&#39;, !Ref &#39;AWS::Region&#39;, &#39;:&#39;, !Ref &#39;AWS::AccountId&#39;, &#39;:&#39;, !Ref &#39;TestApi&#39;, &#39;/*/*&#39; ] ]
  126. TestApiStage:
  127. Type: AWS::ApiGateway::Stage
  128. Properties:
  129. DeploymentId: !Ref TestApiDeployment
  130. MethodSettings:
  131. - DataTraceEnabled: true
  132. HttpMethod: &#39;*&#39;
  133. ResourcePath: /*
  134. RestApiId: !Ref TestApi
  135. TestApiDeployment:
  136. Type: AWS::ApiGateway::Deployment
  137. DependsOn:
  138. - TestRequest
  139. Properties:
  140. RestApiId: !Ref TestApi
  141. StageName: DummyStage
  142. # Deployment with an Empty Embedded Stage
  143. # The following instructional text is no longer present in the AWS
  144. # documentation for AWS::ApiGateway::Deployment StageName and it&#39;s not
  145. # clear if it still applies.
  146. #
  147. # &quot;Note This property is required by API Gateway. We recommend that you
  148. # specify a name using any value (see Examples) and that you don’t use
  149. # this stage. We recommend not using this stage because it is tied to
  150. # this deployment, which means you can’t delete one without deleting the
  151. # other. For example, if you delete this deployment, API Gateway also
  152. # deletes this stage, which you might want to keep. Instead, use the
  153. # AWS::ApiGateway::Stage resource to create and associate a stage with
  154. # this deployment.&quot;
  155. TestResource:
  156. Type: AWS::ApiGateway::Resource
  157. Properties:
  158. RestApiId: !Ref TestApi
  159. ParentId: !GetAtt TestApi.RootResourceId
  160. PathPart: &#39;{proxy+}&#39;
  161. TestRequest:
  162. DependsOn: TestLambdaPermission
  163. Type: AWS::ApiGateway::Method
  164. Properties:
  165. AuthorizationType: NONE
  166. HttpMethod: GET
  167. Integration:
  168. Type: AWS_PROXY
  169. # IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
  170. # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
  171. # &quot;For Lambda integrations, you must use the HTTP method of POST for the
  172. # integration request, according to the specification of the Lambda service
  173. # action for function invocations.&quot;
  174. IntegrationHttpMethod: POST
  175. Uri: !Join [ &#39;&#39;, [ &#39;arn:aws:apigateway:&#39;, !Ref &#39;AWS::Region&#39;, &#39;:lambda:path/2015-03-31/functions/&#39;, !GetAtt &#39;TestFunction.Arn&#39;, &#39;/invocations&#39; ] ]
  176. ResourceId: !Ref TestResource
  177. RestApiId: !Ref TestApi
  178. TestPOSTRequest:
  179. DependsOn: TestLambdaPermission
  180. Type: AWS::ApiGateway::Method
  181. Properties:
  182. AuthorizationType: NONE
  183. HttpMethod: POST
  184. Integration:
  185. Type: AWS_PROXY
  186. # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
  187. # &quot;For Lambda integrations, you must use the HTTP method of POST for the
  188. # integration request, according to the specification of the Lambda service
  189. # action for function invocations.&quot;
  190. IntegrationHttpMethod: POST
  191. Uri: !Join [ &#39;&#39;, [ &#39;arn:aws:apigateway:&#39;, !Ref &#39;AWS::Region&#39;, &#39;:lambda:path/2015-03-31/functions/&#39;, !GetAtt &#39;TestFunction.Arn&#39;, &#39;/invocations&#39; ] ]
  192. ResourceId: !Ref TestResource
  193. RestApiId: !Ref TestApi
  194. TestRootRequest:
  195. # This resource is necessary to get API Gateway to respond to requests for the &#39;/&#39; path
  196. # Without it API Gateway will respond to requests for &#39;/&#39; with the error
  197. # {&quot;message&quot;:&quot;Missing Authentication Token&quot;}
  198. # https://stackoverflow.com/q/46578615/168874
  199. # https://stackoverflow.com/q/52909329/168874
  200. DependsOn: TestLambdaPermission
  201. Type: AWS::ApiGateway::Method
  202. Properties:
  203. AuthorizationType: NONE
  204. HttpMethod: GET
  205. Integration:
  206. Type: AWS_PROXY
  207. # IntegrationHttpMethod is POST regardless of the HttpMethod for this resource
  208. # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
  209. # &quot;For Lambda integrations, you must use the HTTP method of POST for the
  210. # integration request, according to the specification of the Lambda service
  211. # action for function invocations.&quot;
  212. IntegrationHttpMethod: POST
  213. Uri: !Join [ &#39;&#39;, [ &#39;arn:aws:apigateway:&#39;, !Ref &#39;AWS::Region&#39;, &#39;:lambda:path/2015-03-31/functions/&#39;, !GetAtt &#39;TestFunction.Arn&#39;, &#39;/invocations&#39; ] ]
  214. # ResourceId must use the RootResourceId attribute of the AWS::ApiGateway::RestApi
  215. # https://stackoverflow.com/a/56121914/168874
  216. ResourceId: !GetAtt TestApi.RootResourceId
  217. RestApiId: !Ref TestApi
  218. TestCloudFrontDistribution:
  219. Type: AWS::CloudFront::Distribution
  220. Properties:
  221. DistributionConfig:
  222. Comment: !Join [ &#39;:&#39;, [!Ref &#39;AWS::StackName&#39;, &#39;Test&#39;]]
  223. DefaultCacheBehavior:
  224. AllowedMethods:
  225. - GET
  226. - HEAD
  227. - POST
  228. - DELETE
  229. - OPTIONS
  230. - PUT
  231. - PATCH
  232. Compress: true
  233. DefaultTTL: 0
  234. MinTTL: 0
  235. MaxTTL: 0
  236. ForwardedValues:
  237. Cookies:
  238. Forward: all
  239. QueryString: true
  240. Headers:
  241. - &#39;*&#39;
  242. TargetOriginId: TestCloudFrontOriginId
  243. ViewerProtocolPolicy: redirect-to-https
  244. # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-defaultrootobject
  245. DefaultRootObject: &#39;&#39; # &quot;If you don&#39;t want to specify a default root object when you create a distribution, include an empty DefaultRootObject element.&quot;
  246. Enabled: true
  247. Aliases:
  248. - !Ref CustomDomainName
  249. HttpVersion: http2
  250. IPV6Enabled: true
  251. #Logging:
  252. # Logging
  253. Origins:
  254. - CustomOriginConfig:
  255. OriginProtocolPolicy: https-only
  256. OriginSSLProtocols:
  257. - TLSv1.2
  258. DomainName: !GetAtt TestApiGatewayDomainName.RegionalDomainName
  259. Id: TestCloudFrontOriginId
  260. # OriginPath: !Join [ &#39;&#39;, [ &#39;/&#39;, !Ref &#39;TestApiStage&#39; ] ]
  261. PriceClass: PriceClass_100 # US, Canada, Europe, Israel
  262. ViewerCertificate:
  263. AcmCertificateArn: !Ref CertificateArn
  264. MinimumProtocolVersion: TLSv1.2_2018
  265. SslSupportMethod: sni-only

In case AWS updates this documentation page to fix this you can see the original page here

huangapple
  • 本文由 发表于 2020年1月7日 00:49:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/59615948.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定