2

Currently I am working on a mini project where I am designing a static website hosted in S3. There is an 'Upload' page on which users will enter name, email and mobile no and files and click on 'Upload' button to invoke the API url endpoint. I have created API gateway url endpoint "https://myAPIName.execute-api.ap-region-1.amazonaws.com/Testing/TestinLambda2" on which I am invoking a post request using XMLHTTPRequest and sending it like this -

    xhttp.open("POST", "https://myAPIName.execute-api.ap-region-1.amazonaws.com/Testing/TestinLambda2", true);
xhttp.send(JSON.stringify({Name:$('#Name').val(),Email:$('#email').val(),MobileNo:$('#mno').val()}));

I am sending the data as JSON input to aws lambda Java function.

I have not done any body mapping settings in AWS API gateway.

Back in AWS side, I am using AWS Lambda Java function using POJO. Below are my classes which I got from AWS lambda documentation -

My Lambda function

package com.amazonaws.lambda.demo;

import java.util.Map;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class FirstLambda implements RequestHandler<InputClass, OutputClass> {

    @Override
    public OutputClass handleRequest(InputClass input, Context context) {
        String greetingString = String.format("Hello %s, %s , %s.", input.getName(), input.getEmail(), input.getMobile());
         //String greetingString = String.format("Hello");
         return new OutputClass(greetingString);
    }

}

My InputClass

package com.amazonaws.lambda.demo;

public class InputClass {

    String Name;
    String Email;
    String MobileNo;

    public String getName(){
        return this.Name;
    }

    public String getEmail(){
        return this.Email;
    }

    public String getMobile(){
        return this.MobileNo;
    }

    public void setName(String Name){
        this.Name = Name;
    }

    public void setEmail(String Email){
        this.Email = Email;
    }

    public void setMobile(String Mobile){
        this.MobileNo = Mobile;
    }

    public InputClass(){

    }

    public InputClass(String Name, String Email, String Mobile){
        this.Name = Name;
        this.Email = Email;
        this.MobileNo = Mobile;
    }
}

My OutputClass

public class OutputClass {

    String greetings;

    public String getStrings()
    {
        return greetings;
    }

    public void setString()
    {
        this.greetings = greetings;
    }

    public OutputClass(String greetings)
    {
        this.greetings = greetings;
    }

    public OutputClass()
    {

    }
}

When I click on the 'Upload' button I get the value on my screen as -

{"strings":"Hello null, null , null."}

This is the same value I get when I test the POST method in API gateway using 'Test' option.

If someone can point out what I am missing here, I would really appreciate.

Thank you very much!

S_T
  • 51
  • 3
  • 10

3 Answers3

1

The reason why Gerards attempt in recreating the issue didn't work is because he tried this using test event rather than through the apigateway.

What is happening here is that when you are invoking the lambda through the gateway the whole json you send is encapsulated inside another object, which has other details such as pathparameters and other related details. For testing this out you can create a new POJO and have your InputClass as a member in it. Change lambda paramter to new POJO and invoke lambda through the api gateway again.

This will give you the data you need.

0

I tested this with the classes you provided using Lambda test event's instead of API Gateway for simplicity, the fix should carry over to your JavaScript as well.

It seems that no matter how you name the class variables in your code, the parameters in the JSON must follow the names your getters/setters. Simply correcting the properties in your code should fix it. This makes sense because your class variables are not accessible from outside the package. So you have to follow the names of the mutator methods.

xhttp.open("POST", "https://myAPIName.execute-api.ap-region-1.amazonaws.com/Testing/TestinLambda2", true);
xhttp.send(JSON.stringify({name: $('#Name').val(), email: $('#email').val(), mobile: $('#mno').val()}));

The following payload:

{
  "Name": "Gerard",
  "Email": "abc@123.com",
  "MobileNo": "8675309"
}

Generates

{"strings":"Hello null, null , null."}

While this payload

{
  "name": "Gerard",
  "email": "abc@123.com",
  "mobile": "8675309"
}

Generated the expected response of:

{"strings": "Hello Gerard, abc@123.com , "8675309"."}
Gerard
  • 101
  • 8
  • Hi Gerard, I will try with name, email and mobileno. My key in javascript was Name, Email and MobileNo and I had the same keys in json payload. Is it something related to case? – S_T Dec 13 '17 at 03:23
  • Don't use `mobileno`, use `mobile` as I think the property names are generated based on the getters and setters, not the properties themselves (Your method is named `getMobile`, not `getMobileNo`). Lambda behind the scenes is using [Jackson](https://github.com/FasterXML/jackson) for parsing and it is probably normalizing the case to _camelCase_ rather than _ProperCase_. – Gerard Dec 13 '17 at 17:12
  • Thanks Gerard. I will try that as well. – S_T Dec 15 '17 at 03:49
  • Hey @S_T, did you have any luck trying out what I suggested? If not, I would like to work through the problem with you! – Gerard Dec 22 '17 at 23:21
  • Hi @Gerard, sorry haven't tried it yet but I am trying it with JSON format now. – S_T Dec 24 '17 at 07:20
0

If your AWS Lambda is behind an API Gateway, the gateway will transform the incoming payload and pass it to the AWS Lambda in a different format (that you might expect).

It adds some metadata about the request (e.g. headers) and wraps everything in a JSON with the following format:

{
 "headers": {} 
 "body": ""
 ...
}

This JSON is passed to your Java AWS Lambda function. You can take a look at this by temporarily adjust your RequestHandler with the following signature:

public class FirstLambda implements RequestHandler<Map<String, Object>, OutputClass> {

    @Override
    public OutputClass handleRequest(Map<String, Object> input, Context context) {
         input.forEach((key, value) -> System.out.println(key + ":" + value);
         return new OutputClass("");
    }

}

Whenever you use the AWS Console and send a test event with a simple payload like

{
  "Name": "Duke",
  "Email": "mail@whatever.io"
}

it does not reflect the payload you receive when you invoke your Lambda using the API Gateway and e.g. curl or Postman.

So in your case, you should use e.g. Jackson or GSON to serialize the String inside the body field of the payload to your POJO.

If you don't want to work with a generic Map, you can also include

<dependency>
  <groupId>com.amazonaws</groupId>
  <artifactId>aws-lambda-java-events</artifactId>
  <version>3.1.0</version>
</dependency>

to your project and use the APIGatewayProxyRequestEvent and APIGatewayProxyResponseEvent wrapper classes and access headers, body, etc.

public class FirstLambda implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEventinput, Context context) {
         System.out.println(input.getBody());

        // e.g. use the ObjectMapper from Jackson to parse the String body to your POJO
         APIGatewayProxyResponseEvent result = new APIGatewayProxyResponseEvent();
         result.setStatusCode(200);
         result.setBody(objectMapper.writeValueAsString(new OutputClass(""));
         return result;
    }

}

Another solution would be to adjust the way the API Gateway passes data to your Lambda function, as explained here

rieckpil
  • 10,470
  • 3
  • 32
  • 56