I recently migrated from hosting this site on Gitlab pages (still an excellent option, BTW) to hosting on AWS S3 via CloudFront. This solution isn’t cheaper, but I’ve been working on AWS concepts of late and thought this would be a good exercise - and I was right! :-)

I expect to write more about what I learned in that exercise, but I wanted to start with the problem of directory index files, since this solution didn’t come up often in my search for solutions. There are lots of pages out there talking about hosting static pages on S3 and CloudFront has a Default root object setting for each Distribution. But I discovered that only works for the root of the distribution - not any subdirectories.

The simplest solution I found (there may be others) was the use of CloudFront functions.

What is it?

As described in the AWS documentation:

CloudFront Functions is ideal for lightweight, short-running functions for use cases like the following:

  • Cache key normalization – You can transform HTTP request attributes (headers, query strings, cookies, even the URL path) to create an optimal cache key, which can improve your cache hit ratio.
  • Header manipulation – You can insert, modify, or delete HTTP headers in the request or response. For example, you can add a True-Client-IP header to every request.
  • URL redirects or rewrites – You can redirect viewers to other pages based on information in the request, or rewrite all requests from one path to another.
  • Request authorization – You can validate hashed authorization tokens, such as JSON web tokens (JWT), by inspecting authorization headers or other request metadata.

CloudFront Functions support two types of event:

  • Viewer request
  • Viewer response

CloudFront also supports lambda functions via Labmda@Edge executions. Lambda@Edge supports the viewer request and response events as well as Origin requests and response events. But a directory index redirect is simple enough that Lambda@Edge wasn’t needed.

Of note, CloudFront Functions cannot include response body content. Hence, the Lambda@Edge options.

CloudFront functions are written in Javascript. I am no Javascript expert (to put it mildly), but fortunately this particular use case was included in the CloudFront documentation as an example! (Unfortunately, this use case didn’t come up much in my support queries, so I was glad to have finally found it!)

CloudFront Function

Name: folder_index

function handler(event) {
    // Copied from https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example-function-add-index.html
    // Retrieved on 2021-12-18
    var request = event.request;
    var uri = request.uri;

    // Check whether the URI is missing a file name.
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    }
    // Check whether the URI is missing a file extension.
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

Once this function is saved, it must be tested and then published before attaching it to the CloudFront distribution.

Testing and Deployment

Testing is very straightforward in the CloudFront Functions page:

CloudFront Test page

There are other options available for testing not shown in that screenshot, but the above is sufficient for this function.

Press “Test Function” and look for results simliar to below.

CloudFront test results

Once you’ve validated the function returns the expected results, it can be associated to the Distribution from the Functions page, or back on the Distribution Behaviors tab:

CloudFront Distribution Behavior association

Once that was done, all the pretty URL functionality worked perfectly.

Cost

As with all things AWS, CloudFront is (generally) a pay-as-you-go service. But GeekCabinet is a fairly low traffic site, so I should be able to stay well within the Free Tier for CloudFront:

  • 1 TB of data transfer out
  • 10,000,000 HTTP or HTTPS Requests
  • 2,000,000 CloudFront Function Invocations

But even if GeekCabinet had more traffic, pricing on CloudFront and Functions wouldn’t break the bank.

You are charged for the total number of invocations across all your functions. CloudFront Functions counts an invocation each time it starts executing in response to a CloudFront event globally. Invocation pricing is $0.10 per 1 million invocations ($0.0000001 per invocation).

Conclusion

I’m pleased with what I’ve learned and CloudFront Functions ended up being a simple solution to my problem. And a solution I was suprised wasn’t highlighted in other forums, since I’d imagine this is a common problem.