こんにちは、CX 事業本部製造ビジネステクノロジー部の若槻です。
Amazon CloudFront の CloudFront Functions を使用すると、エッジロケーションで軽量の JavaScript コードを実行して、リクエストおよびレスポンスを処理することができます。
CloudFront Functions を使用するユースケースとしては、「リクエスト URL への index.html
の追加」があります。例えば Web サイトをホスティングする CloudFront のオリジンを S3 バケットとした場合、バケットへは index.html
を配置することになりますが、ブラウザでのユーザーからのアクセス(ビューワーリクエスト)ではユーザービリティのためにファイル名を指定せずに /
や about
などのディレクトリのみでアクセスさせたい場合があるためです。
そこで今回は、CloudFront Functions でリクエスト URL に index.html を追加する構成を AWS CDK で実装してみました。
実装
CloudFront Function コード
リクエスト URL に index.html を追加する CloudFront Function のコードです。
src/cloudfront-function/add-index-html-to-request-url/index.js
async function handler(event) {
const request = event.request;
const uri = request.uri;
// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension.
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
このコードは Amazon CloudFront のドキュメントにあるサンプルコードをそのまま使用しています。
CDK コード
AWS CDK のスタック定義のコードです。JavaScript runtime 2.0 を使用した CloudFront Function を作成し、 Distribution と関連付けています。
lib/cdk-sample-stack.ts
import {
aws_cloudfront,
aws_s3,
aws_s3_deployment,
aws_cloudfront_origins,
Stack,
RemovalPolicy,
Duration,
CfnOutput,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class CdkSampleStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
// S3 バケットの作成
const websiteBucket = new aws_s3.Bucket(this, 'WebsiteBucket', {
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
// CloudFront から S3 バケットへのアクセスを許可するために、
// Origin Access Identity を作成し、S3 バケットのアクセスポリシーに追加する
const originAccessIdentity = new aws_cloudfront.OriginAccessIdentity(
this,
'OriginAccessIdentity'
);
websiteBucket.grantRead(originAccessIdentity);
// CloudFront Function の作成
const cloudFrontFunction = new aws_cloudfront.Function(
this,
'AddSecurityHeadersToTheResponseFunction',
{
code: aws_cloudfront.FunctionCode.fromFile({
filePath: 'src/cloudfront-function/add-index-html-to-request-url/index.js',
}),
// JavaScript runtime 2.0 を指定
runtime: aws_cloudfront.FunctionRuntime.JS_2_0,
}
);
// CloudFront Destribution を作成
const distribution = new aws_cloudfront.Distribution(this, 'Distribution', {
// defaultRootObject: 'index.html', // ルートディレクトリへのアクセスへも index.html が付与されることを確認するために敢えて無効化
errorResponses: [
{
ttl: Duration.minutes(5),
httpStatus: 403,
responseHttpStatus: 403,
responsePagePath: '/error.html',
},
{
ttl: Duration.minutes(5),
httpStatus: 404,
responseHttpStatus: 404,
responsePagePath: '/error.html',
},
],
defaultBehavior: {
viewerProtocolPolicy:
aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
origin: new aws_cloudfront_origins.S3Origin(websiteBucket, {
originAccessIdentity,
}),
// CloudFront Function と Distribution の関連付け
functionAssociations: [
{
function: cloudFrontFunction,
eventType: aws_cloudfront.FunctionEventType.VIEWER_REQUEST,
},
],
},
});
// CloudFront Distribution のドメイン名を出力
new CfnOutput(this, 'DistributionUrl', {
value: `https://${distribution.distributionDomainName}`,
});
// S3 バケットへのコンテンツのデプロイ、CloudFront Distribution のキャッシュ削除
new aws_s3_deployment.BucketDeployment(this, 'WebsiteDeploy', {
distribution,
destinationBucket: websiteBucket,
distributionPaths: ['/*'],
sources: [
aws_s3_deployment.Source.data(
'/index.html',
'<html><body><h1>Hello, World!</h1></body></html>'
),
aws_s3_deployment.Source.data(
'/about/index.html',
'<html><body><h1>Abuot Me</h1></body></html>'
),
aws_s3_deployment.Source.data(
'/error.html',
'<html><body><h1>Error!!!</h1></body></html>'
),
aws_s3_deployment.Source.data('/favicon.ico', ''),
],
});
}
}
動作確認
Curl コマンドで CloudFront Distribution にアクセスしてみます。
ルートディレクトリへのアクセスで期待通りのレスポンスが返却されることを確認できました。
# 末尾スラッシュ無し、ファイル名無し -> index.html が返却される
$ curl https://d1ll42wrbl4i37.cloudfront.net
<html><body><h1>Hello, World!</h1></body></html>
# 末尾スラッシュ有り、ファイル名無し -> index.html が返却される
$ curl https://d1ll42wrbl4i37.cloudfront.net/
<html><body><h1>Hello, World!</h1></body></html>
# 末尾スファイル名有り -> index.html が返却される
$ curl https://d1ll42wrbl4i37.cloudfront.net/index.html
<html><body><h1>Hello, World!</h1></body></html>
# 不正なファイル名 -> error.html が返却される
$ curl https://d1ll42wrbl4i37.cloudfront.net/index.js
<html><body><h1>Error!!!</h1></body></html>
/about
ディレクトリへのアクセスで期待通りのレスポンスが返却されることを確認できました。
# 末尾スラッシュ無し、ファイル名無し
$ curl https://d1ll42wrbl4i37.cloudfront.net/about
<html><body><h1>Abuot Me</h1></body></html>
# 末尾スラッシュ有り、ファイル名無し
$ curl https://d1ll42wrbl4i37.cloudfront.net/about/
<html><body><h1>Abuot Me</h1></body></html>
# 末尾ファイル名有り
$ curl https://d1ll42wrbl4i37.cloudfront.net/about/index.html
<html><body><h1>Abuot Me</h1></body></html>
次にブラウザからアクセスしてみます。
それぞれのディレクトリへのアクセスで期待通りのレスポンスが返却されることを確認できました。
リクエストが 503 エラーになった
CloudFront Functions を使用した Distribution にアクセスすると 503 エラーが返却されることがありました。
この時は誤ってイベントタイプを VIEWER_RESPONSE
にしていたことが原因でした。
lib/cdk-sample-stack.ts
defaultBehavior: {
viewerProtocolPolicy:
aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
origin: new aws_cloudfront_origins.S3Origin(websiteBucket, {
originAccessIdentity,
}),
// CloudFront Function と Distribution の関連付け
functionAssociations: [
{
function: cloudFrontFunction,
eventType: aws_cloudfront.FunctionEventType.VIEWER_RESPONSE, // VIEWER_REQUEST を指定するのが正
},
],
},
実施したい処理によって適切なイベントタイプを選択するように注意しましょう。
おわりに
CloudFront Functions でリクエスト URL に index.html を追加する構成を AWS CDK で実装してみました。
よくあるユースケースだと思います。だからこそルートディレクトリ以外へのアクセスでも Distribution のプロパティ一つで設定可能になって欲しくはありますが、現状はこちらの方法で対応する必要があります。
参考
以上