9

i will try to explain my problem clearly.

I have an API who writes something in DynamoDB with a lambda function written in Node.js. When i'm calling it within the AWS console, the API works as expected. I send a body like that:

{
        "user-id":"4dz545zd",
        "name":"Bush",
        "firstname":"Gerard",
}

And that creates the entry within my dynamoDB table. But when i call the same API (freshly deployed) with Postman, i get this error:

{
    "statusCode": "400",
    "body": "One or more parameter values were invalid: An AttributeValue may not contain an empty string",
    "headers": {
        "Content-Type": "application/json"
    }
}

When i check in cloudwatch why it fails, i see: Method request body before transformations: [Binary Data]

This is weird, because i sent JSON with the two headers:

Content-Type:application/json
Accept:application/json

And then in cloudwatch, i see that being processed is:

{
        "user-id":"",
        "name":"",
        "firstname":"",
}

Thats explains the error, but i don't understand why when i'm sending it with postman, being not empty, with the json format, it still sends it as "binary" data, and so not being processed by my mapping rule (And so lambda processing it with an empty json):

#set($inputRoot = $input.path('$'))
  {
  "httpMethod": "POST",
  "body": {
    "TableName": "user",
    "Item": { 
        "user-id":"$inputRoot.get('user-id')",
        "name":"$inputRoot.get('name')",
        "firstname":"$inputRoot.get('firstname')",
                }
            }
}

Thank you in advance !

EDIT: I'm adding the lambda code function

'use strict';

console.log('Function Prep');
const doc = require('dynamodb-doc');
const dynamo = new doc.DynamoDB();

exports.handler = (event, context, callback) => {

    const done = (err, res) => callback(null, {
        statusCode: err ? '400' : '200',
        body: err ? err.message : res,
        headers: {
            'Content-Type': 'application/json'
        },
    });

    switch (event.httpMethod) {
        case 'DELETE':
            dynamo.deleteItem(event.body, done);
            break;
        case 'HEAD':
            dynamo.getItem(event.body, done);
            break;
        case 'GET':
            if (event.queryStringParameters !== undefined) {
                dynamo.scan({ TableName: event.queryStringParameters.TableName }, done);
            }
            else {
                dynamo.getItem(event.body, done);
            }
            break;
        case 'POST':
            dynamo.putItem(event.body, done);
            break;
        case 'PUT':
            dynamo.putItem(event.body, done);
            break;
        default:
            done(new Error(`Unsupported method "${event.httpMethod}"`));
    }
};
E. Donadei
  • 93
  • 1
  • 1
  • 5

4 Answers4

14

That's because when testing from AWS Lambda's console, you're sending the JSON you actually expect. But when this is invoked from API Gateway, the event looks different.

You'll have to access the event.body object in order to get your JSON, however, the body is a Stringified JSON, meaning you'll have to first parse it.

You didn't specify what language you're coding in, but if you're using NodeJS you can parse the body like this:

JSON.parse(event.body).

If you're using Python, then you can do this:

json.loads(event["body"])

If you're using any other language, I suggest you look up how to parse a JSON from a given String

That gives what you need.

This is what an event from API Gateway looks like:

{
    "path": "/test/hello",
    "headers": {
      "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
      "Accept-Encoding": "gzip, deflate, lzma, sdch, br",
      "Accept-Language": "en-US,en;q=0.8",
      "CloudFront-Forwarded-Proto": "https",
      "CloudFront-Is-Desktop-Viewer": "true",
      "CloudFront-Is-Mobile-Viewer": "false",
      "CloudFront-Is-SmartTV-Viewer": "false",
      "CloudFront-Is-Tablet-Viewer": "false",
      "CloudFront-Viewer-Country": "US",
      "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com",
      "Upgrade-Insecure-Requests": "1",
      "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",
      "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)",
      "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==",
      "X-Forwarded-For": "192.168.100.1, 192.168.1.1",
      "X-Forwarded-Port": "443",
      "X-Forwarded-Proto": "https"
    },
    "pathParameters": {
      "proxy": "hello"
    },
    "requestContext": {
      "accountId": "123456789012",
      "resourceId": "us4z18",
      "stage": "test",
      "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9",
      "identity": {
        "cognitoIdentityPoolId": "",
        "accountId": "",
        "cognitoIdentityId": "",
        "caller": "",
        "apiKey": "",
        "sourceIp": "192.168.100.1",
        "cognitoAuthenticationType": "",
        "cognitoAuthenticationProvider": "",
        "userArn": "",
        "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",
        "user": ""
      },
      "resourcePath": "/{proxy+}",
      "httpMethod": "GET",
      "apiId": "wt6mne2s9k"
    },
    "resource": "/{proxy+}",
    "httpMethod": "GET",
    "queryStringParameters": {
      "name": "me"
    },
    "stageVariables": {
      "stageVarName": "stageVarValue"
    },
    "body": "'{\"user-id\":\"123\",\"name\":\"name\", \"firstname\":\"firstname\"}'"
  }

