Repository: localstack/localstack

Issue:
API Gateway incorrectly serializing lambda proxy response body.
It looks like localstack's API Gateway implementation is stringifying response body objects returned by lambda proxy functions. The functionality documented on AWS' website states the response body returned by the lambda function should be a string, it's the responsibility of the function to serialize their objects accordingly. That resulting body string should be sent straight back to the client without modification. This allows for formats other than JSON to be supported.

https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format

Syntax error in API Gateway
This is invalid:

https://github.com/localstack/localstack/blob/6d790a6379c95900246c1e66d20117bb3b307c34/localstack/services/apigateway/apigateway_listener.py#L168

If `get_resource_for_path` returns falsy, which it can do when there’s no resource for the requested path, the runtime moves on to `or {}` which doesn’t return two values, which the left-hand side expects. The runtime then throws:

```
ERROR:localstack.services.generic_proxy: Error forwarding request: need more than 0 values to unpack Traceback (most recent call last):
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 181, in forward
    path=path, data=data, headers=forward_headers)
  File "/opt/code/localstack/localstack/services/apigateway/apigateway_listener.py", line 168, in forward_request
    extracted_path, resource = get_resource_for_path(path=relative_path, path_map=path_map) or {}
ValueError: need more than 0 values to unpack
```


Discussion:

Resolving patch:
diff --git a/localstack/services/apigateway/apigateway_listener.py b/localstack/services/apigateway/apigateway_listener.py
--- a/localstack/services/apigateway/apigateway_listener.py
+++ b/localstack/services/apigateway/apigateway_listener.py
@@ -165,7 +165,11 @@ def forward_request(self, method, path, data, headers):
             except Exception:
                 # if we have no exact match, try to find an API resource that contains path parameters
                 path_map = get_rest_api_paths(rest_api_id=api_id)
-                extracted_path, resource = get_resource_for_path(path=relative_path, path_map=path_map) or {}
+                try:
+                    extracted_path, resource = get_resource_for_path(path=relative_path, path_map=path_map)
+                except Exception:
+                    return make_error('Unable to find path %s' % path, 404)
+
                 integrations = resource.get('resourceMethods', {})
                 integration = integrations.get(method, {})
                 integration = integration.get('methodIntegration')
@@ -215,8 +219,10 @@ def forward_request(self, method, path, data, headers):
                     response.status_code = int(parsed_result.get('statusCode', 200))
                     response.headers.update(parsed_result.get('headers', {}))
                     try:
-                        response_body = parsed_result['body']
-                        response._content = json.dumps(response_body)
+                        if isinstance(parsed_result['body'], dict):
+                            response._content = json.dumps(parsed_result['body'])
+                        else:
+                            response._content = parsed_result['body']
                     except Exception:
                         response._content = '{}'
                     return response


Test patch: