S3
CloudFront
lambda
WebP
Lambda@Edge

CloudFrontにおけるWebPの選択的レスポンス

WebPファイルがある場合、対応ブラウザにはそれを返すWebサーバーの設定方法について書いてきました。

S3に画像ファイルを配置するケースも増えていますが、S3自体には上記の代替となるようなURL書き換えの仕組みがありません。

そこでCloudFrontにリクエストやレスポンスを変換するロジックを実装できる、Lambda@Edgeを使う方法を試してみました。


Lambda@Edge用JavaScriptコード

このようなJavaScriptコードを書きました。

Acceptヘッダは簡単に取得できます。WebP画像(image.png.webp)の有無は実際にオリジンに対してHEADリクエストを送信して確認します。

S3に限らず汎用的なWebサーバをオリジンにできます。

実際に設定するには、CloudFrontの対象BehaviorLambda Function AssociationsOrigin Requestとして対してこのLambda関数を割り当てます。Include Bodyは不要です。

Whitelist HeadersAcceptを追加することも必要なはずです。

const BASE_URL = 'https://s3.amazonaws.com/webp-lambda-edge-test';

const TIMEOUT = 3 * 1000;

const https = require('https'),
url = require('url');

exports.handler = async (event, context) => {
const request = event.Records[0].cf.request;

// JpegまたはPNGファイルへのリクエストのみ対応
if (request.uri.match(/\.(jpe?g|png)$/)) {
// Acceptヘッダがimage/webpを含むか
let webpAcceptable = false;
try {
if (request.headers.accept[0].value.match(/image\/webp/i))
webpAcceptable = true;
} catch (ex) {}

if (webpAcceptable) {
// WebP対応ブラウザの場合はWebP版ファイルの有無をHEADメソッドで確認
await new Promise((resolve, reject) => {
const webpUri = request.uri + '.webp';
const req = url.parse(BASE_URL + webpUri);
req.method = 'HEAD';
req.timeout = TIMEOUT;
https.request(req, res => {
// ステータスコード2XXであればWebPが存在するのでリクエストURIを変更
if (res.statusCode >= 200 && res.statusCode < 300)
request.uri = webpUri;
resolve();
}).on('error', reject).end();
});
}
}

return request;
};

Lambda@Edgeは、通常のLambda関数と異なる点がいくつかあるようです。

Lambda 関数の要件と制限

設定について詳しい手順もまた今度、書いてみたいと思います。


参考

Lambda@Edgeで動的に変換する