EDIT

After further discussion in the comments, one more problem is that the you're using the DynamoDB API rather than the DocumentClient API. When using the DynamoDB API, you must specify the types of your objects. DocumentClient, on the other hands, abstracts this complexity away.

I have also refactored your code a little bit (only dealing with POST at the moment for the sake of simplicity), so you can make use of async/await

'use strict';

console.log('Function Prep');
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {

    switch (event.httpMethod) {
        case 'POST':
            await dynamo.put({TableName: 'users', Item: JSON.parse(event.body)}).promise();
            break;
        default:
            throw new Error(`Unsupported method "${event.httpMethod}"`);
    }
    return {
        statusCode: 200,
        body: JSON.stringify({message: 'Success'})
    }
};

Here's the Item in DynamoDB:

enter image description here

And this is my Postman request:

enter image description here

With proper headers:

enter image description here

When creating API Gateway, I checked the box Use Lambda Proxy integration. My API looks like this:

enter image description here

If you reproduce these steps it should just work.

Thales Minussi
  • 6,965
  • 1
  • 30
  • 48
  • Thank you for the fast response. But it works when i test it from API Gateway and from lambda tests. Should i parse within the lambda function ? Here is my modified code: dynamo.putItem(JSON.parse(event.body), done); break; case 'PUT': dynamo.putItem(JSON.parse(event.body), done); break; – E. Donadei Mar 26 '19 at 10:00
  • Can you please edit your question with the relevant code? – Thales Minussi Mar 26 '19 at 10:02
  • By looking at your question, it doesn't look like it works from API Gateway. When you invoke an URL via Postman, you're then triggering API Gateway which will in turn invoke your Lambda function – Thales Minussi Mar 26 '19 at 10:03
  • And I know why it's not working. It's because you're using DynamoDB rather than the DocumentClient. I'll edit my answer accordingly, but please post your code just in case I am missing anything. – Thales Minussi Mar 26 '19 at 10:05
  • It actually does ! I tried all my POT & PUT methods, and within the API Gateway test, it just works – E. Donadei Mar 26 '19 at 10:05
  • I have edited my answer accordingly, including some refactoring :) – Thales Minussi Mar 26 '19 at 10:11
  • I did all the edits in my code and it works in API Gateway and lambda aswell. But still not in Postman. Same error ! – E. Donadei Mar 26 '19 at 10:17
  • What do you mean it works in API Gateway but not in Postman? How are you invoking a POST request via API gateway? – Thales Minussi Mar 26 '19 at 10:21
  • I'm using the "TEST" option when i'm on my POST Method. I put my body with no headers. And i receive a 200 code sucess, and the entry is created in dynamoDB. – E. Donadei Mar 26 '19 at 10:24
  • Well, I still believe it's a problem of not parsing the JSON when coming from Postman. I've edited my answer on the post line to parse the JSON before persisting it. That's my last resort, I think (reposting this comment fixing a few typos) – Thales Minussi Mar 26 '19 at 10:38
  • I think we're being very close to the solution and thank you for your help ! But even with all these changes, the json being processed by dynamodDB still has empty values. So i'm having the same error. – E. Donadei Mar 26 '19 at 10:40
  • @E.Donadei I have edited my answer with a working example (it worked when I tested it, screenshots attached to the answer) – Thales Minussi Mar 26 '19 at 10:51
  • I tried it, used your lambda function, linked to a POST Method in API gateway by using proxy. No more parameters. And it's just not working on my side. I'm seeing in cloudwatch that he's sending suspicious binary data in POST body, but still not my simple JSON content. So that create a "message": "Internal server error" when sending back the JSON response to postman. – E. Donadei Mar 26 '19 at 12:29
  • I'm sorry @E.Donadei I've run out of ideas since I have done it from scratch and it worked like a charm. Maybe there's another configuration in your API Gateway which you're not seeing or you're sending a wrong header. I am sorry. – Thales Minussi Mar 26 '19 at 12:32
3

I got the exact same problem, the solution for me was deploying the api to make my changes available through Postman !

Hope it helps, even one year later

Eshanel
  • 946
  • 8
  • 6
1

you need to deploy your Amazon API Gateway!!! It took me forever to figure this out, than

Deploy API

bob
  • 21
  • 1
  • 2
  • I was deploying to APIGateway via Cloudformation. Turns out, that is not enough to rollout the new api. You have to manually go into the API gateway and hit Deploy API. Thanks for this. I was going to go crazy after speding 8 hrs trying to figure out why api did not work – Soren Goyal Jul 11 '22 at 23:50
0

I encountered the same problem while working with java and I fixed it by just checking the Use Lambda Proxy integration for POST method.

alps_ren
  • 11
  • 2