DEV Community

Jurriaan Proos
Jurriaan Proos

Posted on • Originally published at jurriaan.cloud on

Redirect www to non-www using Lambda@Edge

A recurring question among website owners is whether to choose non-www or www URLs. (source)

As I’m considering myself somewhat of a minimalist, I’ve decided I want to serve my website on the non-www domain.

In this post I’ll show you how to redirect viewers from the www domain to the non-www domain using Lambda@Edge.

Lambda@Edge

Lambda@Edge is an extension of AWS Lambda, a compute service that lets you execute functions that customize the content that CloudFront delivers. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html

There are four CloudFront events that can trigger a Lambda function to execute: viewer request, origin request, origin response and viewer response.

Lambda@Edge events

Image from AWS documentation

The right place to implement our redirect is the viewer request event as we want to redirect the user before CloudFront even checks if the requested object is in the CloudFront cache.

Add the redirect

I’m using the Serverless Framework to provision the CloudFront distribution that is serving this blog. The framework supports the CloudFront events as a trigger so adding a function that does the redirect is relatively easy.

I have configured two A records in Route53 that route traffic to the CloudFront distribution, one for the www domain and one for the non-www domain.

We can check the Host header to determine whether a viewer came in through the www domain or the non-www domain.

If they did come in through the www domain, we return a response with status code 301 and the Location header set to the same url the user came in with except for the www. part stripped. If they didn’t we do not touch the request and pass it on.

In the end, the function looks like this:

def handle_event(event, context):
    request = event["Records"][0]["cf"]["request"]
    headers = request["headers"]
    host_header = headers["host"][0]["value"]

    if host_header.startswith("www."):
        uri = request["uri"]
        qs = request["querystring"]

        domain_without_www = host_header.strip("www.")

        response = {
            "status": "301",
            "statusDescription": "Moved Permanently",
            "headers": {
                "location": [{
                    "key": "Location",
                    "value": f"https://{domain_without_www}{uri}" if not qs else f"https://{domain_without_www}{uri}?{qs}"
                }]
            }
        }

        return response

    return request
Enter fullscreen mode Exit fullscreen mode

I’ve then configured the function in my serverless.yaml like this (irrelevant parts left out):

service: cloudfront

provider:
  name: aws
  region: us-east-1
  runtime: python3.8

custom:
  domainName: <myDomainName>

functions:
  viewerRequest:
    handler: src/functions/viewer_request/handler.handle_event
    events:
      - cloudFront:
          eventType: viewer-request
          origin: http://${self:custom.domainName}.s3-website-eu-west-1.amazonaws.com

resources:
  Resources:
    CloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Aliases:
            - ${self:custom.domainName}
            - www.${self:custom.domainName}
          Origins:
            - Id: ${self:custom.domainName}
              DomainName: ${self:custom.domainName}.s3-website-eu-west-1.amazonaws.com
              CustomOriginConfig:
                OriginProtocolPolicy: http-only
          DefaultCacheBehavior:
            TargetOriginId: ${self:custom.domainName}
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods:
              - GET
              - HEAD
              - OPTIONS
            ForwardedValues:
              QueryString: true
Enter fullscreen mode Exit fullscreen mode

After deploying this I can also see that the Lambda function is associated with the cache behavior in the AWS console.

CloudFront cache behavior associated Lambda

Let’s test it out:

$ curl -iL https://www.jurriaan.cloud
HTTP/2 301
content-length: 0
location: https://jurriaan.cloud/
server: CloudFront
date: Tue, 29 Dec 2020 11:04:14 GMT
x-cache: LambdaGeneratedResponse from cloudfront
via: 1.1 3c01812e357a7900959ea67a1c5782ad.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: QJ-TK3kaLkJ1BMgzxP-LduIHy7efkpWu2Um9LJL0VYOSStLpf90CeQ==

HTTP/2 200
content-type: text/html
content-length: 4301
x-amz-id-2: OeaCkgGlHbaDO6VHVHSghVFysdRLzsPlJ7LcVPCEc658vQIPQ6CyU6PsSOFy/VUW8XSTiXQvVtE=
x-amz-request-id: 6162F12678845FAB
last-modified: Mon, 28 Dec 2020 14:35:41 GMT
server: AmazonS3
date: Tue, 29 Dec 2020 11:04:15 GMT
cache-control: no-cache
etag: "84107066793acaa28d5ffbadc3fec382"
x-cache: RefreshHit from cloudfront
via: 1.1 9fce949f3749407c8e6a75087e168b47.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: ATiO8jCE27cBONXbw4VPcHZ8R_-tEgCdtNMBPN4XZmyl8TkFPJYh9Q==
Enter fullscreen mode Exit fullscreen mode

It works for URLs that contain a path as well:

$ curl -iL https://www.jurriaan.cloud/manage-python-dependencies-in-serverless-projects-with-serverless-layers-plugin/
HTTP/2 301
content-length: 0
location: https://jurriaan.cloud/manage-python-dependencies-in-serverless-projects-with-serverless-layers-plugin/
server: CloudFront
date: Tue, 29 Dec 2020 11:06:31 GMT
x-cache: LambdaGeneratedResponse from cloudfront
via: 1.1 dd133741afef09b02f3e6afd7cb39f40.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: 21uTcM40vvO2yy9clgfcdUThH_26nr4em2zE874wXDW23sn1cP74cQ==

HTTP/2 200
content-type: text/html
content-length: 19357
x-amz-id-2: 9F/cdUB9VxX4RhacXD5+nEk4QQtO3ZAtBR+B/v6RHXtbjgdC3DvjPwFZo5DbzX2DUXfynqyf6XM=
x-amz-request-id: 67F3725866AE73EB
last-modified: Mon, 28 Dec 2020 14:35:41 GMT
server: AmazonS3
date: Tue, 29 Dec 2020 11:06:33 GMT
cache-control: no-cache
etag: "b8c913de37afe87f7662626e09def0f8"
x-cache: RefreshHit from cloudfront
via: 1.1 52102486f97ad6ff39f81538f01349ab.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: qVtHd8fPzsierejaZF-CfjvTONvckVRy0HwbO29Pevc4AXXau4Qhjg==
Enter fullscreen mode Exit fullscreen mode

And we can also confirm that requests to the non-www domain aren’t redirected:

$ curl -iL https://jurriaan.cloud/manage-python-dependencies-in-serverless-projects-with-serverless-layers-plugin/
HTTP/2 200
content-type: text/html
content-length: 19357
x-amz-id-2: 9F/cdUB9VxX4RhacXD5+nEk4QQtO3ZAtBR+B/v6RHXtbjgdC3DvjPwFZo5DbzX2DUXfynqyf6XM=
x-amz-request-id: 67F3725866AE73EB
last-modified: Mon, 28 Dec 2020 14:35:41 GMT
server: AmazonS3
date: Tue, 29 Dec 2020 11:08:07 GMT
cache-control: no-cache
etag: "b8c913de37afe87f7662626e09def0f8"
x-cache: RefreshHit from cloudfront
via: 1.1 552d1a24616d6b8d6e3fbbdf18a54b6a.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS50-C1
x-amz-cf-id: nmPyUhi99-Pt8McBCtgqa1tFxRTbchbOB0XAlS5eF-hD5OklS9_-6g==
Enter fullscreen mode Exit fullscreen mode

🎉

Conclusion

Lambda@Edge is a perfect place to apply this redirect of www to non-www. It can be used for lots of other things as well. Make sure to take into account the requirements and restrictions when using Lambda@Edge for more complex things.

Top comments (0)