Networking & Content Delivery

Introducing multi-function packager, allowing more than one function per event trigger on Amazon CloudFront

In this post, you’ll learn about the ‘multi-function packager’ framework that handles the assembly and execution of discrete Edge functions.

Amazon CloudFront is a content delivery network (CDN) service that improves the performance, availability, and security of your application, allowing you to serve a consistent experience to your viewers globally. Lambda@Edge and CloudFront Functions are the two edge function features of CloudFront, and they can be associated to different event triggers emitted by CloudFront. This provides developers with the ability to program business logic closer to viewers and customize how CloudFront responds. Moving your business logic to the edge reduces latency and offloads requests from your origin servers.

As business requirements grow over time, you may need to perform multiple operations in the request/response flow. For example, you want to normalize request attributes for better cache performance, perform A/B tests, rewrite or redirect URLs, or switch origins intelligently on a request. The following diagram shows some of the common use cases and the most appropriate event triggers for them.

Figure 1 CloudFront event triggers and common use cases

Figure 1 CloudFront event triggers and common use cases

Each use case is a distinct operation on its own, and you implement them either as a CloudFront Function or Lambda@Edge function. However, to apply these operations in the same event trigger, you would copy the code and its dependencies into a single monolith function, which is then associated to the event trigger. For Lambda@Edge functions, apart from copying code artifacts, you would need to combine the AWS Identity and Access Management (IAM) roles and policies from each function, and then re-evaluate the memory and timeout requirements.

Furthermore, there could be dependencies between these operations regarding their sequence of execution as downstream components could be expecting certain attributes which are set by upstream operations. For example, you want to normalize the URL before a subsequent module would check a pre-defined list of redirections for a match.

Here is an example of multiple operations: Incoming request → normalize → check redirects → rewrite URI→ CloudFront cache.

The code associated with these discrete use cases could be well-organized into some form of libraries, but it still needs engineering and refactoring efforts to combine, test, and maintain them.

In this post, you’ll explore one such way using the ‘Multi-function packager’ framework that handles the assembly and execution of discrete Edge function logic ‘as-is’ and returns the desired output.

The assembled function can be associated with the desired CloudFront event trigger just like you would associate any other Lambda@Edge or CloudFront Function. This would let you mix and match existing functional implementation, reusing code while enabling better maintainability as individual functional logic evolves over time.

Prerequisites

The following prerequisites are required to follow along with this post:

1. Today this framework is built to support only Node.js runtimes for Lambda@Edge functions and JavaScript for CloudFront Functions.

2. You must have the Lambda@Edge and CloudFront Functions defined in your AWS account.

In the rest of this post, you’ll walk through the deployment and usage of this framework.

Deployment

1. Clone and deploy the CDK solution in AWS N.Virginia Region (us-east-1). From a terminal

git clone https://github.com/aws-samples/amazon-cloudfront-multi-function-packager.git
cd amazon-cloudfront-multi-function-package
npm audit fix
cdk bootstrap
cdk deploy

2. The stack creates an Amazon Simple Storage Service (Amazon S3) bucket to hold the assembled code artifacts and three AWS Lambda Functions, as explained in the following:

  • {StackName}-LambdaFunctionPackager{UniqueID}: the main Lambda Multi-function packager
  • {StackName}-LambdaFunctionAssembly{UniqueID}: the helper function to assemble the Lambda@Edge function
  • {StackName}-CloudFrontFunctionAssembly{UniqueID}: the helper function to assemble CloudFront Functions
Figure 2 Multi-function packager helper functions

Figure 2 Multi-function packager helper functions

Procedure to chain Lambda@Edge functions

1. To combine Lambda@Edge functions, navigate to {StackName}-LambdaFunctionAssembly function on the AWS Lambda console in us-east-1 AWS Region.

2.We’ll use the ‘Test’ feature of AWS the Lambda service to build our combined function artifacts. Switch to the ‘Test’ tab to ‘Create new event’, and then give it a name.

Figure 3 Procedure to chain Lambda@Edge functions

Figure 3 Procedure to chain Lambda@Edge functions

For ‘Event JSON’, use the following JSON structure to combine the Lambda@Edge Functions using their ARNs.

