[Lambda@Edge x ウェブサイト運用] AWS S3上で静的ホスティングしたウェブサイトのアクセスURLに'/index.html'を補完する


2021/03/11

AWS S3に置いた静的なリソースをAWS CloudFrontからコンテンツ配信するタイプのウェブサイトでは、そのクライアントへ送り出されるhtmlファイルの名前まで指定してアクセスしてあげないと、CloudFrontは何をリクエストされたのか分からずに、以下のようなページを返すようになります。

合同会社タコスキングダム|蛸壺の技術ブログ

ということで、今回はAWS Lambda@Edgeを利用して、AWS S3の静的ホスティングサイトの構築に則した、ユーザーからのリクエストURLに自動で
/index.htmlを処理して返すような手法についてメモしておきます。

なお、前回の以下の記事では今回の内容を説明しなかったのですが、S3による静的ウェブサイトホスティングでは必須の項目では無かったので、別の技術記事としてそれぞれ切り出した次第です。


「/index.html」を補った/補わなかった場合の比較

まずは改めて、本件の問題点を確認してみます。

前回の記事のようにAWS S3/Clounflont/Route53を組み合わせてS3バケット内のオブジェクトを配信することで、まるでWebサーバーのように扱えるのがS3静的ウェブサイトの魅力でもあります。

そのテクニックの核となっているのがCloudFrontなのですが、あくまでもコンテンツ配信ネットワーク(CDN)サービスですので、リクエストされたファイル名の指定が無ければ、どれをクライアントに送り返して良いものは自動で判別なんてしてくれるわけはありません。

合同会社タコスキングダム|蛸壺の技術ブログ

結果的に、CloudFront単体では供給すべきファイルの名前までは解決できずに、以下のようにファイル名に指定がない場合にはレスポンスエラーが発生してしまいます。

合同会社タコスキングダム|蛸壺の技術ブログ

もちろん
/index.htmlをサフィックスとして常に省略しないURLでユーザーにアクセスさせるようにしたら問題はないのですが、サイトへのアクセスしやすさを考えると、/index.htmlはやはり冗長です。

/index.htmlがあってもなくても、柔軟にCloudFrontからファイルを供給できるようにするためのひと手間として、今回はLambda@Edgeを使った方法を以下で紹介していきます。


Lambda@Edgeの使い方

以下ではステップごとにLambda@Edgeを仕込む手順を説明していきます。

Lambda関数の作成

まずはAWS Lambdaコンソールダッシュボードから通常のLambdaで関数を新規作成します。

合同会社タコスキングダム|蛸壺の技術ブログ

関数名やランタイムは適当に決めてもらうとして、ここでは
Nodejs 12.xを指定します。

実行ロールの割当では
既存のロールを使用するからもっとも一般的なlambda_basic_executionを追加します。

合同会社タコスキングダム|蛸壺の技術ブログ

なお、
lambda_basic_executionの内容はLambda@Edgeを利用するための必要最低限のエンティティセットですが、カスタムロールを利用する場合には、以下のような2つのエンティティがロールに付与されているか確認しましょう。

合同会社タコスキングダム|蛸壺の技術ブログ

関数が作成されたらその関数の中身を設定していきます。以下のコードをindex.jsに編集します。

            
            exports.handler = async (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const olduri = request.uri;
    const newuri = olduri.replace(/\/$/, '\/index.html').replace(/\/([^\.\/]+)$/, '/$1/index.html');

    console.log("Old URI: " + olduri);
    console.log("New URI: " + newuri);

    request.uri = newuri;
    return callback(null, request);
};
        
ソースコードは[コード]タブの左側のツリービューからindex.jsをクリックして、そのまま先ほどの貼り付けます。

合同会社タコスキングダム|蛸壺の技術ブログ

コードを貼り付けたら、
[Deploy]ボタンを押して変更を適用します。

テスト

実際にEdgeへ本番関数を起動させる前に、この関数のテストをしてみましょう。

テストを作成するにはまず
[テスト]タブから新しいイベントで、テンプレートからclouldfront-respose-generationなどを選択します。

合同会社タコスキングダム|蛸壺の技術ブログ

