AWS Lambda, Ruby behind Application Load Balancers

One of the latest announcements to come out of re:Invent 2018 was the native Ruby support for AWS Lambda functions. That news alone was fantastic for our team, but they took it a step further by allowing the placement of Lambda functions behind an Application Load Balancer (ALB). We liked the idea of not having to rely on an API Gateway.

Hot on the heels of that news, the Serverless Framework merged a PR that provided Ruby support, offering a new aws-ruby template.

Setting up a basic Lambda function using the Serverless Framework and deploying it is very straightforward. View their how-to here.

If you need to include gems that bundle with native extensions, you should read this blog post from Benjamin Curtis. He has released a docker image that will bundle all of your dependencies, making them compatible with AWS Lambda.

While this setup works, you will quickly run into an issue once you place your Lambda function behind an Application Load Balancer. Out of the box, the aws-ruby template doesn’t fulfill the Application Load Balancers response requirements. You will receive a 502 response when hitting your end-point.

While looking through the documentation here, you might happen to notice the following:

The response from your Lambda function must include the Base64 encoding status, status code, status description, and headers. You can omit the body. The statusDescription header must contain the status code and reason phrase, separated by a single space.

(At the time of this post, the aws-ruby template does not include these attributes in the response object by default)

Without those additional attributes, you will receive a 502 response when hitting your Application Load Balancer. It will still make the call to your Lambda function, but you will never receive the response as it doesn’t adhere to the public interface the Application Load Balancer is expecting to receive.

Thankfully, it is easy to fix:


def hello(event:, context:)
  { statusCode: 200, body: "hello" }


def hello(event:, context:)
  { statusCode: 200, statusDescription: "200 OK", isBase64Encoded: false, body: "hello", headers: { "Content-Type" => "text/html" } }

You can write a test to ensure that your other functions honour the interface.

test "it must adhere to the AWS response interface for ALB" do
  response = hello(event: {}, context: {})

  assert_equal 200, response[:statusCode]
  assert_equal "200 OK", response[:statusDescription]
  assert_equal false, response[:isBase64Encoded]
  assert_equal "text/html", response[:headers]["Content-Type"]