AWS API Gateway Response Template Mapping json string cannot be converted - amazon-web-services

My Spring Cloud Function(v3.1.6) based AWS Lambda function (without Lambda proxy integration) returns a list of data via API Gateway in the following format:
{
"isBase64Encoded": false,
"headers": {
"id": "<some_id>",
"contentType": "application/json",
"timestamp": "1644307568294"
},
"body": "{\"resultList\":[{\"id\":\"1\",\"name\":\"item\",(...some other fields...)}]}",
"statusCode": 200
}
My problem here is I want to return response.body in a JSON (ofc I've also created a Model schema) :
{
"resultList": [
{
"id": "1", "name": "item ",(...some other fields...)
}
]
}
I've configured an application/json based Response Template Mapping to transform the response to the desired format:
$util.parseJson($input.json('$.body'))
which returned that I wanted (check the attached image):
But when I call it via Postman, I've got this:
{
"message": "Internal server error"
}
and in CloudWatch I can see this logs:
2022-02-08T08:56:00.688+01:00 (...) Endpoint response body before transformations: [Binary Data]
2022-02-08T08:56:00.694+01:00 (...) Execution failed due to configuration error: Unable to transform response
What can be the problem?

Related

Appsync as Proxy to another Graphql server

I have a existing graphql server provied by 3rd party. I also have my own backend running on EC2 to provide APIs.
I'm trying to build the appsync with aws-cdk for connecting to both 3rd party graphql and my backend instance also.
With the graphql server, appsync will act as proxy to forward queries only. My questions are:
Do we have anyway to load remote schema and populate it in appsync along with its schema?
How can we forward the requests to another graphql server using aws-cdk? I'm trying something like this:
private get _requestMappingTemplate(): string {
return `
{
"version": "2018-05-29",
"method": "GET",
"resourcePath": $util.toJson("/graphql"),
"params": {
"headers": {
"Authorization": "Bearer $ctx.request.headers.Authorization"
},
"body": {
"query": "$util.escapeJavaScript($ctx.info.getSelectionSetGraphQL())"
}
}
}`;
}
But from the aws doc, getSelectionSetGraphQL returns string representation of the selection set, formatted as GraphQL schema definition language (SDL). Although fragments aren't merged into the selection set
Is that possible to setup AppSync for forwarding request to another GraphQL servers? Any best practice to follow?
It's quite a bit more complicated. I'm still working on it, and the best I got so far is given below. It still drops query arguments, so has limited use.
#* TODO: Add some more interesting info to the operation name, e.g. a timestamp *#
#set($operationName = $context.info.parentTypeName)
#set($payloadBody = {
"query": "$util.str.toLower($context.info.parentTypeName) $operationName { $context.info.fieldName $context.info.selectionSetGraphQL }",
"operationName": $operationName,
"variables": $context.info.variables
})
{
"version" : "2018-05-29",
"operation": "Invoke",
"payload":{
"path": "/graphql",
"httpMethod": "POST",
"headers": $util.toJson($ctx.request.headers),
"requestContext": {
"authorizer": {
"claims": $context.identity.claims
}
},
"body": "$util.escapeJavaScript($util.toJson($payloadBody))",
"isBase64Encoded": false
},
}

Spring cloud function routing api gateway null pointer exception

