1
votes

I'm using an API-Gateway + Lambda combo to process a POST request. I'm using Node + Serverless Framework.

When I run serverless offline, I am able to send a POST request and have my data stored on an S3. But when I deploy it and run the same POST request, I get a "502 Internal Server Error" message. Because it works locally but not in production, I'm pretty sure I have some permission/config problems.

    saveToS3(newData)
      .then(result => {
        callback(null, {
          statusCode: 200,
          headers: { 'Content-Type': 'application/json' },
          body: "Successfully added data!"
        });
      })
      .catch(e => callback(null, { statusCode: 500, body: JSON.stringify(e) }));

What I've checked:

  • Response body is a string
  • Include status code, body, response header in the response
  • Using a callback pattern to return my response (see above)

What I haven't checked:

  • One possible reason is that my API-gateway api doesn't have access to invoke the lambda function. How do I make that setting in the serverless.yml file?

My yml:

service: myService

provider:
  name: aws
  runtime: nodejs12.x

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:GetObject"
        - "s3:PutObject"
      Resource: "arn:aws:s3:::myS3Bucket/*"

functions:
  saveToS3:
    handler: handler.saveToS3
    events:
      - http:
          path: users
          method: post
          cors: true
plugins:
  - serverless-offline

resources:
 Resources:
   NewResource:
     Type: AWS::S3::Bucket
     Properties:
       BucketName: myS3Bucket
3
Serverless Framework will automatically give API Gateway relevant permissions when it is created in this way. Probably worth double checking the obvious things first, like are you definitely calling the correct url with /users path and trying to run a test from the API Gateway console to check your Lambda response isn't an issue. - K Mo
@KMo good to know, thank you! - tbd_

3 Answers

2
votes

Found the issue, facepalming because it took me hours to find it.

I had two problems:

  • My main lambda function had an "async" in front of it, but I was implementing it with callbacks. Removing the "async" fixed it.

  • My response format was missing the "headers" and "isBase64Encoded" fields. Including that removed the 502 error (see below).

Helpful links: - https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format - (if you're using serverless framework) https://github.com/dherault/serverless-offline/issues/405

If using API Gateway, make sure your lambda function's response looks like the code snippet below. Otherwise it will throw a "502 - Malformed Lambda Function" error.

{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
    "body": "..."
}
1
votes

If you are using proxy integration you need to be careful because for every possible syntax error it will throw an internal server error.

catch(e => callback(null, { statusCode: 500, body: JSON.stringify(e) }));

Can the error be not setting the headers here? If not it might probably be a syntax error.

0
votes

In our case, we are using serverless + API Gateway + Lambda. Our main app.ts exports an async handler wrapped by serverless. Ex (EnvironmentService is just a service layer for the environment config):

const allowedBinaryMimeTypes = EnvironmentService.getFileMimeTypesAllowed();
...
const handler = serverless(app, { binary: allowedBinaryMimeTypes });

Before modifying any code, the 502 Bad Gateway Error log was showing this (AWS CloudWatch):

enter image description here

enter image description here

Our solution was to override the default aws provider timeout in serverless.yml:

where to add override variable

S3Service file (call to getObject):

    static async getObject(type: string, pathParams: APIGatewayProxyEventPathParameters): Promise<any> {
        const params = await this.setRequestParams(type, pathParams.id, pathParams.fileName);

        try {
            // get S3 object/file
            const data = await S3.getObject(params).promise();

            // some bug with AWS converting JPG objects to JPEGg
            const contentType = data.ContentType === 'image/jpeg' ? 'image/jpg' : data.ContentType;

            return {
                statusCode: 200,
                headers: {
                    'Access-Control-Allow-Headers': 'Content-Type',
                    'Access-Control-Allow-Origin': '*', // Required for CORS support to work
                    'Access-Control-Allow-Methods': 'OPTIONS,GET',
                    'Content-Type': contentType
                },
                body: data.Body.toString('base64'),
                isBase64Encoded: true
            };
        } catch (err) {
            console.error(err);
            throw err;
        }
    }