選択するテンプレートは今回テストするコードに合わせて
Records.cf.request.urlがあるものであればなんでもOKです。

せっかくですので、以下のようにリクエストの内容も
/hogeにアクセスするようにしてみます。

            
            {
  "Records": [
    {
      "cf": {
        "config": {
          "distributionId": "EXAMPLE"
        },
        "request": {
          "uri": "/hoge",
          "method": "GET",
          "clientIp": "1111:2222::3333:4444",
          "headers": {
            "host": [
              {
                "key": "Host",
                "value": "d123.cf.net"
              }
            ]
          }
        }
      }
    }
  ]
}
        
なお、uriプロパティは相対的アクセスURIを取ります。

なので例えば
https://hoge-piyo.com/hogeへクライアントからアクセスがあった場合には、uriプロパティは/hogeだけが抽出されます。

このテストを適当な名前を付けて
[変更を保存]ボタンで保存し、[呼び出し]ボタンでうまくレスポンスが返ってきていること確認します。

合同会社タコスキングダム|蛸壺の技術ブログ

期待通りに
/hoge/index.htmlというURIのリクエストになっていることが分かります。

ログ出力も観てみると、コード内のconsole.logから変換前後のURI出力が確認できます。

合同会社タコスキングダム|蛸壺の技術ブログ

なお、一度作成したテストはコードタブ側の
[Test]ボタンで以降は便利に呼び出して使えるので、テストタブと画面を切り替えることなく使えるようになります。

合同会社タコスキングダム|蛸壺の技術ブログ

関数のEdgeへのバージョニング

ではlambda@Edgeにデプロイを行います。

コンソール右上の
[アクション] > [Lambda@Edgeへのデプロイ]を押してみます。

合同会社タコスキングダム|蛸壺の技術ブログ

初回に作成したLambda関数のデプロイではトリガーとなるCloudFrontディストリビューションの設定が聞かれてきます。ウェブサイトのコンテンツを配信しているCloudFrontのディストリビューションを結びつけましょう。

合同会社タコスキングダム|蛸壺の技術ブログ

ターゲットしたいCloudFrontのディストリビューションを選択し、他の設定はデフォルト値のままにしておいて、
Lambda@Edgeへのデプロイを確認のチェックボックスを入れ、良ければ[デプロイ]ボタンを押します。

その後CloudFrontのダッシュボードで確認するとターゲットのディストリビューションの項目に変更が
In Progressになっているので、設定が反映されるまで暫く待ちます。

合同会社タコスキングダム|蛸壺の技術ブログ

Lambda@Edgeによって、リクエストの内容を柔軟に変化させることで、リクエストしたURIに
/index.htmlが無ければ自動的に補ってくれるようになりました。

Lambda@Edgeのバージョンアップについて

おまけで、もしLambda関数のindex.jsのコードの中身を修正した内容で、現在可動しているバージョンを上書きする場合にも触れておきます。

既にLambda関数の何らかのバージョンがデプロイされて稼働状態でも、再び
[アクション] > [Lambda@Edgeへのデプロイ]してみると、この関数で既存のCloudFrontトリガーを使用というオプションが増えています。

合同会社タコスキングダム|蛸壺の技術ブログ

これで以前のバージョンでトリガーとして利用していたものが引き継ぐことができるようになります。

この修正した関数はバージョンで管理されており、新しい関数をデプロイすると、バージョンが上書きされ、最新のバージョンが
$LATESTとして即時稼働状態に移行します。

また古くなったバージョンのものは機能を休止して自動でアーカイブされます。

各バージョンの情報は以下の図のように
バージョンタブから確認することが可能です。

合同会社タコスキングダム|蛸壺の技術ブログ


まとめ

今回はLambda@Edgeを使ったクライアント側からのリクエストURIを適切に処理して、AWS CloudFront側から特定の名前を付けたファイルを引き出すためのテクニックに関して詳しく解説していきました。

Lambda関数の処理時間も数十ミリ秒程度なので、仮にアクセスが増加しても利用料としてはほぼ無いような金額ですので、コストパフォマンスの意味でもLambda@EdgeとCloudFrontを活用するのは優れたやり方だと言えます。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。