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


※ 当ページには【広告/PR】を含む場合があります。
2021/03/11
サブドメイン付きURLで静的なWebサイトをAWS S3/CloudFront(とCloudFront Functions)/Route53で作成する
[AWS x SEO対応] S3 & CloudFrontで静的ホスティングしたウェブサイトのドメイン移管 ~ 301リダイレクトを仕込む
[Lambda@Edge x ウェブサイト運用] AWS S3で静的ホスティングしたウェブサイトをレスポンス400~500番台の時にエラーページへ誘導する




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

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


ということで、今回はAWS Lambda@Edgeを利用して、AWS S3の静的ホスティングサイトの構築に則した、ユーザーからのリクエストURLに自動で
/index.html を処理して返すような手法についてメモしておきます。
なお、前回の以下の記事では今回の内容を説明しなかったのですが、S3による静的ウェブサイトホスティングでは必須の項目では無かったので、別の技術記事としてそれぞれ切り出した次第です。

合同会社タコスキングダム|蛸壺の技術ブログ
サブドメイン付きURLで静的なWebサイトをAWS S3/CloudFront(とCloudFront Functions)/Route53で作成する

AWS S3/Clounflont/Route53を利用したクラウドネイティブな静的ウェブサイトのホスティングを、サブドメイン付きのURLで構築していきます。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集
図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

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



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

前回の記事 のように AWS S3/Clounflont/Route53 を組み合わせてS3バケット内のオブジェクトを配信することで、まるでWebサーバーのように扱えるのがS3静的ウェブサイトの魅力でもあります。
そのテクニックの核となっているのがCloudFrontなのですが、あくまでもコンテンツ配信ネットワーク(CDN)サービスですので、リクエストされたファイル名の指定が無ければ、どれをクライアントに送り返して良いものは自動で判別なんてしてくれるわけはありません。

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


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

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


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

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


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集
図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

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 として即時稼働状態に移行します。
また古くなったバージョンのものは機能を休止して自動でアーカイブされます。
各バージョンの情報は以下の図のように
バージョン タブから確認することが可能です。

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

合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集
図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

まとめ



今回はLambda@Edgeを使ったクライアント側からのリクエストURIを適切に処理して、AWS CloudFront側から特定の名前を付けたファイルを引き出すためのテクニックに関して詳しく解説していきました。
Lambda関数の処理時間も数十ミリ秒程度なので、仮にアクセスが増加しても利用料としてはほぼ無いような金額ですので、コストパフォマンスの意味でもLambda@EdgeとCloudFrontを活用するのは優れたやり方だと言えます。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

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

合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集