Repository: serverless/serverless (JavaScript)
Date: 2019-11-20 21:18:42+00:00
Task type: Bug Fix

Issue:
Request with binary media returns 500
# Bug Report

## Description
When using `binaryMediaTypes` in combination with lambda non-proxy integration, then a 500 error is always returned. When using the lambda proxy integration (as given here: #6063 ) everything works fine.

The only workaround I found is to manually edit the referenced lambda function once and to re-deploy the stage (as described here: #4628). This adds additional permission to the function's policy - which I assume is what "fixes" the issue.

Lambda policy **before** applying workaround:
```
aws lambda get-policy --function-name test-dev-hello  
{
    "Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"default\",\"Statement\":[{\"Sid\":\"test-dev-HelloLambdaPermissionApiGateway-T1A01LP3JK5R\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:eu-central-1:123:function:test-dev-hello\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:eu-central-1:123:z9qb5x6fnb/*/*\"}}}]}",
    "RevisionId": "4d4b049e-f69b-4aad-a14b-76d4fba515ff"
}

```

Lambda policy **after** applying workaround:
```
aws lambda get-policy --function-name test-dev-hello  
{
    "Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"default\",\"Statement\":[{\"Sid\":\"test-dev-HelloLambdaPermissionApiGateway-T1A01LP3JK5R\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:eu-central-1:123:function:test-dev-hello\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:eu-central-1:123:z9qb5x6fnb/*/*\"}}},{\"Sid\":\"653c0028-bc43-43b0-84d5-f8c3ae45d8f4\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:eu-central-1:123:function:test-dev-hello\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:eu-central-1:123:z9qb5x6fnb/*/GET/hello\"}}}]}",
    "RevisionId": "79bb00a3-93b9-4fd6-8bf6-95cb49a3818a"
}


```



1. What did you do?
- Created an API gateway with 

```
binaryMediaTypes:
      - '*/*'
```

and a simple function with `integration: lambda`.

1. What happened?
- Calling the function returns 500.
1. What should've happened?
- Calling the function should return 200.
1. What's the content of your `serverless.yml` file?

```
service: test

provider:
  name: aws
  runtime: nodejs8.10
  region: eu-central-1
  apiGateway:
    binaryMediaTypes:
      - '*/*'

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          method: GET
          path: hello
          integration: lambda
```

```
// handler.js
'use strict';

module.exports.hello = async (event) => {
  return "hello";
};
```

1. What's the output you get when you use the `SLS_DEBUG=*` environment variable (e.g. `SLS_DEBUG=* serverless deploy`)

Similar or dependent issues:

- #2797
- #4628



Issue discussion:
It was not related to permissions after all. Instead, it was caused by the missing [contentHandling](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html#cfn-apigateway-method-integration-contenthandling) property. 

Workaround:
```
resources:
  Resources:
    ApiGatewayMethodHelloPost:
      DependsOn: IamRoleLambdaExecution # Otherwise an error "The role defined for the function cannot be assumed by Lambda." is returned after first deployment
      Type: AWS::ApiGateway::Method
      Properties:
        Integration:
          ContentHandling: CONVERT_TO_TEXT
```

Solution patch:
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index aa43826ed8f..a78d3331297 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -1494,6 +1494,22 @@ provider:
 
 In your Lambda function you need to ensure that the correct `content-type` header is set. Furthermore you might want to return the response body in base64 format.
 
+To convert the request or response payload, you can set the [contentHandling](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-workflow.html) property.
+
+```yml
+functions:
+  create:
+    handler: posts.create
+    events:
+      - http:
+          path: posts/create
+          method: post
+          request:
+            contentHandling: CONVERT_TO_TEXT
+          response:
+            contentHandling: CONVERT_TO_TEXT
+```
+
 ## AWS X-Ray Tracing
 
 API Gateway supports a form of out of the box distributed tracing via [AWS X-Ray](https://aws.amazon.com/xray/) though enabling [active tracing](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-xray.html). To enable this feature for your serverless application's API Gateway add the following to your `serverless.yml`
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
index 252de8169e7..f3f0639b2e8 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
@@ -97,13 +97,15 @@ module.exports = {
     if (type === 'AWS' || type === 'HTTP' || type === 'MOCK') {
       _.assign(integration, {
         PassthroughBehavior: http.request && http.request.passThrough,
+        ContentHandling: http.request && http.request.contentHandling,
         RequestTemplates: this.getIntegrationRequestTemplates(http, type === 'AWS'),
         IntegrationResponses: this.getIntegrationResponses(http),
       });
     }
     if (
       ((type === 'AWS' || type === 'HTTP' || type === 'HTTP_PROXY') &&
-        (http.request && !_.isEmpty(http.request.parameters))) ||
+        http.request &&
+        !_.isEmpty(http.request.parameters)) ||
       http.async
     ) {
       _.assign(integration, {
@@ -151,6 +153,7 @@ module.exports = {
           SelectionPattern: config.pattern || '',
           ResponseParameters: responseParameters,
           ResponseTemplates: {},
+          ContentHandling: http.response.contentHandling,
         };
 
         if (config.headers) {


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
index a4481719073..16950dcb33b 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
@@ -1110,6 +1110,35 @@ describe('#compileMethods()', () => {
       });
     });
 
+    it('should use defined content-handling behavior', () => {
+      awsCompileApigEvents.validated.events = [
+        {
+          functionName: 'First',
+          http: {
+            method: 'GET',
+            path: 'users/list',
+            integration: 'AWS',
+            request: {
+              contentHandling: 'CONVERT_TO_TEXT',
+            },
+            response: {
+              statusCodes: {
+                200: {
+                  pattern: '',
+                },
+              },
+            },
+          },
+        },
+      ];
+      return awsCompileApigEvents.compileMethods().then(() => {
+        expect(
+          awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+            .ApiGatewayMethodUsersListGet.Properties.Integration.ContentHandling
+        ).to.equal('CONVERT_TO_TEXT');
+      });
+    });
+
     it('should set custom request templates', () => {
       awsCompileApigEvents.validated.events = [
         {
@@ -1310,6 +1339,7 @@ describe('#compileMethods()', () => {
         SelectionPattern: 'foo',
         ResponseParameters: {},
         ResponseTemplates: {},
+        ContentHandling: undefined,
       });
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
@@ -1319,6 +1349,7 @@ describe('#compileMethods()', () => {
         SelectionPattern: '',
         ResponseParameters: {},
         ResponseTemplates: {},
+        ContentHandling: undefined,
       });
     });
   });
@@ -1484,6 +1515,34 @@ describe('#compileMethods()', () => {
     });
   });
 
+  it('should use defined content-handling behavior', () => {
+    awsCompileApigEvents.validated.events = [
+      {
+        functionName: 'First',
+        http: {
+          method: 'get',
+          path: 'users/list',
+          integration: 'AWS',
+          response: {
+            contentHandling: 'CONVERT_TO_BINARY',
+            statusCodes: {
+              200: {
+                pattern: '',
+              },
+            },
+          },
+        },
+      },
+    ];
+    return awsCompileApigEvents.compileMethods().then(() => {
+      expect(
+        awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+          .ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[0]
+          .ContentHandling
+      ).to.equal('CONVERT_TO_BINARY');
+    });
+  });
+
   it('should handle root resource methods', () => {
     awsCompileApigEvents.validated.events = [
       {