Guilherme Cabral
Guilherme Cabral

Reputation: 111

Lambda@Edge function not being called on Cloudfront error page

I have an Angular app's static files being served on an S3 bucket through Cloudfront. My Cloudfront distribution has error pages set up so it still renders the Angular's index.html. This means that if I request <cloudfront-distribution>.cloudfront.net/home-page, instead of saying that it didn't find a file named home-page on the S3 bucket, it will still render the angular app and the angular app will handle that /home-page route.

I needed to include some security headers on the app server so I set up a Lambda@Edge function to inject those headers on a viewer response event (like described here https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/).

The Lambda@Edge is working for routes that actually correspond to a file in the S3 bucket (if I have a file called image.png on the root folder of my S3 bucket, and I request <cloudfront-distribution>.cloudfront.net/image.png, I see the response headers that I injected via the Lambda@Edge function. The issue is when accessing a route that doesn't correspond to a file in the S3 bucket. If I access <cloudfront-distribution>.cloudfront.net/home-page, S3 will return a 404, Cloudfront will handle the 404 and act accordingly to the Error Pages configuration, i.e., respond with a 200 status code and render the index.html file. And when this happens, I don't see any of the headers I injected via the Lambda@Edge function, while all the other script files of my Angular app have the headers.

How can I make all responses go throught the Lambda@Edge function?

Upvotes: 11

Views: 4329

Answers (3)

select
select

Reputation: 2618

Based on the answer of @philarmour I will show a concrete example for a solution.

Remove the custom 404 page that returns /index.html in your CloudFront Distribution. Then add a new CloudFront Function (customize the code below to your needs)

var rootPaths = {
    css: 1,
    favicon: 1,
    fonts: 1,
    img: 1,
    js: 1,
    'robots.txt': 1,
};
function handler(event) {
    var firstPart = event.request.uri.split('/')[1];
    if (!(firstPart in rootPaths)) event.request.uri = '/index.html';
    return event.request;
}

Associate the function with your CloudFront Distribution on VIEWER_REQUEST and don't forget to publish it.

Upvotes: 2

philarmour
philarmour

Reputation: 414

I just went through this exact same situation. I found this page in the AWS docs to be most helpful: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html

While the current answer is definitely correct for this question, I had another constraint that might be helpful to discuss:

  • CloudFront Functions are only triggered for Viewer Request/Response events; As opposed to Lambda@Edge Functions which can be trigger by Origin or Viewer events. In my case I wanted to keep using the CloudFront function as they are lighter/faster/cheaper.

As you observed (and called in the docs) the viewer response event are not trigger when there is as error at the origin - including when a custom error page is returned as a 200.

Option #1 - Swapping to the Origin Request event

If you're already using Lambda@Edge and don't want to change anything this is probably the simplest change.

  • keep in mind this happens before any caching in CloudFront and therefore won't be called again for cache hits. So may not work for all use cases.
  • but, that might also be a good thing as it's fewer lambda executions and the headers should be included in the cache.

Option #2 - Stop Using Custom Error Responses

For me, I went this route. I needed to create a second function for the Viewer Request event that re-writes any requests that are not specifically for an s3 resource so React Router paths return my index.html. (Something like this: https://stackoverflow.com/a/60012469/4413888). finally, removed my existing custom error responses in CloudFront.

  • yes this is another function executing on each resource request, but at about 1/6 the cost of lambda@edge invocations (after free tier), I think I'm still coming out ahead.
  • get the benefit of discovering actual 403/404s from s3 - like when users try to load old webpack chunks and dead links

Upvotes: 7

Mohd yousuf
Mohd yousuf

Reputation: 1

It works fine after running Lambda@Edge Function on Origin Response. However do not forget to run invalidation on CloudFront after making this change. Also it would save money as Lambda would only be executed when request would be sent to Origin

Upvotes: 0

Related Questions