Mirai Translate TECH BLOG

株式会社みらい翻訳のテックブログ

複数アプリが同居するCloudFrontで404ページを表示する

この記事は、みらい翻訳Advent Calendar 202320日目です。

 

こんにちは。プラットフォーム開発部エンジニアのeinです。

今回はAWS CloudFrontでホスティングしているサービスで、404ページ*1を表示する手段にちょっとだけ悩んだので、その対処法の紹介です。

 

前提

私が担当しているシステムでは、複数のフロントエンドアプリケーションおよびAPIを同一のドメインで公開するために、単一のCloudFrontでホスティングしています。

各フロントエンドはReactをSSGしたものでS3オリジンとして公開しています。単一のS3バケットの中にディレクトリを分けて複数のアプリケーションを格納しています。

APIサーバはEKSコンテナだったりLambdaだったりします。

CloudFrontのルーティング設定で、/api/hoge だったらEKSのこのサービスに、 /app_alpha だったらS3のこのディレクトリに、といった振り分けをしています。

ごく簡単にお絵描きするとこんな感じ

課題

さて、この構成のときに、エンドユーザから無効なパスを叩かれた際に、以下の挙動をさせる方法を考えます。

  1. APIが叩かれた際には、パスが有効であれ無効であれ、APIに処理を任せる
  2. フロントエンドの無効なパスを叩かれたときには404ページを表示する

「フロントエンドの無効なパスを叩かれたとき」とは、言い換えるとエンドユーザがS3に存在しないファイルパスをリクエストしたときです。S3はs3:ListBucket権限がないコンポーネント(今回はCloudFront)から存在しないパスを要求されると、403 Forbiddenを返却します。

つまり、今回の課題は「CloudFrontに届いた、S3オリジンへのリクエストについてのみ、ステータスコード403のときに代わりに404ページを返却したい」になります。

うまくいかない手段

CloudFrontのカスタムエラーレスポンス

CloudFrontにはカスタムエラーレスポンスという機能があり、特定のステータスコードのときに設定したエラーページを返却してくれます。「CloudFront 404ページ」などとググると先ずこの手段がヒットするでしょう。

ですが、カスタムエラーレスポンスは適用範囲を特定のオリジンやビヘイビアに限ることはできません。従って、今回のケースではAPIが返却する403エラーまで書き換えてしまうので、採用できませんでした。

解決策

Lambda@Edge オリジンレスポンス

そこで、今回はLambda@Edgeでレスポンスボディを書き換えてしまう手段を取りました。Lambad@Edgeはビヘイビア単位で設定できるので、S3に関するビヘイビアにのみ適用できます。

Lambda@Edgeの発火タイミングは4種類ありますが、オリジンレスポンスにします。ビューワーレスポンスでは、レスポンスボディを書き換えることができないので、オリジンレスポンス一択になります。

以下に、TypeScriptで記載したLambda@Edgeのソースコード例を示します。

非常にカンタンなコードです。ポイントは2つです。

  1. index.htmlはLambdaのソースに同梱しています
  2. Content-Type ヘッダも text/html に書き換えています
import fs from 'fs';
import { CloudFrontResponseEvent, CloudFrontResponseResult, Handler } from 'aws-lambda';

export const handler: Handler<CloudFrontResponseEvent, CloudFrontResponseResult> = (event, _, callback) => {
  const response = event.Records[0].cf.response;

  if (response.status !== '403') {
    callback(null, response);
    return;
  }

  const html = fs.readFileSync('./index.html', 'utf-8');

  callback(null, {
    ...response,
    status: '404',
    body: html,
    headers: {
      ...response.headers,
      'content-type': [
        {
          key: 'Content-Type',
          value: 'text/html; charset=UTF-8',
        },
      ],
    },
  });
};

 

これをデプロイすれば完了です!

一方、404ページのデザインをリッチにしたいときや開発運用を便利にしたいときには、HTMLをLambdaに同梱するのを辞めてS3からホスティングしたくなるかもしれません。するとまた少し違った工夫が必要になると思います。

おわりに

今回は特定のケースにおいて404ページを表示するのにLambda@Edgeが便利、というお話でした。

そもそもAPIゲートウェイとフロントエンドのホスティングサービスを分離していれば、他の手段も様々取れると思います。今回の対処が必要な場合は限られているかもしれませんが、なにか参考になれば幸いです。

We are hiring!

みらい翻訳では、私たちと一緒にプロダクト開発を進めてくれるエンジニアを募集しています!ご興味のある方は、ぜひ下記リンクよりご応募・お問い合わせをお待ちしております。

 

miraitranslate.com

 

*1:よくある「ご指定のページは見つかりません。」などと表示するページのことです