AWS CDK で CloudFront Functions をやってみた
AWS, TypeScriptCloudFront Functions が AWS CDK で L2 対応したので触った。
サンプルコードはこちら
対象バージョン
CDK 1.107.0 以降が必須。
CloudFront Functions とは
CloudFront エッジロケーションで動かす Function。略して CF2。
Lambda@Edge のリージョナルエッジロケーションより、手前のレイヤーだとかなんとか。
Lambda@Edge は呼び出し回数と実行時間で課金されるが、CloudFront Functions は呼び出し回数のみで、無料枠もある。
(公式よりクラメソさんの記事の方がわかりやすいので、そちらをご覧ください)
エッジで爆速コード実行!CloudFront Functionsがリリースされました! | DevelopersIO
エッジでJavaScriptを実行するCloudFront Functionsのユースケースまとめ | DevelopersIO
制約
制約はかなり厳しい。
- ES5(ECMAScript 5.1)
- 実行時間 1ms 未満
- 最大メモリ 2MB
- 最大サイズ 10 KB
- トリガーイベントは Viewer request と Viewer response のみ
- ネットワーク、ファイル、request body へのアクセス不可
あまり凝ったことはできない。
Lambda のコードを書く
今回は、リダイレクト処理とパス解決を雑に ESBuild でビルドする Lambda@Edge があるので、それを CloudFront Functions でも動かしてみた。
ES5 という制約がきついけど、これのために Babel や Polyfill を入れたくないし、大したことしてないので手動で ES5 互換に置き換えようとした。
結局怒られたのは const
だけだったので var
に書き換えるだけで済んだ。
あとは、バンドルやめて index に突っ込んだりしたので、テスタビリティはどこかに消えてしまった。
Req, Res の型は仮。
src/lambda/index.ts
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-var */
import { CloudFrontRequest } from 'aws-lambda'
type CloudFrontFunctionRequest = CloudFrontRequest & {
request: CloudFrontRequest & {
headers: {
host: {
value: string
}
}
}
}
type CloudFrontFunctionResult =
| {
statusCode: number
statusDescription?: string
headers: {
location: {
value: string
}
}
}
| CloudFrontRequest
function handler(event: CloudFrontFunctionRequest): CloudFrontFunctionResult {
var request = event.request
var uri = request.uri
var host = request.headers.host.value
var newUrl = `https://${host}/bar/`
// redirect
if (uri.match(/^\/foo\/?/)) {
console.log(`redirect: ${uri} to /bar/`)
return {
statusCode: 301,
statusDescription: 'Found',
headers: {
location: {
value: newUrl,
},
},
}
}
// rewrite
var normalizedUri: string
var INDEX_PATH = 'index.html' as const
var extension = /(?:\.([^.]+))?$/.exec(uri)
if (extension && extension[1]) {
normalizedUri = uri
} else {
// endsWith はそのままいけた
normalizedUri = uri.endsWith('/')
? `${uri}${INDEX_PATH}`
: `${uri}/${INDEX_PATH}`
}
return { ...request, uri: normalizedUri }
}
あとはビルドするだけ。
ESBuild 自体は ES5 へのトランスパイルをサポートしないが、--target=es5
を指定して、互換性のあるコードを生成できる。
shell
esbuild src/lambda/index.ts --minify --outfile=src/lambda/dist/index.js --target=es5
CloudFront
CloudFront で aws-cloudfront
の Function
を追加し、 functionAssociations
で指定する。
eventType は FunctionEventType
から使用する。さっき書いたように、イベントタイプは Viewer request と Viewer response のみ。
今回は Viewer request でやる。
src/stacks/cloudfront-stack.ts
import {
Function as CloudFrontFunction,
FunctionCode,
FunctionEventType,
LambdaEdgeEventType,
} from '@aws-cdk/aws-cloudfront'
// これ
const cloudFrontFunction = new CloudFrontFunction(this, 'Redirect', {
functionName: 'redirectFunction',
// 後述
code: FunctionCode.fromInline(
fs.readFileSync(
`${path.resolve(__dirname)}/lambda/dist/index.js`,
'utf8',
),
),
})
// 〜〜〜〜〜〜省略〜〜〜〜〜〜
this.distribution = new CloudFrontWebDistribution(this, `Distribution`, {
originConfigs: [
{
s3OriginSource: {
s3BucketSource: this.bucket,
originAccessIdentity,
},
behaviors: [
{
compress: true,
isDefaultBehavior: true,
// ここ
functionAssociations: [
{
eventType: FunctionEventType.VIEWER_REQUEST,
function: cloudFrontFunction,
},
],
},
],
},
],
// 〜〜〜〜〜〜省略〜〜〜〜〜〜
})
注意点は code
の記述。現在はインラインしか対応していないので、ファイルの中身を文字列でぶち込む。
今上がっている、fromFile を追加する PR とやってることは同じ。
src/stacks/cloudfront-stack.ts
code: FunctionCode.fromInline(
fs.readFileSync(
`${path.resolve(__dirname)}/lambda/dist/index.js`,
'utf8',
),
),
デプロイすると、コードが CloudFront Functions の develop と live ステージに反映されるはず。
コンソール上でテストして、正しく動作していることを確認した。