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!
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:
- Implement your Lambda Handler.
- Import the gointercept and gointercept/interceptors packages.
- In the main() function, wrap your Lambda handler with the gointercept.This() function.
- 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 Step | Middleware | Phase |
---|---|---|
1 | Middleware1 | Before |
2 | Middleware2 | Before |
3 | Middleware3 | Before |
4 | Lambda Handler | N/A |
5 | Middleware3 | After |
6 | Middleware2 | After |
7 | Middleware1 | After |
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:
- DNS Prefetch Control
- Frameguard
- Hide Powered-By
- HTTP Strict Transport Security
- IE No Open
- Don't sniff Mimetype
- Referrer Policy
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