Return Custom HTTP Status Code in AWS Java Lambda? - amazon-web-services

I am trying to make my AWS Lambda function written in Java return an error JSON model. The problem is that when the request is sent using Postman or any other HTTP client, the HTTP Status code returned by API Gateway is 200. I need the HTTP Status code to be the one that I return in JSON.
I have learned from the following documentation page that for this to work, my AWS Java Lambda code needs to return a specific header and explicitly set HTTP status code in JSON. But that still results in a 200 OK status code received by HTTP Client.
https://docs.aws.amazon.com/apigateway/latest/developerguide/handle-errors-in-lambda-integration.html
How do I make AWS API Gateway return the HTTP status code that I manually set in the JSON response?
This is not a Proxy integration, so I cannot use the APIGatewayProxyResponseEvent.
My AWS Java lambda returns JSON String that looks like this.
{
"statusCode": 502,
"isBase64Encoded": false,
"headers": {
"X-Amzn-ErrorType": "InvalidParameterException",
"content-type": "application/json"
},
"body": {
"requestId": "a4c946aa-3ca9-4252-ab6c-4f5fb1c6f53e",
"errorMessage": "InternalServerError Manually triggered error"
}
}
Thank you very much for your advice!

Related

Spring Cloud Function AWS Lambda with proxy integration on APIGW : CORS issue

We have created our AWS Lambda function using Spring Cloud function. This function returns APIGatewayProxyResponseEvent response. Sample below
{
"statusCode": 200,
"headers": {
"Access-Control-Expose-Headers": "Access-Control-Allow-Methods,Access-Control-Allow-Origin",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
"Access-Control-Max-Age": "200",
"Access-Control-Allow-Headers": "Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers",
"Content-Type": "application/json"
},
"multiValueHeaders": null,
"body": "response Data json Body",
"isBase64Encoded": false
}
APIGW uses Lambda proxy integration , and hence there is no option for response mappings.
We have enabled CORS by using Actions on the console. This automatically adds the OPTIONS method where we have configured the 200 response with below headers
Access-Control-Max-Age : '200'
Access-Control-Allow-Headers : 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
Access-Control-Expose-Headers : 'Access-Control-Allow-Methods,Access-Control-Allow-Origin'
Access-Control-Allow-Origin : '*'
Access-Control-Allow-Methods : 'GET,OPTIONS'
The above steps are in sync with the AWS documentation AWS - How to CORS Lambda proxy
We deployed the API in a stage and are able to access it via Postman. On accessing from our web-application, which is currently on localhost we get CORS error.
In Network tab its visible that the preflight request (OPTIONS) returns 200 OK and the required CORS headers. However the actual GET call still fails , saying "CORS Error".
The issue is that APIGW is not copying the headers returned in the APIGatewayProxyResponseEvent object to final APIGW Response headers
Is this a known issue or am I missing something
Edit
Screen shot of APIGW lambda proxy
Screen shot from APIGW response (Testing from console)
Network tab in browser developer options showing preflight request successful
Edit2
Adding Console output
Edit 1
On checking the spring cloud milestone release, This issue has been addressed starting 3.2.0-M1 . (Currently available release is 3.1.5). Once this is released my previous approach of sending APIGatewayProxyResponseEvent as output will work just fine.
#Oleg Zhurakousky can confirm
Original Answer below : (almost a work around)
Got help from AWS support and understood that the response being returned from the Spring Cloud function was being modified. This in turn resulted in all the headers being encapsulated as part of Lambda response body.
My previous implementation of the function was
#Bean
public Function<APIGatewayProxyRequestEvent,APIGatewayProxyResponseEvent> testFunc2(){
return event -> {
System.out.println(event);
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
Map<String,String> hdr = new HashMap<>();
hdr.put("Access-Control-Allow-Origin","*");
response.setHeaders(hdr);
response.setStatusCode(HttpStatus.OK.value());
response.setBody("Hello World!");
return response;
};
}
I had to change it to below to make sure the headers are treated as http headers and not a part of the lambda response body
#Bean
public Function<APIGatewayProxyRequestEvent, Message<APIGatewayProxyResponseEvent>> testFunc3(){
return event -> {
System.out.println(event);
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
Map<String,Object> hdr = new HashMap<>();
hdr.put("Access-Control-Allow-Origin","*");
response.setStatusCode(HttpStatus.OK.value());
response.setBody("Hello World!");
Message<APIGatewayProxyResponseEvent> finalResponse = new GenericMessage<APIGatewayProxyResponseEvent>(response,hdr);
System.out.println("Response prepared " +response);
System.out.println("Final Response being returned " +finalResponse);
return finalResponse;
};
}
The actual entry point into Spring cloud function is org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest
Here while preparing response spring takes the returned value from the function as Message (org.springframework.messaging) Payload.
Hence In order to set http headers we need to return a Message<APIGatewayProxyResponseEvent> instead of APIGatewayProxyResponseEvent. Here we explicitly set our http headers in Message headers.

Api Gateway with Lambda proxy integration via serverless not decoding response