{
"viewer-request":[
    {
    "function_arn":"Lambda Function ARN1:Version1"
    },
    {
    "function_arn":"Lambda Function ARN2:Version2"
    }
 ]
}

Here is an example of combining Lambda@Edge functions and attaching them to more than one event trigger.

{
"origin-request":[
    {
        "function_arn":"arn:aws:lambda:us-east-1:123456789012:function:RedirectionFunction:1"
    },
    {
        "function_arn":"arn:aws:lambda:us-east-1:123456789012:function:ABTestingCookieFunction:1"
    },
    {
        "function_arn":"arn:aws:lambda:us-east-1:123456789012:function:DeviceDetectionFunction:1"
    }
    
 ],
"origin-response":[
    {
        "function_arn":"arn:aws:lambda:us-east-1:123456789012:function:CacheControlFunction:$LATEST"
    },
    {
        "function_arn":"arn:aws:lambda:us-east-1:123456789012:function:HSTSInsertionFunction:2"
    }
 ]
}

Note that you should change Lambda Function ARNs to match your environment.

3. The JSON structure defines three Lambda@Edge function ARNs, which will be combined (in the order specified) to a new Lambda function and associated to the ‘Origin Request’ trigger. The new combined function will also have two Lambda@Edge functions which will be invoked on the ‘Origin Response’ event trigger. Combining across multiple event triggers could be useful when the same function must operate across multiple event triggers. Moreover, it helps reduce cold starts and the reuse of Lambda containers across event types.

The Lambda Function ARNs could be pointing to a particular Lambda version or to $LATEST, and each function could have a different entry point handler. The framework will infer all of this information from the function metadata associated.

4. Save changes to the event and select Test in the Lambda console to generate the combined function and IAM role. Refer to the GitHub repository for detailed instructions.

You can generate a combined packaged function per event trigger or combine multiple triggers. The Multi-function packager framework is agnostic to event triggers and invokes only relevant functions for the event type currently being triggered.

5. Publish a version and associate to your CloudFront distribution’s behavior.

6. Then test if it works.

Procedure to chain CloudFront Functions

1. To combine CloudFront Functions, navigate to {StackName}-CloudFrontFunctionAssembly function on the AWS Lambda console in us-east-1 AWS Region.

2. Switch to the ‘Test’ tab to create a test event.

Figure 4 Procedure to chain CloudFront Functions

Figure 4 Procedure to chain CloudFront Functions

Define a ‘New Event’ with the following JSON structure to combine CloudFront Functions using the function name and their deployment stage.

{
  "viewer-request": [
    {
      "function_name": "Function Name",
      "stage": "DEVELOPMENT || LIVE"
    },
    {
      "function_name": "CanaryFunction",
      "stage": "DEVELOPMENT || LIVE"
    },
    {
        "function_name":"AddIndexHtml",
        "stage":"DEVELOPMENT || LIVE"
    }
  ]
}

Here is an example of combining CloudFront Functions and attaching them to a single event trigger.

{
  "viewer-request": [
    {
      "function_name": "TrueClientIP",
      "stage": "DEVELOPMENT"
    },
    {
      "function_name": "CanaryFunction",
      "stage": "LIVE"
    },
    {
        "function_name":"AddIndexHtml",
        "stage":"DEVELOPMENT"
    }
  ]
}

Note that you should change the function_name and stage to match your environment.

Save changes to the event.

3. Select Test in the CloudFront Functions console to generate the new combined CloudFront Function.

4. Check the CloudFront Functions console for the newly created combined Function. Study the code structure.

5. Test if it works.

Although this framework is tuned to combine edge functions with CloudFront as the event source, it can be extended to support other AWS Services by modifying the handling of the event structure accordingly.

Conclusion

To summarize, you learned how to use the ‘multi-function packager’ framework to combine multiple Lambda@Edge or CloudFront Functions to build complex request/response handling at the edge with CloudFront. Depending on your deployment scenario, we hope that this post helps.

Jaiganesh Girinathan

Jaiganesh Girinathan

Jaiganesh Girinathan is a Senior Edge Specialist Solutions Architect focused on content delivery networks and edge computing capabilities with AWS. He has worked with several media customers globally over the last two decades, helping organizations modernize & scale their platforms. He is passionate about building solutions to address key customer needs. Outside of work, you can usually find Jaiganesh star gazing!