Networking & Content Delivery

Handling Redirects@Edge Part 1

A HTTP URL redirect is a webserver function that redirects a user to a different URL from the one they originally requested. Redirections are useful when you want a short easy to remember URL which when accessed redirects you to the actual landing page. URL shortener services are a good example of this use case. Redirects are also helpful when you have a new URL structure for your website and want to migrate all existing URLs to the new structure.

Typically, such redirects are defined and managed as part of your origin infrastructure. Today we will see how to use Amazon CloudFront with Lambda@Edge to offload these redirects from the origin.

Benefits of this approach are:

  1. Simplified origin infrastructure by offloading redirect logic to the edge.
  2. Redirect logic is centrally defined and managed using the Lambda@Edge functions.
  3. Reduces the response time to serve these redirects as they are now being generated closer to the end users using CloudFront.

In this part of the series, we will start by addressing a simple use case with code snippets showcasing how redirections can be handled at the edge and extend it to include more advanced capabilities (including a user interface to define the redirections) in part 2.

Before we delve into this specific use case a brief overview of CloudFront and Lambda@Edge.

Amazon CloudFront is a web service that speeds up distribution of your static and dynamic web content to your users. CloudFront delivers your content through a worldwide network of data centers called edge locations. When a user requests content that you’re serving with CloudFront, the user is routed to the edge location that provides the lowest latency (time delay), so that content is delivered with the best possible performance.

If the content is already in the edge location with the lowest latency, CloudFront delivers it immediately. If the content is not in that edge location, CloudFront retrieves it from an origin that you’ve defined —such as an Amazon S3 bucket or an HTTP server that you have identified as the source for the definitive version of your content.

Lambda@Edge allows you to execute custom business logic closer to the viewer. This capability enables intelligent/programmable processing of HTTP requests at locations that are closer (for the purpose of latency) to your viewer. To get started, you simply author your Lambda code in one Region, US-East-1 (N. Virginia) and pick one of the CloudFront behaviors associated with your distribution.

You can run a Lambda@Edge function in response to four different CloudFront events. The following diagram illustrates the available events:

Lambda@Edge overview

To learn more on Lambda@Edge design best practices please refer to this blog post

Now let’s see below two scenarios describing when to use origin-request and viewer-request event for setting up your redirections. To learn more on factors to consider to decide which CloudFront trigger to use please refer to this documentation.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-how-to-choose-event.html

Consider using origin side functions to maximize the use of CloudFront cache while reducing latency and cost associated with subsequent Lambda function executions. Origin side functions will only run when an object is not in the cache.

Scenario 1: Setup redirections to migrate campaign URLs to a landing page:

Suppose, you want to setup a friendly short-URL for your upcoming campaign and redirect it to the actual landing page quickly without making any configuration change at your origin server. Further, if this response is common across all users then we can setup a Origin-Request Lambda trigger to setup this redirection.Following diagram depicts the flow

The Origin-Request Lambda function (code snippet below) gets executed only on a cache miss and it inspects and redirects to a different URL before the resource is fetched from the origin.

Origin Request Lambda@Edge function code snippet

'use strict';

exports.handler = (event, context, callback) => {
    
    let request = event.Records[0].cf.request;
    
    //if URI matches to 'pretty-url' then redirect to a different URI   
    if(request.uri == "/pretty-url"){
    
    //Generate HTTP redirect response to a different landing page.
    const redirectResponse = {
        status: '301',
        statusDescription: 'Moved Permanently',
        headers: {
          'location': [{
            key: 'Location',
            value: '/somecampaign/index.html',
          }],
          'cache-control': [{
            key: 'Cache-Control',
            value: "max-age=3600"
          }],
        },
    };
    callback(null, redirectResponse);
  }
  else{
      // for all other requests proceed to fetch the resources
      callback(null, request);
  }
};

The generated response is then cached (in this case for 3600 seconds) for subsequent requests. If no redirection is defined for a given URI, then CloudFront fetches the object from the origin.