I have a problem with routing using API Gateway headers. I am using org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest as a handler request. I have two functions, they work locally. They work if I set environment variable.
If I use API Gateway headers (spring.cloud.function.definition:lowercase), I get:
{
"errorMessage": "java.lang.NullPointerException",
"errorType": "java.lang.NullPointerException",
"stackTrace": [
"org.springframework.cloud.function.adapter.aws.AWSLambdaUtils.generateMessage(AWSLambdaUtils.java:123)",
"org.springframework.cloud.function.adapter.aws.FunctionInvoker.handleRequest(FunctionInvoker.java:105)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)",
"java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)",
"java.base/java.lang.reflect.Method.invoke(Unknown Source)"
]
}
Example code reproducing the issue is here: https://github.com/cygi/cloudexample
POM is based on samples from Spring Cloud Function codebase (example code (https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing). Spring Cloud Function version is 3.2.1 (sample had a SNAPSHOT version, that uses JAVA 11, which is not available in AWS Lambda, at least without docker).
Reverting to Spring Cloud Function 3.1.6 has resolved the problem.
Test event for AWS Lambda:
{
"body": "foo",
"httpMethod": "POST",
"isBase64Encoded": false,
"headers": {
"spring.cloud.function.definition": "uppercase"
}
}
Result on 3.2.2
{
"statusCode": 417,
"headers": null,
"body": "Failed to establish route, since neither were provided: 'spring.cloud.function.definition' as Message header or as application property or 'spring.cloud.function.routing-expression' as application property."
}
Result on 3.1.6
{
"isBase64Encoded": false,
"headers": {
"id": "758c1873-9377-25af-5ca2-84f55710ff2a",
"contentType": "application/json",
"timestamp": "1644500775689"
},
"body": "\"bbbb\"",
"statusCode": 200
}

Can the "queryStringParameter" in the eventbody of a GET request be moved to the "body" of the event for an AWS API Gateway HTTP API?

I'm trying to get my "queryStringParameters" in the event into the "body" key of the event so I can parse them in the same way that I parse post requests. Is this possible with a HTTP api? I tried using parameter mappings in the integration settings but this only allows me to append the body to the queryString.
The way that you get the values from a POST request using body and with query parameters are different. So both ca not be in the body property as you want.
The queryStringParameters is passed when you add a URL Query String Parameter in your Api gateway resource.
When you send a request to the event in your API the following event will be send to the Lamba function
{
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"isBase64Encoded": true,
"queryStringParameters": {
"foo": "bar"
},
"multiValueQueryStringParameters": {
"foo": [
"bar"
]
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"body":"{ \"time\": \"evening\" }",
"headers": {
...
},
...
}
You be able to get the query parameters of event in your Lambda using the properties:
event.queryStringParameters && event.queryStringParameters.foo
And the body we need to get with the body property:
if (event.body) {
let body = JSON.parse(event.body)
if (body.time)
time = body.time;
}
In this way in your Lambda you need to parse them in different ways.
For more information of how to work with API Gateway and Lambda take a look here: Tutorial: Build a Hello World REST API with Lambda proxy integration

API Gateway Mapping Template for context.requestTime is Blank

I'm attempting to to have an API Gateway act as a proxy to DynamoDB and I'm currently testing with a simple POST call to append the $context.requestId and $context.requestTime to my table. I am getting the following error message:
{
"__type": "com.amazon.coral.validate#ValidationException",
"message": "One or more parameter values were invalid: An AttributeValue may not contain an empty string"
}
and what is sent is:
Mon Apr 15 19:10:24 UTC 2019 : Endpoint request body after transformations: {
"TableName": "BurgerOrders",
"Item": {
"OrderId": {
"S": "1f54a90b-5fb2-11e9-8b31-c9003bb71ec2"
},
"RequestTime": {
"S": ""
}
}
}
The mapping template within Integration Request that I have is:
{
"TableName": "BurgerOrders",
"Item": {
"OrderId": {
"S": "$context.requestId"
},
"RequestTime": {
"S": "$context.requestTime"
},
}
}
I've tried to change $context.requestTime to $context.requestTimeEpoch and I get the same error.
I know this was posted a while back, but according to an AWS dev:
"At this time, [$context.requestTime] is only available when the API has been deployed and API call was invoked to deployed stage."
Source: https://forums.aws.amazon.com/thread.jspa?messageID=697652

aws apigateway lambda always return 502

I have created aws apigateway lambda integration for my proxy server. When i am making get request to the gateway, the request is successfully going through. The lambda function also executes successfully and writes response in outputstream with statusCode as 200. But apigateway always returns 502.
Snippet of handleRequest():
BufferedReader reader = new BufferedReader(new
InputStreamReader(inputStream));
JSONObject event = (JSONObject) parser.parse(reader);
request = Input.builder().setEvent(event).build();
Response response = requestManager.handleRequest(request);
logger.log(String.format("Response [%s]", response.toString()));
JSONObject responseJson = new JSONObject();
responseJson.put("statusCode", response.getStatusCode());
responseJson.put("headers", response.getHeaders());
JSONObject jsonBody = (JSONObject) parser.parse(response.getBody());
responseJson.put("body", jsonBody);
OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
logger.log("response recieved");
logger.log(String.format("responseJson [%s]", responseJson));
writer.write(responseJson.toJSONString());
writer.close();
logger.log(String.format("output stream [%s]", outputStream));
Am i missing anything ?
502 errors with Lambda usuaully indicate that you are using the Lambda proxy method and not generating the proper JSON response. Make sure your response adheres to the appropriate format.
If you are still having problems, please share a sample JSON generated by your Lambda function.
I had the same issue where all of my logs were saying the lambda executed successfully but API Gateway was still returning 502s on every request. Turns out I forgot to configure the response status codes in the API Gateway Console so it was throwing errors because the response was "incorrect" even with proper formatting. Just add in a status code 200, 400, 403, etc. to the route on your gateway and that might solve your problem.
Make sure you're hitting your callback with the response before calling context.succeed(event) or some other call to context.
This was my problem and putting my callback first fixed the persistent 502 res.
There are two versions of the response format. It might be that are posting a version 1 format response to an endpoint expecting version 2:
Lambda function response for format 1.0
With the 1.0 format version, Lambda integrations must return a response in the following format.
{
"isBase64Encoded": true|false,
"statusCode": httpStatusCode,
"headers": { "headername": "headervalue", ... },
"multiValueHeaders": { "headername": ["headervalue", "headervalue2", ...], ... },
"body": "..."
}
Lambda function response for format 2.0
With the 2.0 format version, API Gateway can infer the response format for you. API Gateway makes the following assumptions if your Lambda function returns valid JSON and doesn't return a statusCode:
isBase64Encoded is false.
statusCode is 200.
content-type is application/json.
body is the function's response.
The following examples show the output of a Lambda function and API Gateway's interpretation.
Lambda function output
API Gateway interpretation
"Hello from Lambda!"
{ "isBase64Encoded": false, "statusCode": 200, "body": "Hello from Lambda!", "headers": { "content-type": "application/json" }}
{ "message": "Hello from Lambda!" }
{ "isBase64Encoded": false, "statusCode": 200, "body": "{ \"message\": \"Hello from Lambda!\" }", "headers": { "content-type": "application/json" }}
To customize the response, your Lambda function should return a response with the following format:
{
"cookies" : ["cookie1", "cookie2"],
"isBase64Encoded": true|false,
"statusCode": httpStatusCode,
"headers": { "headername": "headervalue", ... },
"body": "Hello from Lambda!"
}