I've set up an API Gateway integration with a a lambda proxy integration, done automatically with the serverless framework. I usually have no problem with JSON returns, but doing this with http4k and kotlin, all my responses come back still base64 encoded.
Here are my logs for a response (from a mock inventory server)
f874ff6f-291c-4f9e-b106-f5f4a26e25e5) Endpoint response body before transformations:
{ "statusCode": 200, "headers": { "Content-Type": "application/json; charset=utf-8" }, "body": "W3siaXRlbSI6eyJpdGVtSWQiOiJjOTNjM2MxYy1iMWFjLTRiMTUtYjI1Yi1lMWVmNWY2ZDYxNzIiLCJuYW1lIjoidGVzdCIsInN0b2NrIjoxLCJzdGF0dXMiOiJBVkFJTEFCTEUifSwicHJpY2UiOnsiaXRlbUlkIjoiYzkzYzNjMWMtYjFhYy00YjE1LWIyNWItZTFlZjVmNmQ2MTcyIiwiYW1vdW50IjoxMDAwLCJjdXJyZW5jeSI6ImpweSJ9fV0=", "isBase64Encoded": true }
And then
(f874ff6f-291c-4f9e-b106-f5f4a26e25e5) Method response body after transformations: W3siaXRlbSI6eyJpdGVtSWQiOiJjOTNjM2MxYy1iMWFjLTRiMTUtYjI1Yi1lMWVmNWY2ZDYxNzIiLCJuYW1lIjoidGVzdCIsInN0b2NrIjoxLCJzdGF0dXMiOiJBVkFJTEFCTEUifSwicHJpY2UiOnsiaXRlbUlkIjoiYzkzYzNjMWMtYjFhYy00YjE1LWIyNWItZTFlZjVmNmQ2MTcyIiwiYW1vdW50IjoxMDAwLCJjdXJyZW5jeSI6ImpweSJ9fV0=
I've try to set binary media types to /, but still no luck.
Has anyone seen anything like this before?

AWS Documentation: How is Lambda's response var used in API Gateway?

I am reading this AWS blog post to learn how to make my static website's form POST data to API Gateway and Lambda.
Most of it makes sense to me, but the Lambda code provided contains this unused variable:
var response = {
"isBase64Encoded": false,
"headers": { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': 'example.com'},
"statusCode": 200,
"body": "{\"result\": \"Success.\"}"
};
I believe this is needed (based on this AWS documentation), but is it used automatically? Or is the Lambda missing some vital code? For example:
callback(null, response)
Side note: In the blog post, the integration set up is not the lambda proxy one, but rather lambda custom integration - https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-custom-integrations.html
To the question: I am pretty sure that the post contains a typo in context.done(err, null); which should have been context.done(err, response);
However as a result this is not an error, as the custom lambda integration response template is just passing through the empty data and the method response is set to 200 with an empty body.
The response object you are referring to needs to be returned back to the client with the structure provides. The code that you are referencing in the AWS article isn't sending any responses back to the client and that is why you are not seeing the response variable implemented anywhere. To complete the handler function you would return that response variable and transforming the body property to what message you intend to send back to your client. Without this return structure, you will get a 502 error on the client.

How to set the api version being called in API Gateway when integrating with AWS Service CloudWatch?

I get the following error message when calling actions for CloudWatch in API Gateway.
"Error": {
"Code": "InvalidAction",
"Message": "Could not find operation DescribeAlarms for version 2009-05-15",
"Type": "Sender"
}
I've been using DescribeAlarms for testing. My setup is as follows.
Integration Type = AWS Service
AWS Service = CloudWatch
HTTP method = POST
Action = DescribeAlarms
The error references the API Version 2009-05-15, which only has ListMetrics and GetMetricStatistics according to it's documentation on page 54. ListMetrics does indeed work as expected with my setup.
The current version is 2010-08-01 but I don't see anyway to reference that in API Gateway. In an example of a POST request in the documentation it shows a header labeled x-amz-target with a value of GraniteServiceVersion20100801.API_Name.
My interpretation is I can put Name = x-amz-target and value 'GraniteServiceVersion20100801.DescribeAlarms' in my http header for the Integration Request in API Gateway.
This doesn't change the response and gives the same error message.
I also used the --debug in CLI when calling describe-alarms, and in the body it shows...
"body": {
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
So I also set http headers to include Content-Type with a value of 'application/x-amz-json-1.1' and then put in
{
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
but nothing changed with that either.
Any help or guidance would be greatly appreciated.
Under Method Integration -> URL Query String Parameters
I added Version as the Name and '2010-08-01' under Mapped From.
All actions are now working as expected.
I'm trying to PutMetrics directly from Api Gateway -> Cloudwatch using PutMetricData, Version in the query string params didn't work for me.
These 3 HTTP headers in the Integration Request solved it for me:
Content-Type 'application/json'
X-Amz-Target 'GraniteServiceVersion20100801.PutMetricData'
Content-Encoding 'amz-1.0'

AWS API Gateway returning different status via Postman to Method Test

I have an AWS Lambda integrated into API Gateway. The lambda is current just returning an error:
exports.handler = (event, context, callback) => {
context.fail(JSON.stringify({
status: 500,
errors: 'This is definitely the same endpoint.'
}));
};
I have added an HTTP status 500 response in method response of API Gateway, and I have mapped an integration response using regex *"status":500.* to the 500 response.
When I test this using the Method Test functionality in AWS, I get the 500 response I expect:
But when send command to the endpoint with Postman, I get a 200 status:
How can this be? The Method Test seems to suggest my Integration Response and Method Response setups are correct, and I have my regex set up correctly, but what am I missing between the API Gateway and the rest of the world that would produce this different result?
Have you tried Deploying the API? Maybe a redeployment might fix the issues.
did you stumble across the Handle Custom Lambda Errors in API Gateway guide already?
The format suggested there is:
{
"isBase64Encoded" : "boolean",
"statusCode": "number",
"headers": { ... },
"body": "JSON string"
}
So you basically need to return this JSON structure. I check this with AWS serverless express library (line 100-105) - which we use - and it's returning this structure.
The major challenge is, that sending the HTTP response to the client is a task for the API Gateway and to do so, you must return a valid JSON response from the lambda in a format that the API gateway unterstands. The execution status of the lambda will be also 200 (because it worked A-Okay) but the API Gateway will transform your response into a 500. This is also why you nedd this double/triple JSON encoding (JSON-as-string in JSON-attributes).