Let’s walk through the various steps that the request flow would take:

  1. Viewer makes a request for a URL.
  2. If the requested resource is cacheable and available in the cache, it is returned from CloudFront Edge location. In the example above, this would be a cached HTTP 301 redirect to a different URL.
  3. If the resource is not cacheable or not present in the cache, CloudFront fetches the object from the origin you have configured.
  4. The ‘Origin-Request’ Lambda@Edge function intercepts the request from step 3 and checks whether a redirection has been defined for this URI. If yes, the Lambda@Edge function generates a permanent redirection (HTTP 301 status code) which is cached by CloudFront and returned to viewer.
  5. If the URI does not match a redirection rule then the ‘Origin-Request’ function does a pass through of the request.

Scenario 2: Redirect unauthenticated users to Sign In/Up page:

Suppose you have a dynamic application whose Accelerate delivery of dynamic content using CloudFront and you want to ensure that unauthenticated or first time users are automatically redirected to a login/register page. Let’s assume that a secure encrypted cookie is set for authenticated users from your origin. In this case, we need to validate each user request for the presence of authentication cookie. Following diagram depicts the flow:

Let’s understand the various steps through which the request flows through.

  1. Viewer makes a request for a URL.
  2. Viewer-Request Lambda@Edge function looks for the specific session cookie to validate the user session. Note this function is triggered for each request.
  3. If the request does not have the session cookie a temporary redirection (HTTP 302 status code) to the ‘Signup’ page is generated from the ‘Viewer-Request’ Lambda@Edge function and sent to the viewer.
  4. If the request carries the session cookie the ‘Viewer-Request’ Lambda@Edge function simply does a pass through of the request.

Viewer Request Lambda@Edge function code snippet

'use strict';

exports.handler = (event, context, callback) => {

    let request = event.Records[0].cf.request;

    /* check whether is user is authenticated */
    let authenticated = parseAndCheckCookie(request.headers);

    if (authenticated) {

        /* This is an authenticated client and we just want to continue 
         * processing their request. To do that we return the request object.
         */

        callback(null, request);
    }
    else {

        /* Generate HTTP temporary redirect response for anonymous users 
         * using 302 status code with URL to the Sign In/Register page.
         */

        const redirectResponse = {
            status: '302',
            statusDescription: 'Found',
            headers: {
                'location': [{
                    key: 'Location',
                    value: '/signin',
                }],
                'cache-control': [{
                    key: 'Cache-Control',
                    value: "private"
                }],
            },
        };

        /* We want to redirect this client to the signin page and 
         * we do so by returning the redirect response generated 
         * inside the function
         */

        callback(null, redirectResponse);
    }
};

function parseAndCheckCookie(requestHeaders) {

    /* parse all the cookies from request */
    
    let parsedCookies = parseCookies(requestHeaders);

    /* Check for session-id in request cookie in viewer-request event, 
     * if session-id is absent, redirect the user to sign in page. 
     */

    if (parsedCookies && parsedCookies['session-id']) {
        return true;
    }
    else {
        return false;
    }
}

/* logic to parse the cookie */
function parseCookies(headers) {
    const parsedCookie = {};
    if (headers.cookie) {
        headers.cookie[0].value.split(';').forEach((cookie) => {
            if (cookie) {
                const parts = cookie.split('=');
                parsedCookie[parts[0].trim()] = parts[1].trim();
            }
        });
    }
    return parsedCookie;
}

The function validates whether the user is unauthenticated (no session cookie) and redirects him to a sign-in page. For authenticated users, the request just proceeds as per the CloudFront Cache Behavior defined.

Note: Since we are generating the responses from the Viewer-Request trigger these responses not cached.

To learn more on other scenarios where you can generate a HTTP responses with sample code please refer below:

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html#lambda-examples-generated-response-examples

Summary

The main benefits of using Lambda@Edge for handling redirections are:

  1. Offload your origin(s) from managing and synchronizing all the redirection rules, reduced bandwidth and compute cycle cost.
  2. Central management of redirects using Lambda@Edge
  3. Lower latency response to your end users using CloudFront.

In the next part of this series we will look at more advanced capabilities including a UI to help define and manage rules easily.

If you’re new to Amazon CloudFront and Lambda@Edge, I encourage you to refer to Getting Started with Amazon CloudFront and Getting Started with Lambda@Edge documentation for more information on how to get started with our services today. To help debug and troubleshoot Lambda@Edge function please refer here.