API Gateway Mock Integration Fails with 500

Viewed 13920

I have an API Gateway integration for a method/resource which works when I call it from the API but not when I actually call it:

$ aws apigateway test-invoke-method --rest-api-id $REST_API_ID \
    --resource-id $RESOURCE_ID --http-method GET | jq -r .log,.body

This works out fine and I get the following output:

Tue May 16 17:46:42 UTC 2017 : Starting execution for request: test-invoke-request
Tue May 16 17:46:42 UTC 2017 : HTTP Method: GET, Resource Path: /status.json
Tue May 16 17:46:42 UTC 2017 : Method request path: {}
Tue May 16 17:46:42 UTC 2017 : Method request query string: {}
Tue May 16 17:46:42 UTC 2017 : Method request headers: {}
Tue May 16 17:46:42 UTC 2017 : Method request body before transformations:
Tue May 16 17:46:42 UTC 2017 : Endpoint response body before transformations:
Tue May 16 17:46:42 UTC 2017 : Endpoint response headers: {}
Tue May 16 17:46:42 UTC 2017 : Method response body after transformations: { "statusCode": 200 }
Tue May 16 17:46:42 UTC 2017 : Method response headers: {Content-Type=application/json}
Tue May 16 17:46:42 UTC 2017 : Successfully completed execution
Tue May 16 17:46:42 UTC 2017 : Method completed with status: 200

{ "statusCode": 200 }

However, I cannot access this at my URL, which is api.naftuli.wtf/v1/status.json. I have stages defined at glhf, stable, and v1, so by replacing that, you will see different responses. I just simply want a dummy response that returns a 200 JSON blob.

My Terraform for the resources is here as a Gist. Hopefully this fully shows my API Gateway configuration.

If I test invoke this from the CLI or from the web console, I get back what is expected. However, if I curl this from my deployed API at api.naftuli.wtf, I don't get anything nice:

$ for stage in glhf stable v1 ; do 
>   url="https://api.naftuli.wtf/${stage}/status.json"
>   echo "${url}:" 
>   curl -i -H 'Content-Type: application/json' \
>     https://api.naftuli.wtf/${stage}/status.json
>   echo -e '\n
> done
https://api.naftuli.wtf/glhf/status.json:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 36
Connection: keep-alive
Date: Tue, 16 May 2017 21:41:38 GMT
x-amzn-RequestId: 712ba52b-3a80-11e7-9fec-b79b62d3bf7f
X-Cache: Error from cloudfront
Via: 1.1 da7a5d0ed7f424609000879e43743066.cloudfront.net (CloudFront)
X-Amz-Cf-Id: hBwlbPCP9n2rlz53I-Qb9KoffHB_FoxUCZUaJYNnU3XhCWuMpQTP1Q==

{"message": "Internal server error"}

https://api.naftuli.wtf/stable/status.json:
HTTP/1.1 403 Forbidden
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Date: Tue, 16 May 2017 21:41:38 GMT
x-amzn-RequestId: 71561066-3a80-11e7-9b00-6700be628328
x-amzn-ErrorType: ForbiddenException
X-Cache: Error from cloudfront
Via: 1.1 0c146399837c7d36c1f0f9d2636f8cf8.cloudfront.net (CloudFront)
X-Amz-Cf-Id: ITX765xD8s4sNuOdXaJ2kPvqPo-w_dsQK3Sq_No130FAHxFuoVhO8w==

{"message":"Forbidden"}

https://api.naftuli.wtf/v1/status.json:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 36
Connection: keep-alive
Date: Tue, 16 May 2017 21:41:39 GMT
x-amzn-RequestId: 7185fa99-3a80-11e7-a3b1-2f9e659fc361
X-Cache: Error from cloudfront
Via: 1.1 586f1a150b4ba39f3a668b8055d4d5ea.cloudfront.net (CloudFront)
X-Amz-Cf-Id: dvnOa1s-YlwLSNzBfVyx5tSL6XrjFJM4_fES7MyTofykB3ReU5R1fg==

{"message": "Internal server error"}

My understanding of stages were that they were additional path prefixes to the base path under which all API resources were available. If I had a stage called v1 with a path of /v1, I'd expect that an API Gateway resource for status.json will be basically mapped under /v1, yielding /v1/status.json.

