はじめに
CloudFrontでLambda@Edgeを使ってHTTPリダイレクトする方法についてまとめてみました。
他にも方法はあると思いますのであくまで参考にしていただければと思います。
前提条件
- CloudFrontが用意されていること。
- Lambdaの初期作成が終わっていること。(プログラムは空でOK)
- 動作確認言語:Node.js 12.x
ここでは行わないこと
- CloudFrontについての説明。
- Lambdaの初期作成。
- Lambda@Edgeについての説明。
#事前準備
##IAMロールの修正
Lambda@Edgeを利用するためにはIAMロールの信頼関係を以下のように修正する必要があります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
Lambda@Edgeとして利用するために信頼されたエンティティにedgelambda.amazonaws.com
を追加しています。
また、CloudWatchログへのアクセス権限も修正します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:*"
]
}
]
}
なぜなら、Lambda@Edgeのログは**「関数が実行される場所に最も近い AWS リージョンで CloudWatch Logs ログストリームを作成する」**からです。
また、その際のロググループ名は/aws/lambda/us-east-1.function-name
になります。
Lambda関数
① 全てのアクセスを特定URLへリダイレクトさせる。
ここでは、CFtへのアクセス全てを http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html へリダイレクトさせる例です。
サービスページのドメイン変更などで利用できます。
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 302 status code and Location header.
*/
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: 'http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html',
}],
},
};
callback(null, response);
};
value
の部分でリダイレクト先を指定しています。この値を変更することでリダイレクト先を変更できます。
実際に挙動について確認してみます。
まず、Lambda@Edgeデプロイ前のステータスコードです。
% curl -I https://d20glirsrxu021.cloudfront.net/
HTTP/2 200
content-type: text/html
content-length: 4
...
デプロイ後に改めてアクセスしてみます。
% curl -I https://d20glirsrxu021.cloudfront.net/
HTTP/2 302
content-length: 0
location: http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html
server: CloudFront
...
ステータスコードが302になり、locationもLambda@Edgeで指定した値になっているのが確認できます。
ブラウザでアクセスしてみると実際の挙動も確認できます。
② 特定パス配下のアクセスを特定URLへリダイレクトさせる。
次は特定パスを含むURLに対してリダイレクト設定を行います。
今回は https://d20glirsrxu021.cloudfront.net/test/ 配下のアクセスに対してリダイレクトを行います。
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 302 status code and Location header.
*/
var request = event.Records[0].cf.request;
// Extract the URI from the request
var uri = request.uri; //追加①:URIの取得
// Match any '/' that occurs at the end of a URI. Replace it with a default index
var pattern = '^\/test\/'; //追加②:検索文字列の設定
if(uri.match(pattern)){ //追加③:文字列のマッチング
request = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: 'http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html',
}],
},
};
} //追加④:追加③の閉じカッコ
callback(null, request);
};
ここでは、matchメソッドを利用することで文字列チェックを行なっています。
それではまた、デプロイ前と後で確認してみます。
% curl -I https://d20glirsrxu021.cloudfront.net/test/index.html
HTTP/2 200
content-type: text/html
content-length: 4
・・・
% curl -I https://d20glirsrxu021.cloudfront.net/test/index.html
HTTP/2 302
content-length: 0
location: http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html
server: CloudFront
・・・
ちゃんとリダイレクトされていることが確認できました。
③ 特定パス配下のアクセスを特定URLへリダイレクトさせる。(複数パスの場合)
特定パス配下が複数ある場合はpatternの部分に|
を使って記載します。
var pattern = '^\/test\/';
var pattern = '^\/test\/|^\/test2\/';
④ 特定パス配下へのアクセス以外を特定URLへリダイレクトする。
通常は特定パス配下に対してが多いと思いますが、逆のパターン(特定パス配下以外)の場合、matchメソッドの先頭に!
を記載します。
if(uri.match(pattern)){
if(!uri.match(pattern)){
④ 特定パス配下へのアクセスを同じURLの異なるパスへリダイレクトする。
②のソースをベースに修正します。
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 302 status code and Location header.
*/
var request = event.Records[0].cf.request;
// Extract the URI from the request
var uri = request.uri;
var host = event.Records[0].cf.request.headers["host"][0]; //追加①:ヘッダーのHost情報を取得
var redirect = 'https://' + host.value + '/test/index.html'; //追加②:リダイレクト先を整形
// Match any '/' that occurs at the end of a URI. Replace it with a default index
var pattern = '\/test2\/'; //変更①:test2配下に適用するための設定
if(uri.match(pattern)){
request = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: redirect, //変更②:リダイレクト先の設定
}],
},
};
}
callback(null, request);
};
リクエストイベントからHost情報を取得することで、クライアントのリクエスト先へ柔軟に対応しています。
デプロイ
アクションのプルダウンから Lambda@Edgeへデプロイ をクリックします。
Lambda@Edgeへのデプロイ が表示されるので、新しいCloudFrontトリガーの設定 を選択します。
ディストリビューションには関連付けたい CloudFront の ディストリビューションID を入力します。
また、 CloudFrontイベント は ビューアーリクエスト を選択します。
・ビューワーリクエスト
CloudFront がビューワーからリクエストを受け取ると、リクエストされたオブジェクトが CloudFront キャッシュにあるかどうかを確認する前に関数が実行されます。
あとは Lambda@Edgeへのデプロイを確認 にチェックを入れて デプロイ をクリックしたら完了です。
おわりに
S3がOriginの場合で有効なのはもちろんですが、EC2の場合もCloudFrontで処理できれば負荷の軽減につながるので良いのではと思います。
参考URL
Lambda@Edge 関数の例(レスポンスの生成 - 例)
Lambda@Edge イベント構造(リクエストイベント)
Lambda 関数の CloudWatch メトリクスと CloudWatch Logs([CloudWatch Logs])
Lambda 関数をトリガーできる CloudFront イベント