Notes on Lambda

Notes

  • By default, serverless framework uses Lambda-Proxy integration. Response must be: { statusCode: N, headers: { ... }, body: '....' } This is what you want 95% of time!!!

  • Lambda integration requires Method Request/Response <==> Integration Request/Response

  • Method Request specifies Authentication requirements. It exists even with proxy integration. The other 3 does not exist with proxy integration.

  • Integration Request/Response uses VTL templates. e.g. You can change it so that the query string is transformed to JSON :

    #set($inputRoot = $input.path('$'))
    {
       "city": "$input.params('city')",
       "time": "$input.params('time')",
    }
    
  • The Lambda integration Response must be of the form::
    { somekey; someval, message : '.....' }

  • The input event is always a JSON object, all headers and query parameters already parsed for you :

    {
     "resource": "Resource path",
     "path": "Path parameter",
     "httpMethod": "Incoming request's method name"
     "headers": {String containing incoming request headers}
    
     "multiValueHeaders": {List of strings containing incoming request headers}
    
     "queryStringParameters": {query string parameters }
     "multiValueQueryStringParameters": {List of query string parameters}
    
     "pathParameters":  {path parameters}
     "stageVariables":  {Applicable stage variables}
     "requestContext": 
           {Request context, including authorizer-returned key-value pairs}
     "body": "A JSON string of the request payload."
     "isBase64Encoded": "A boolean flag to indicate if the applicable 
                         request payload is Base64-encode"
    }
    
  • How to send non-json output ?

  • If the message contains "Error", you can map the error code accordingly.

If you are using proxy lambda integration your lambda should look like this:

// handler.js
// Response must be: {  statusCode: N, headers: { ... }, body: '....' }
//

'use strict';

module.exports.publicHello = (event, context, callback) => {

  // event is JSON representation of input.

  const response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*', // Required for CORS support to work
    },
    body: JSON.stringify({
      message: 'Public Hello!',
      input: event,
    }),
  };

  console.log('Public Event is', event)
  console.log('Public Context is', context)

  callback(null, response);
};

module.exports.privateHello = (event, context, callback) => {

  const response = {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*', // Required for CORS support to work
    },
    body: JSON.stringify({
      message: 'Private Hello!',
      input: event,
    }),
  };

  console.log('Private Event is', event)
  console.log('Private Context is', context)

  callback(null, response);

Example serverless framework template for lambda integration :

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: users
          method: get
          integration: lambda
          request:
            template:
              text/xhtml: '{ "stage" : "$context.stage" }'
              application/json: '{ "httpMethod" : "$context.httpMethod" }'
          response:
            headers:
              Access-Control-Allow-Origin: "'*'"
            statusCodes:
              400:
                pattern: '.*wrong.*'
                template:
                  application/json: >
                    #set ($errorMessageObj = $input.path('$.errorMessage'))
                    $errorMessageObj

Example when using with ANY method:

exports.handler = async (event) => {
    console.log(event);
    if (event.httpMethod === 'PUT'){
        let response = putMovie(event)
        return done(response);
    } else if (event.httpMethod === 'GET'){
        let response = getMovie(event);
        return done(response);
    }
};

//
// e.g. return done({ message: 'OK!', somekey : someval })
//
const done = response => {
    return {
        statusCode: '200',
        body: JSON.stringify(response),
        headers: {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Methods': '*',
            'Access-Control-Allow-Origin': '*'        
        }
    }
}

Invoke Lambda Directly using AWS-SDK

const AWS = require('aws-sdk')
const Lambda = new AWS.Lambda()

async function invokeLambda(functionName) {
  const req = {
    FunctionName: functionName,
    Payload: JSON.stringify({ message: 'hello world' })
  }
  await Lambda.invoke(req).promise()
}

Lambda Coldstart

A cold start happens when code is executed for the first time and consists of three stages:

  • code is downloaded
  • container is started
  • runtime is bootstrapped

Lambda Error Handling

During throttling Lambda returns the following to API Gateway:

{
 "Reason" :"ReservedFunctionConcurrentInvocationLimitExceeded",
 "Type"   :"User",
 "message":"Rate Exceeded."
}

For API Gateway proxy integration, this is wrong format. It expects response in this format :

{
       statusCode: '200',
       body: JSON.stringify(response, null, 2),
       headers: { ... }
}         

Each service may have its own retry policy. API Gateway does not try to retry the failed calls.

Preferred way to return from Lambda function

//
// Prefer this when you do not have promise on hand.
// As soon as he callback is called, the main job is done.
//

exports.handler = (event, context, callback) => {
  var params = {
      Bucket: "examplebucket", 
      Key: "HappyFace.jpg"
  };
  s3.getObject(params, function(err, data) {
      if (err) return callback(err);
      callback(null, data);
  });    
}

//
// Prefer this following style in general whenever possible.
//

exports.handler = async event => {
    var params = {
        Bucket: "examplebucket", 
        Key: "HappyFace.jpg"
    };
    return await s3.getObject(params).promise();
}