この記事は ユニファ Advent Calendar 2022 の 5 日目の記事です。
こんにちは、rightgo09 です。今回は Amazon CloudFront 上で実行できるエッジコンピューティング「Lambda@Edge」で実行するコードを「CloudFront Functions」に置き換えてみます。
昨日: Lambda@Edge と CloudFront Functions の料金比較 - ユニファ開発者ブログ
料金面でのメリットを確認したので、では実際に置き換えを考えてみます。
題材は、動的にサムネイルを生成する AWS 公式のブログで紹介されている Lambda@Edge です。
ビューワリクエストとオリジンレスポンス、それぞれに設置するための 2 つのコードが掲載されています。
ビューワリクエスト用のコードは URL を都合よく変換しているだけなので、今回はこちらを CloudFront Functions に置き換えます。
現在オリジンレスポンスに CloudFront Functions を設置することはできませんが、それを抜きにしても、ここにはサムネイルを生成する重量級の処理があり、軽量向けの CloudFront Functions では耐えられそうにもないため、こちらは素直に Lambda@Edge で稼働させるのが良さそうです。
掲載されているビューワリクエスト用 Lambda@Edge の JavaScript コード
コード内容
このコードの仕事は、以下のようにリクエストの URL を変換することです。
GET /images/image.jpg?d=180x180 HTTP/2 Host: example.com Accept: image/avif,image/webp,*/* ...
↓ Lambda@Edge 実行後
GET /images/200x200/webp/image.jpg HTTP/2 Host: example.com Accept: image/avif,image/webp,*/* ...
- URL パスの変換
- d パラメータがあればそれに応じて URL に含める
- Accept に webp が含まれているかによって URL に含めるパスを変える
コード
ref. https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/
index.js
'use strict'; const querystring = require('querystring'); // defines the allowed dimensions, default dimensions and how much variance from allowed // dimension is allowed. const variables = { allowedDimension: [{ w: 100, h: 100 }, { w: 200, h: 200 }, { w: 300, h: 300 }, { w: 400, h: 400 }], defaultDimension: { w: 200, h: 200 }, variance: 20, webpExtension: 'webp' }; exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers; // parse the querystrings key-value pairs. In our case it would be d=100x100 const params = querystring.parse(request.querystring); // fetch the uri of original image let fwdUri = request.uri; // if there is no dimension attribute, just pass the request if (!params.d) { callback(null, request); return; } // read the dimension parameter value = width x height and split it by 'x' const dimensionMatch = params.d.split("x"); // set the width and height parameters let width = dimensionMatch[0]; let height = dimensionMatch[1]; // parse the prefix, image name and extension from the uri. // In our case /images/image.jpg const match = fwdUri.match(/(.*)\/(.*)\.(.*)/); let prefix = match[1]; let imageName = match[2]; let extension = match[3]; // define variable to be set to true if requested dimension is allowed. let matchFound = false; // calculate the acceptable variance. If image dimension is 105 and is within acceptable // range, then in our case, the dimension would be corrected to 100. let variancePercent = (variables.variance / 100); for (let dimension of variables.allowedDimension) { let minWidth = dimension.w - (dimension.w * variancePercent); let maxWidth = dimension.w + (dimension.w * variancePercent); if (width >= minWidth && width <= maxWidth) { width = dimension.w; height = dimension.h; matchFound = true; break; } } // if no match is found from allowed dimension with variance then set to default //dimensions. if (!matchFound) { width = variables.defaultDimension.w; height = variables.defaultDimension.h; } // read the accept header to determine if webP is supported. let accept = headers['accept'] ? headers['accept'][0].value : ""; let url = []; // build the new uri to be forwarded upstream url.push(prefix); url.push(width + "x" + height); // check support for webp if (accept.includes(variables.webpExtension)) { url.push(variables.webpExtension); } else { url.push(extension); } url.push(imageName + "." + extension); fwdUri = url.join("/"); // final modified url is of format /images/200x200/webp/image.jpg request.uri = fwdUri; callback(null, request); };
CloudFront Functions に置き換えてみる
CloudFront Functions の制約は以下となります。
ref. CloudFront Functions の使い方を学ぶ - ユニファ開発者ブログ
- 1 ms 以下で処理を完了させなければならない
- リソース設定(CPU, メモリ)はできない
- 環境変数は使えない
- 使用可能言語が JavaScript 一択(機能制限もあり)
- HTTP のボディ部分(リクエスト・レスポンスともに)には関与できない
今回は JavaScript から JavaScript への置き換えなので特に大きくは変更はなくできそうです。実際に変更するときに見ていくところは以下です。
- 1 ms 以下で走り切るかを念頭に置く(=重いことは極力しない)
- let, const は慎重に var に置き換える
- 起動する関数名は hanlder() 固定
- event.request 構造を return すると後続に継続する
置き換えてみた CloudFront Functions に登録する JavaScript コード
var allowedDimension = [ { w: 100, h: 100 }, { w: 200, h: 200 }, { w: 300, h: 300 }, { w: 400, h: 400 }, ]; var defaultDimension = { w: 200, h: 200 }; var variancePercent = 20 / 100; var webpExtension = 'webp'; function handler(event) { var request = event.request; var querystring = request.querystring; if (!querystring.d) { return request; } var dimensionMatch = querystring.d.value.split("x"); var width = dimensionMatch[0]; var height = dimensionMatch[1]; var fwdUri = request.uri; var match = fwdUri.match(/(.*)\/(.*)\.(.*)/); var prefix = match[1]; var imageName = match[2]; var extension = match[3]; var matchFound = false; for (var i in allowedDimension) { var dimension = allowedDimension[i]; var minWidth = dimension.w - (dimension.w * variancePercent); var maxWidth = dimension.w + (dimension.w * variancePercent); if (width >= minWidth && width <= maxWidth) { width = dimension.w; height = dimension.h; matchFound = true; break; } } if (!matchFound) { width = defaultDimension.w; height = defaultDimension.h; } var url = []; url.push(prefix); url.push(width + "x" + height); var headers = request.headers; if (headers.accept && headers.accept.value.includes(webpExtension)) { url.push(webpExtension); } else { url.push(extension); } url.push(imageName + "." + extension); fwdUri = url.join("/"); request.uri = fwdUri; return request; }
実際に稼働させてみると、Compute Utilization (コンピューティング使用率)はおおよそ 40
以下で、最大の 100
にはまだまだ余裕があるような結果でした。
Compute Utilization とは
ref. https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/test-function.html#compute-utilization
まとめ
もし Lambda@Edge が稼働していて、それが CloudFront Functions に置き換えが可能なのであれば、メリットとデメリットを考慮しつつ、CloudFront Functions を使っていくのが良いかと思います。
ユニファでは一緒に働いてくれる仲間を募集中です! unifa-e.com