#middleware #serverless 

Simplify your AWS Lambda functions with GoIntercept

A middleware layer allows developers to focus on business logic when designing and implementing Lambda functions. This way, additional functionality like authentication/authorization, input validation, and serialization, can be added in a modular and reusable way.

Web frameworks such as Echo, Spiral, Gin, among others, provide middleware functionality to wrap HTTP requests and thus provide additional features without polluting the core logic in the HTTP handler.

Similar frameworks exist for the development of AWS Lambda functions. Check Middy! However, no framework is available for Golang-based Lambdas.

Enter GoIntercept!

Elegant and modular middleware for AWS Lambdas with Golang

Elegant and modular middleware for AWS Lambdas with Golang

GoIntercept is a simple but powerful middleware engine that streamlines the development of AWS Lambda functions implemented in Go.

Quick Example

The simple example below shows the power of GoIntercept:

 1package main
 2
 3import (
 4	"context"
 5	"errors"
 6	"github.com/aws/aws-lambda-go/lambda"
 7	"github.com/jpcedenog/gointercept"
 8	"github.com/jpcedenog/gointercept/interceptors"
 9	"log"
10)
11
12type Input struct {
13	Content string `json:"content"`
14	Value   int    `json:"value"`
15}
16
17type Output struct {
18	Status  string
19	Content string
20}
21
22func SampleFunction(context context.Context, input Input) (*Output, error) {
23	if input.Value%2 != 0 {
24		return nil, errors.New("passed incorrect value")
25	} else {
26		return &Output{
27			Status:  "Function ran successfully!",
28			Content: input.Content,
29		}, nil
30	}
31}
32
33func main() {
34	lambda.Start(gointercept.This(SampleFunction).With(
35		interceptors.Notify("Start -1-", "End -1-"),
36		interceptors.CreateAPIGatewayProxyResponse(&interceptors.DefaultStatusCodes{Success: 200, Error: 400}),
37		interceptors.AddHeaders(map[string]string{"Content-Type": "application/json", "company-header1": "foo1", "company-header2": "foo2"}),
38		interceptors.AddSecurityHeaders(),
39		interceptors.ParseInput(&Input{}, false),
40	))
41}

Usage

The example above shows that GoIntercept wraps around your existing Lambda Handlers seamlessly (see highlighted lines). It is designed to get out of the way and remove all the boilerplate related to trivial and repetitive operations, such as logging, response formatting, HTTP header creation, input parsing, and validation, etc.

The steps below describe the process to use GoIntercept:

  1. Implement your Lambda Handler.
  2. Import the gointercept and gointercept/interceptors packages.
  3. In the main() function, wrap your Lambda handler with the gointercept.This() function.
  4. Add all the required interceptors with the .With() method. More interceptors coming soon! Stay tuned!

Execution Order

GoIntercept is based on the onion middleware pattern. This means that each interceptor specified in the With() method wraps around the subsequent interceptor on the list, or the Lambda Handler itself, when the last interceptor is reached.

The sequence of interceptors passed to the .With() method specifies the order in which they are executed. This means that the last interceptor on the list runs just before the Lambda handler is executed. Additionally, each interceptor can contain at least one of three possible execution phases: Before, After, and OnError.

The Before phase runs before the following interceptor on the list, or the Lambda handler itself, runs. Note that in this phase the Lambda handler’s response has not been created yet, so you will have access only to the request.

The After phase runs after the following interceptor on the list, or the Lambda handler itself, has run. Note that in this phase the Lambda handler’s response has already been created and is fully available.

As an example, if three middlewares have been specified and each has a Before and After phases, the steps below present the expected execution order:

Execution StepMiddlewarePhase
1Middleware1Before
2Middleware2Before
3Middleware3Before
4Lambda HandlerN/A
5Middleware3After
6Middleware2After
7Middleware1After

Error Handling

Optionally, an interceptor can specify an OnError phase handler. This handler is triggered whenever an error is raised by the execution of any of the handler’s phases(Before or After) or the Lambda handler itself.

If no OnError handler is specified and an error is raised, the error is simply passed as is to the parent handler (interceptor) or the method that called the Lambda handler.

Custom Interceptors

Custom interceptors are simply instances of the gointercept.Interceptor struct. This struct allows to specify any of the phases executed by the interceptor which are, in turn, specified by the type LambdaHandler:

type LambdaHandler func(context.Context, interface{}) (interface{}, error)

type Interceptor struct {
	Before  LambdaHandler
	After   LambdaHandler
	OnError ErrorHandler
}

All native interceptors are implemented as a function that returns an instance of gointercept.Interceptor. This offers the advantage of specifying configuration parameters that are needed by the interceptor (see the .AddHeaders interceptor in the example above).

Available Middlewares

As of this writing, only a few interceptors are available. However, this post aims to highlight the potential of GoIntercept to work as a flexible framework to implement more useful functionality in the future.

The sections below detail what the initial version of GoIntercept has to offer.

Notify

Used for logging purposes. It prints the two given messages during the Before and After phases respectively.

	lambda.Start(gointercept.This(SampleFunction).With(
		interceptors.Notify("Start -1-", "End -1-"),
	))

CreateAPIGatewayProxyResponse

Formats the output or error of the Lambda handler as an instance of API Gateway Proxy Response.

	lambda.Start(gointercept.This(SampleFunction).With(
		interceptors.CreateAPIGatewayProxyResponse(&interceptors.DefaultStatusCodes{Success: 200, Error: 400}),
	))

AddHeaders

Adds the given headers (provided as key-value pairs) to the response. It converts the response to an APIGatewayProxyResponse if it is not already one.

	lambda.Start(gointercept.This(SampleFunction).With(
		interceptors.AddHeaders(map[string]string{"Content-Type": "application/json", "company-header1": "foo1", "company-header2": "foo2"}),
	))

ParseInput

Parses the JSON-encoded payload (request) and stores it in the value pointed to by its input.

	lambda.Start(gointercept.This(SampleFunction).With(
		interceptors.ParseInput(&Input{}, false),
	))

AddSecurityHeaders

Adds the default security HTTP headers. It converts the response to an APIGatewayProxyResponse if it is not already one. These headers follow security best practices, similar to what is done by HelmetJS.

	lambda.Start(gointercept.This(SampleFunction).With(
		interceptors.AddSecurityHeaders(),
	))

The list below presents the HTTP security headers that are added by default:

Wrapping up…

GoIntercept aims to become the de facto standard to implement middleware layers for Golang-based AWS Lambdas. The road ahead is still long but exciting. New features are being added on a regular basis.

Stay tuned!

Image by C. M. Sturgeon from Pixabay