Specifying different error behaviors for different S3 origins on CloudFront

0

Hello, I have the following situation and I need help on how to proceed:

  • I have a multi-page website in a private S3 bucket. It uses an OAI to allow a CloudFront distribution to distribute this website at example.com.

  • I have a single-page application hosted in another private S3 bucket. It uses an OAI to allow the same CloudFront distribution to distribute the website at example.com/app. The CloudFront distribution contains an "Additional Behavior" which directs requests to the path /app* to the app's S3 bucket.

My problem lies in the fact that I need different error behaviors for both origins. The first origin, the S3 bucket containing the website, returns 404.html on 404 Not Found. This is simple enough to do; I can specify the error response for the distribution to use this filepath. The problem is that the second origin, the SPA served at example.com/app, needs to return its index.html document on 404, since it is an SPA. I cannot specify error handling in CloudFront on a per-origin basis.

I have two known workarounds to this issue:

  1. Use the static website feature on S3 for the SPA and specify its error document there.
  2. Create a Lambda@Edge function to somehow customize the error handling behavior for URIs to /app*.

My understanding is that it is recommended to avoid the static website feature of S3 and instead use an OAI to CloudFront, so I'd prefer to implement option 2. I am having trouble writing the necessary function however, and I can't find any examples where someone has implemented something similar. Any guidance or help on this issue would be appreciated - either in terms of accomplishing the above or if there is a superior strategy altogether.

Cole
gefragt vor 2 Monaten383 Aufrufe
2 Antworten
1

You can create a Lambda@Edge function that triggers on the "Origin Response" event. In the function, check if the response is a 404 and the request URI starts with /app; if so, modify the response to return your SPA's index.html with a 200 status code. Deploy this Lambda function to your CloudFront distribution, associating it with the cache behavior for your SPA.

As example of your Lambda@Edge:

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const response = event.Records[0].cf.response;

    if (response.status === '404' && request.uri.startsWith('/app')) {
        response.status = '200';
        response.statusDescription = 'OK';
        response.headers['content-type'] = [{ key: 'Content-Type', value: 'text/html' }];
        response.body = 'Your SPA index.html content goes here';
    }

    callback(null, response);
};

Key Sources:

profile picture
EXPERTE
beantwortet vor 2 Monaten
AWS
EXPERTE
überprüft vor 2 Monaten
  • Thank you Osvaldo, this almost gets me there. The only part of this solution that I am stuck on is this line: response.body = 'Your SPA index.html content goes here';

    Does this imply I have to hardcode the content of index.html from the SPA bucket in the lambda function? I am wondering if there is any way to avoid this.

  • Sure, please update the response.body by fetching the object from your S3 bucket. Here's an example to guide you:

    const s3 = new AWS.S3();
    
    if (response.status === '404' && request.uri.startsWith('/app')) {
        const bucketName = 'Your bucket name here';
        const key = 'index.html';
    
        try {
            const object = await s3.getObject({ Bucket: bucketName, Key: key }).promise();
            response.body = object.Body.toString('utf-8'); // Set the response body to the content of index.html
        } catch (error) {
            console.error(`Error fetching index.html from S3: ${error}`);
        }
    }
    
    callback(null, response);
  • Ended up solving it using a different approach, see below.

0

For anyone stumbling upon this, here is how I ended up solving it:

Since my SPA origin should always serve index.html regardless of the path, instead of intercepting the response at error-time, I simply change the URI for all requests to the path to /app/index.html. That way, when CloudFront receives the path /app/page1 or /app/page2, etc., they are all converted to /app/index.html under the hood. I implemented this using a simple Lambda@Edge function, like this:

export const handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    request.uri = "/app/index.html"
    return callback(null, request);
};

I then associated this function to both the /app and /app/* behavior in my CloudFront distribution.

My only lingering question is why a vanilla CloudFront function didn't work. I tried this:

function handler(event) {
    var request = event.request;
    var uri = request.uri;

    if (uri.startsWith("/app")) {
        request.uri = "/app/index.html"
    }
    return request
}

But it would give me 404 errors when I tried to use it...

Cole
beantwortet vor 2 Monaten

Du bist nicht angemeldet. Anmelden um eine Antwort zu veröffentlichen.

Eine gute Antwort beantwortet die Frage klar, gibt konstruktives Feedback und fördert die berufliche Weiterentwicklung des Fragenstellers.

Richtlinien für die Beantwortung von Fragen