Strahil Hristov
Cloud Engineer
Leveraging lambda@edge for dynamic website routing based on cookies
In this post, we’ll explore Lambda@Edge, an innovative service that places “intelligent friends” at strategic points across the globe, delivering dynamic content with speed and precision.
Table of Contents
- Introduction
- Concept of Lambda at Edge
- Infrastructure setup
- Serverless function code
- Origin redirecting URL
- Testing the solution
- CloudFront Functions
- Conclusion
Introduction
In today’s digital landscape, delivering personalized user experiences is key to engaging and retaining website visitors. One effective way to achieve this is by dynamically routing traffic between different versions of a website based on user preferences or other factors. Amazon Web Services (AWS) provides a powerful solution for this scenario through Lambda@Edge, a feature that allows you to run AWS Lambda functions at the edge of the AWS global network.
In this blog post, we’ll explore how to leverage Lambda@Edge to implement dynamic traffic routing based on cookies. We’ll dive into the concept of Lambda@Edge, its benefits, and walk through a practical example of routing traffic to different versions of a website using cookies at the origin request. For the purpose of this practical lab, the entire infrastructure is provisioned with Terraform.
Concept of Lambda at Edge
Lambda@Edge is a powerful feature provided by Amazon Web Services (AWS) that enables the execution of AWS Lambda functions at edge locations of the AWS global network. This integration of Lambda with the edge locations, which are part of Amazon CloudFront CDN, brings serverless computing closer to end users.
Traditionally, Lambda functions are triggered by events within AWS services. However, Lambda@Edge expands this capability by allowing you to associate Lambda functions with specific CloudFront events. When a user makes a request to a CloudFront distribution, the request is automatically routed to the nearest edge location. If a Lambda@Edge function is associated with the corresponding CloudFront event, that function is executed at the edge location. This decentralized execution brings the power of Lambda functions closer to the user, reducing latency and improving response times.
Lambda@Edge supports different types of CloudFront events, including viewer request, origin request, viewer response, and origin response. These events cover various stages of the request-response cycle. By leveraging Lambda@Edge, you can modify request and response headers, manipulate content, redirect requests, customize caching behavior, and perform real-time processing.
Here are some of the key benefits of Lambda@Edge:
- reduced latency
- improved performance
- global scalability
- customization and personalization
- enhanced security
- simplified architecture
- real-time processing
Infrastructure setup
Resources are provisioned with Terraform. For more information regarding the usage of Terraform, please refer to the following article: Terraform 101.
All the source code presented in this article is available in a Github repository.
Terraform infrastructure schema:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ tree lambda-edge-cookies/
├── modules
│ ├── ACM
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── CloudFront
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── s3.tf
│ │ ├── datasources.tf
│ │ └── variables.tf
│ └── Lambda
│ ├── main.tf
│ ├── outputs.tf
│ ├── iam.tf
│ ├── templates
│ │ └── lambda_edge_cookie.js
│ ├── datasources.tf
│ └── variables.tf
├── main.tf
├── variables.tf
└── provider.tf
The Terraform code provisions the following resources:
-
CloudFront: This module sets up a CloudFront distribution, which acts as a content delivery network. It defines two S3 buckets as origins, which will serve as public websites containing both old and new versions. They contain index.html files with redirection logic that allows redirecting to different versions based on user preferences. An alternate domain and an ACM (AWS Certificate Manager) certificate will be attached to the CloudFront distribution. Forwarding of cookies will be enabled to allow inspection of the cookie value by the Lambda Edge function.
-
Lambda: This module provisions a Lambda@Edge function code and an execution role.
-
ACM: This module creates an ACM certificate using DNS validation. The certificate will be used for securing the connection between the viewers and the CloudFront distribution. It also creates an A record alias that points to the CloudFront distribution. This allows the alternate domain to be associated with the CloudFront distribution for serving content.
Important notes
- To use a certificate in AWS Certificate Manager (ACM) to require HTTPS between viewers and CloudFront, make sure you request (or import) the certificate in the US East (N. Virginia) Region (us-east-1).
- The Lambda edge function must be in the US East (N. Virginia) Region. This is because Cloudfront is deployed in us-east-1 region, even though it is a global service. Lambda edge considers N.Virginia region as the source of truth.
- The IAM execution role associated with the Lambda function must allow the service principals lambda.amazonaws.com and edgelambda.amazonaws.com to assume the role.
Restrictions
- You must use a numbered version of the Lambda function, not $LATEST or aliases.
- Lambda runtime support (June 2023):
For more information, please refer to the official AWS documentation Runtime Support.
Serverless function code
The codebase utilizes the Node.js runtime in order to implement the dynamic routing functionality.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
'use strict';
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const newVersionCookie = "version=new";
const oldVersionCookie = "version=old";
const newSiteDomain = "spikeseed-new.s3-website-eu-west-1.amazonaws.com";
const oldSiteDomain = "spikeseed-old.s3-website-eu-west-1.amazonaws.com";
let domain = oldSiteDomain; // Default to old site
if (headers.cookie) {
for (let i = 0; i < headers.cookie.length; i++) {
const cookie = headers.cookie[i].value;
if (cookie.includes(newVersionCookie)) {
domain = newSiteDomain;
break;
} else if (cookie.includes(oldVersionCookie)) {
domain = oldSiteDomain;
break;
}
}
}
// Modify the request headers to serve content from the selected domain
request.headers.host = [{ key: 'host', value: domain }];
callback(null, request);
};
When a request is made, the Lambda@Edge function inspects the request headers to check if there is a cookie present. Specifically, it looks for two different cookies: version=new and version=old. These cookies are used to determine whether the user should be served content from the new version or the old version of the website. If the version=new cookie is found in the request headers, it sets the domain variable to the domain associated with the new version of the website (spikeseed-new.s3-website-eu-west-1.amazonaws.com). Similarly, if the version=old cookie is found, it sets the domain variable to the domain associated with the old version of the website (spikeseed-old.s3-website-eu-west-1.amazonaws.com). By modifying the request.headers.host property, the Lambda function overrides the host header in the request to ensure that the content is served from the selected domain. This effectively routes the request to the appropriate S3 bucket containing the desired version of the website. The code provides a flexible and dynamic way to switch between versions without performing redirects or changing URLs.
Origin redirecting URL
In addition to the Lambda edge function, the S3 buckets associated with the CloudFront distribution are configured to support URL redirection. This allows for seamless switching between multiple versions of the website using simple URL changes. When a user visits a specific URL, the CloudFront distribution intercepts the request and forwards it to the Lambda function, as mentioned earlier. The Lambda function examines the request headers for the presence and value of the version cookie, determining whether to serve content from the new or old version of the website. If the serverless function detects that the user should be redirected to a different version, it modifies the request headers and sets the appropriate domain value associated with the desired version. This effectively changes the destination S3 bucket for serving the requested content. As a result, the user is seamlessly redirected to the correct version of the website without any manual URL changes or the need to reconfigure the CloudFront distribution.
- Old version index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<title>Old Spikeseed Site</title>
</head>
<body>
<h2>Old Spikeseed Site</h2>
<p>Do you want to try the new Spikeseed site?</p>
<a id="btnNewVersion" href="#">Please bring me to the new Spikeseed site</a>
<br>
<img src="https://spikeseed-old.s3.eu-west-1.amazonaws.com/spikeseed-old.png" alt="Image description">
<script>
const btnNewVersion = document.getElementById("btnNewVersion");
btnNewVersion.addEventListener("click", (event) => {
event.preventDefault();
document.cookie = "version=new";
location.reload();
});
</script>
</body>
</html>
- New version index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<title>New Spikeseed Site</title>
</head>
<body>
<h2>New Spikeseed Site</h2>
<p>Do you want to return to the old Spikeseed site?</p>
<a id="btnOldVersion" href="#">Yes, please redirect me to the old good Spikeseed site</a>
<br>
<img src="https://spikeseed-new.s3.eu-west-1.amazonaws.com/spikeseed-new.png" alt="Image description">
<script>
const btnOldVersion = document.getElementById("btnOldVersion");
btnOldVersion.addEventListener("click", (event) => {
event.preventDefault();
document.cookie = "version=old";
location.reload();
});
</script>
</body>
</html>
Testing the solution
To test the solution, follow these steps:
-
Make sure that Lambda@Edge function is associated with the CloudFront distribution at the origin request. This ensures that the Lambda function is triggered before CloudFront forwards the request to the origin.
-
Access the alternate domain name that was defined, in this case, it is https://edge.labs.spikeseed.cloud/. A route 53 alias record has been setup for this purpose. When you initially access this domain, you will be routed to the old version of the website.
-
To test the redirection to the new version, click on the provided link Please bring me to the new Spikeseed site. This will trigger the Lambda function, which will inspect the value of the version cookie. In this case, it will check if the cookie contains version=new. If the cookie is present and has the value version=new, you will be redirected to the new version of the website served from the new S3 origin. To return to the old version, you can follow the same mechanism by clicking on the other redirecting URL. This time, the version cookie should be set to version=old. The Lambda function will recognize the version=old cookie and route you back to the old version of the website.
-
Make sure that the version cookies are whitelisted in the cookies section of the CloudFront configuration (under behaviors). This allows CloudFront to forward and handle these cookies correctly.
During testing, you can inspect the cookie value using Chrome Developer Tools or similar tools. By checking the value of the version cookie before and after clicking on the redirecting URLs, you can observe how the value of the cookie changes, triggering the Lambda@Edge function to serve content from the corresponding S3 origin. By following these steps, you can verify that the solution correctly handles the version cookie and dynamically routes requests to different versions of the website without requiring manual URL changes or CloudFront reconfiguration.
CloudFront Functions
CloudFront Functions is a serverless compute environment provided by AWS that allows you to run lightweight JavaScript functions at the edge locations of the CloudFront content delivery network. It can be considered as an alternative of Lambda@Edge. However, as of now, CloudFront Functions does not support dynamic routing based on cookies natively. While it lacks this specific capability, it can still be useful in other scenarios:
- Modifying and transforming HTTP requests and responses at the edge locations of CloudFront.
- URL rewrites and redirects, which can be valuable for A/B testing.
- Access authorization, enabling you to implement custom authorization logic at the edge.
CloudFront Functions, similar to Lambda@Edge, allows you to execute code in response to events generated by CloudFront. However, there are a few limitations to be aware of. Functions can only be triggered after CloudFront receives a request from a viewer (viewer request) and before CloudFront forwards the response to the viewer (viewer response). Origin request and response events are not supported.
In summary, while CloudFront Functions may not provide dynamic routing based on cookies like Lambda@Edge does, it can still be useful in various other use cases, simplifying certain tasks and offering a more straightforward alternative to Lambda@Edge.
Conclusion
Lambda@Edge empowers with the ability to extend CloudFront’s capabilities and deliver faster, customized, and secure experiences to users across the globe. It provides a flexible and scalable serverless computing model that brings the power of AWS Lambda closer to the end users.
In this particular scenario, the lambda function allows the CloudFront distribution to serve different versions of a website based on the presence and value of the version cookie in the request headers. This provides a flexible and dynamic way to switch between versions of a website without changing the URL or CloudFront configuration.
Overall, using Lambda@Edge functions for website switching based on cookies can provide dynamic and personalized experiences for users while leveraging the benefits of serverless architecture. However, it’s important to carefully consider the complexity, limitations, and potential latency implications before adopting this solution. Additionally, when implementing Lambda@Edge functions, it’s also important to be aware of the associated pricing, which is based on the number of requests processed and the duration of the function execution.