I may be misunderstanding how API Gateway base path mappings and stages work, but CloudWatch tells me that the call is at least happening, though failing for some obscure reason:

21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) Verifying Usage Plan for request: c5be3842-6af4-4725-a34f-d6eea8042d17. API Key: API Stage: tcips69qx2/prod_v1
21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) API Key authorized because method 'GET /status.json' does not require API Key. Request will not contribute to throttle or quota limits
21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) Usage Plan check succeeded for API Key and API Stage tcips69qx2/prod_v1
21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) Starting execution for request: c5be3842-6af4-4725-a34f-d6eea8042d17
21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) HTTP Method: GET, Resource Path: /v1/status.json
21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) Execution failed due to configuration error: statusCode should be an integer which defined in request template
21:41:39(c5be3842-6af4-4725-a34f-d6eea8042d17) Method completed with status: 500

Apparently only traffic across the V1 stage is getting through to CloudWatch logs. I have a misconfiguration somewhere and I can't seem to find it.

8 Answers

With Mock Integration Amazon API Gateway there are 2 common reasons for 500 Internal Server error.

  1. Check the mapping template in Integration Request and ensure that you are passing statusCode as an integer to the MOCK Integration endpoint.

    { "statusCode": <Integer_Status_code> }

Note: Make sure that status code is passed as integer not string.

Correct : 200 Incorrect : "200"

  1. Mock Integrations do not support the binary content. If the API is enabled with bixnary support and has application/json or */* set as binaryMediaTypes, MOCK Integration endpoints would throw a 500 Internal server error when trying to transform the content.

    A workaround is to update the contentHandling property of the MOCK Integration to CONVERT_TO_TEXT

Read more here :- https://cloudnamaste.com/500-internal-server-error-mock-integration/

Having had the identical errors I found what helped me solve this issue was to delete my OPTIONS request definition in the AWS Console. I then followed the console's "Enable CORS" form which created a new OPTIONS method.

I subsequently ran terraform plan and looked at the diff between my OPTIONS defintion and theirs. Given that the AWS Console created OPTIONS method worked I applied the changes.

Using terraform 0.12 or greater makes this possible as the terraform plan output detail is more fine grained.

For my case, when I deploy the lambda with serverless framework, the OPTIONS returns 200 when called. However, when I configure manually on AWS API Gateway, it returns 500 Internal Server Error.

When I check the execution log of API gateway, it said

(ee0a42d9-2cfc-4788-8679-00fbd7938cf1) Method request body before transformations: [Binary Data]
(ee0a42d9-2cfc-4788-8679-00fbd7938cf1) Execution failed due to configuration error: Unable to transform request
(ee0a42d9-2cfc-4788-8679-00fbd7938cf1) Gateway response body: 
{
    "message": "Internal server error"
}

After you create the Resources and OPTIONS methods, select the OPTIONS method then

  1. At Method Execution --> Integration Request --> expand the Mapping Templates, choose "When no template matches the request Content-Type header". Add or select "application/json" in the Content-Type. Click the "application/json", and in the Generate template, paste {statusCode: 200} without any double quote. Note, if the {"statusCode": 200} exists but with double quote, remove it to be the same as above. Then Save it
  2. At Method Execution --> Integration Response --> expand the 200 response status --> Mapping Template --> Add or select "application/json" in the Content-Type. Make sure that the Generate template box is empty. Then Save it
  3. At Method Execution --> Method Response --> expand the 200 response. In Response Headers for 200, add three headers: Access-Control-Allow-Headers, Access-Control-Allow-Methods, and Access-Control-Allow-Origin. Leave the Response Body for 200 to be empty
  4. Action --> Enable CORS for the Resource
  5. Action --> Deploy API

I was doing this in CloudFormation.

It took me a while to get it and the accepted answer here was extremely helpful, but a little vague, so adding some more info.

Stefano Buliani's answer, in CloudFormation YAML, looks like:

RequestTemplates:
    application/json: | 
        { statusCode: 200 }

I had a similar problem and eventually figured out that my client was using a different content type than I expected. I had foolishly assumed it would use application/json, but it was some custom json thing. In my setup, API Gateway is logging to cloudwatch, which is where I found the content type it received from the client. Once I updated the content type in the request template of the mock integration, things worked as expected.

Related