カテゴリー
[Lambda@Edge x ウェブサイト運用] AWS S3上で静的ホスティングしたウェブサイトのアクセスURLに'/index.html'を補完する
※ 当ページには【広告/PR】を含む場合があります。
2021/03/11
AWS S3に置いた静的なリソースをAWS CloudFrontからコンテンツ配信するタイプのウェブサイトでは、そのクライアントへ送り出されるhtmlファイルの名前まで指定してアクセスしてあげないと、CloudFrontは何をリクエストされたのか分からずに、以下のようなページを返すようになります。

ということで、今回はAWS Lambda@Edgeを利用して、AWS S3の静的ホスティングサイトの構築に則した、ユーザーからのリクエストURLに自動で
/index.html
なお、前回の以下の記事では今回の内容を説明しなかったのですが、S3による静的ウェブサイトホスティングでは必須の項目では無かったので、別の技術記事としてそれぞれ切り出した次第です。
「/index.html」を補った/補わなかった場合の比較
まずは改めて、本件の問題点を確認してみます。
AWS S3/Clounflont/Route53
そのテクニックの核となっているのがCloudFrontなのですが、あくまでもコンテンツ配信ネットワーク(CDN)サービスですので、リクエストされたファイル名の指定が無ければ、どれをクライアントに送り返して良いものは自動で判別なんてしてくれるわけはありません。

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

もちろん
/index.html
/index.html
/index.html
Lambda@Edgeの使い方
以下ではステップごとにLambda@Edgeを仕込む手順を説明していきます。
Lambda関数の作成
まずは

関数名やランタイムは適当に決めてもらうとして、ここでは
Nodejs 12.x
実行ロールの割当では
既存のロールを使用する
lambda_basic_execution

なお、
lambda_basic_execution

関数が作成されたらその関数の中身を設定していきます。 以下のコードを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
せっかくですので、以下のようにリクエストの内容も
/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
/hoge
このテストを適当な名前を付けて
[変更を保存]
[呼び出し]

期待通りに
/hoge/index.html
ログ出力も観てみると、コード内の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を活用するのは優れたやり方だと言えます。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー