Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

API Gateway POST method working during tests, but not with postman

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}"`));
    }
};
like image 932
E. Donadei Avatar asked Mar 26 '19 09:03

E. Donadei


3 Answers

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.

like image 110
Thales Minussi Avatar answered Oct 11 '22 05:10

Thales Minussi


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

like image 2
Eshanel Avatar answered Oct 11 '22 07:10

Eshanel


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

Deploy API

like image 1
bob Avatar answered Oct 11 '22 07:10

bob