Lambda Functions are a FaaS implementation on Amazon Web Services. Setting them as HTTP/S endpoints over API Gateway can be complicated, and more often than not is an overkill for simple, internal APIs. Besides, API Gateway endpoints for Lambda are public, no matter how we slice and dice it. The recently announced VPC Link for API Gateway only allows the endpoints to route to a NLB target, not a Lambda.

This is a lightweight HTTP/S proxy written in Java, which wraps a lambda invocation, mimicking the API Gateway-Lambda Proxy Integration.

The /function endpoint supports GET, PUT, POST, and DELETE. Any request sent to this endpoint is wrapped as APIGatewayProxyRequestEvent.

The Response from the Lambda is wrapped as APIGatewayProxyResponseEvent.

Here is the documentation and samples of various events (this proxy only supports APIGatewayProxy events).

The request path, headers, HTTP method, querystring, body, etc. become a part of the event request that is sent to the Lambda as-is.

An alternative is to call Lambdas from within the code using AWS SDKs. However, having a proxy helps with centralizing monitoring (like NewRelic), security via IAM roles, and logging, making the infrastructure more manageable.

API

  • GET /healthcheck - Used by the load balancer

  • GET /functions - Get a list of lambda functions

  • GET | PUT | POST | DELETE /function - Invoke the Lambda Function named in the header x-lambda-function-name. This header can also contain the ARN of the function.

Deployment

$ git clone git@github.com:lobster1234/lambda-proxy.git
$ cd lambda-proxy
$ mvn clean install jetty:run

This will run the lambda proxy server on jetty, port 8080.

To deploy in Tomcat, copy target/lambda-proxy-1.0-SNAPSHOT.war and copy it to $TOMCAT_HOME/webapps as lambda-proxy.war for the context lambda-proxy or as ROOT.war for /.

IAM Policy

If this proxy will run in an autoscaling group in AWS (recommended), create a role with the below inline policy for the instances -

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "InvokePermission",
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction",
                "lambda:ListFunctions"
            ],
            "Resource": "*"
        }
    ]
}  

If on-prem or on a laptop, attach the above policy to the IAM user whose credentials will sit in ~/.aws/credentials.

Usage

  • Healtcheck for LBs

$ curl -i http://localhost:8080/healthcheck
HTTP/1.1 200 OK
Date: Tue, 12 Dec 2017 02:17:31 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Server: Apache Tomcat/8.5.11

OK
  • Get a list of lambda functions
$ curl -i http://localhost:8080/functions
HTTP/1.1 200 OK
Date: Tue, 12 Dec 2017 02:19:07 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Server: Apache Tomcat/8.5.11

[
  {
    "functionName": "internal-api-function",
    "functionArn": "arn:aws:lambda:us-east-1:************:function:internal-api-function",
    "runtime": "python3.6",
    "role": "arn:aws:iam::************:role/lambda_basic_execution",
    "handler": "lambda_function.lambda_handler",
    "codeSize": 224,
    "description": "",
    "timeout": 3,
    "memorySize": 128,
    "lastModified": "2017-12-11T21:29:15.067+0000",
    "codeSha256": "LatqSbOH1YVr/D1eQBdPdTxeBP9RN2HvcsQ4zq5kSBA=",
    "version": "$LATEST"
  },
  { ..... },


 ]
  • Invoke a lambda function
$ curl -i -X POST http://localhost:8080/function -H 'x-lambda-function-name:internal-api-function'
  HTTP/1.1 200
  Content-Type: application/json
  Transfer-Encoding: chunked
  Date: Fri, 08 Dec 2017 09:22:43 GMT
  Server: Apache Tomcat/8.5.11

  {
     "statusCode":200,
     "headers":{
        "Connection":"keep-alive",
        "Content-Length":"32",
        "Content-Type":"application/json",
        "Date":"Tue, 12 Dec 2017 02:37:26 GMT",
        "X-Amz-Executed-Version":"$LATEST",
        "x-amzn-Remapped-Content-Length":"0",
        "x-amzn-RequestId":"63fb9b2a-dee5-11e7-b8f2-d9f80d0f5343",
        "X-Amzn-Trace-Id":"root\u003d1-5a2f40e6-52ae1b8237280e7433c44129;sampled\u003d0"
     },
     "body":"{\"message\": \"Hello from Lambda\"}"
  }

Typical Deployment in AWS

AWS Deployment

Errors

  • Missing function name
$ curl -i  http://localhost:8080/function                                          
HTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 09 Dec 2017 03:53:12 GMT
Connection: close
Server: Apache Tomcat/8.5.11

{
   "statusCode":400,
   "body":"{\"Error\":\"Must provide x-lambda-function-name header\"}}"
}

  • Function Not Found
$ curl -i -X POST http://localhost:8080/function -H 'x-lambda-function-name:internal-api-func'
HTTP/1.1 404
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 08 Dec 2017 10:00:01 GMT
Server: Apache Tomcat/8.5.11

{
   "statusCode":404,
   "body":"Function not found: arn:aws:lambda:us-east-1:************:function:internal-api-func
   (Service: AWSLambda; Status Code: 404; Error Code: ResourceNotFoundException;
   Request ID: 324e3566-dee6-11e7-a4b9-4b1d43c1a0fb)"
}
  • Runtime Failure
$ curl -i -X POST http://localhost:8080/function -H 'x-lambda-function-name:getBankTransactions-dev-get-accounts'
HTTP/1.1 500
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 08 Dec 2017 09:56:01 GMT
Connection: close
Server: Apache Tomcat/8.5.11

{
   "statusCode":500,
   "headers":{
      "Connection":"keep-alive",
      "Content-Length":"977",
      "Content-Type":"application/json",
      "Date":"Tue, 12 Dec 2017 02:45:17 GMT",
      "X-Amz-Executed-Version":"$LATEST",
      "X-Amz-Function-Error":"Unhandled",
      "x-amzn-Remapped-Content-Length":"0",
      "x-amzn-RequestId":"7a631eae-dee6-11e7-8086-336ec7993e7e",
      "X-Amzn-Trace-Id":"root\u003d1-5a2f42b9-345e852014cba0137b8dc18a;sampled\u003d0"
   },
   "body":"{\"errorMessage\":\"java.lang.ExceptionInInitializerError\",\"errorType\":
   \"java.lang.ExceptionInInitializerError\",\"stackTrace\":[\"com.serverless.foo.AccountsHandler.handleRequest
   (AccountsHandler.java:34)\"],\"cause\":{\"errorMessage\":\"com.serverless.foo.ApplicationException:
   Bad Request\",\"errorType\":\"java.lang.RuntimeException\",\"stackTrace\":\"]}}}"
}

Comments