Dynamically rewrite HTTP headers

Joris Verbogt
Joris Verbogt
May 14 2021
Posted in Engineering & Technology

Use the new CloudFront functions for common use-cases

Dynamically rewrite HTTP headers

When hosting your static website on Amazon Web Services, you commonly expose this content through AWS CloudFront. Up until recently, dynamically modifying responses required setting up Lambda@Edge functions.

We've written about this setup in a previous blog post.

On the one hand, these Lambda functions are very powerful and can be written in many languages, benefiting from the full AWS Lambda stack. On the other hand, however, in a lot of cases they are also too complex and require too many unnecessary steps to deploy and maintain. It is also not always easy to check which version of the Lambda function is used for which CloudFront distribution.

CloudFront functions

Earlier this month, AWS introduced CloudFront functions, which are limited in power, but easier to write and also present you with a clear overview which distribution is using them.

Another important difference is the pricing: CloudFront functions only cost $0.10 per million requests, where Lambda@Edge would cost $0.60.

The functions are written using plain JavaScript with ES 5.1 language features and some additional features from ES 6 and up, like arrow functions and string templates.

Use Cases

A couple of common cases arise when hosting static websites:

  • Add necessary security headers to prevent frame hijacking, content sniffing and insecure connections
  • Add or modify caching headers if they can not be set in the static content objects' metadata
  • Redirect or rewrite parts of the URL

Examples

Each function is called with an event that contains the following properties:

  • context, with information about the distribution and the event type
  • viewer, containing info about the client IP address
  • request, with uri, method, headers, query string parameters and cookies for the request
  • response, when called upon a content response. It contains status, headers and cookies

Let's add some examples for all 3 use cases mentioned above.

Add security headers

function handler(event) {
    // Get contents of response
    var response = event.response;
    var headers = response.headers;
    
    // Set new headers 
    headers['x-ua-compatible'] = {value: 'IE=edge,chrome=1'};
    headers['strict-transport-security'] = {value: 'max-age=63072000'};
    headers['x-content-type-options'] = {value: 'nosniff'}; 
    headers['x-xss-protection'] = {value: '1; mode=block'};
    headers['x-frame-options'] = {value: 'SAMEORIGIN'};
    headers['content-security-policy'] = {value: "frame-ancestors 'self'"};

    // Return modified response
    return response;
}

We can now test this function before publishing it:

And we can see the security headers are added:

Now, associate this function with the Viewer Response of one or more distributions:

This screen also gives you a quick overview, at all times, of the distributions this function is associated with.

Rewrite caching headers

Upon a response with static content, we still have the possibility to rewrite headers based on the request that this response is for:

function handler(event) {
    //Get contents of response
    var request = event.request;
    var response = event.response;
    var headers = response.headers;
    //Set new headers 
    if (request.uri.endsWith('.xml') || request.uri.endsWith('.json')) {
        headers['cache-control'] = {value: 'public, max-age=0, must-revalidate'};
    }
    //Return modified response
    return response;
}

Redirect URIs with duplicate slashes

This function is added in the Viewer Request handling of a distribution, so it can generate a response before even hitting the static content:

function handler(event) {
    //Get contents of response
    var request = event.request;
    var cleanUri = request.uri.replace(/\/+/g,'/')

    //Return modified request
    if (cleanUri !== request.uri) {
        return {
            statusCode: 301,
            statusDescription: 'Moved Permanently',
            headers: {
                'location': { value: cleanUri }
            }
        };
    } else {
        return request;
    }
}

Conclusion

The examples above clearly show that writing these functions is pretty simple. If your current setup is already employing Lambda@Edge, replacing those with the new CloudFront functions should be pretty straight-forward. For detailed information and more examples, check out the CloudFront documentation.

And as always, feel free to contact us if you have any question, suggestion or correction to this post. Get in touch via our Support Channel.

Keep up-to-date with the latest news