tag:crieit.net,2005:https://crieit.net/tags/Lambda@Edge/feed
「Lambda@Edge」の記事 - Crieit
Crieitでタグ「Lambda@Edge」に投稿された最近の記事
2018-12-26T17:34:10+09:00
https://crieit.net/tags/Lambda@Edge/feed
tag:crieit.net,2005:PublicArticle/14695
2018-12-26T14:22:38+09:00
2018-12-26T17:34:10+09:00
https://crieit.net/posts/Lambda-Edge
Lambda@Edgeで画像をリアルタイムにリサイズできる仕組みを作った
<p>今作成中のサービスで画像をリアルタイム変換できる仕組みを AWS の Lambda@Edge を用いて実現してみたので紹介します。</p>
<p>参考:<a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/">https://aws.amazon.com/jp/blogs/news/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/</a></p>
<h2 id="Lambda@Edge とは"><a href="#Lambda%40Edge+%E3%81%A8%E3%81%AF">Lambda@Edge とは</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-edge.html">Lambda@Edge</a> とは、一言で言うと CloudFront (CDN) の HTTP リクエスト・レスポンスを操作できる Lambda 関数です。</p>
<p>下記のタイミングで CloudFront リクエスト・レスポンスを変更することができます。<br />
* CloudFront がビューワーからリクエストを受信した後 (ビューワーリクエスト)<br />
* CloudFront がリクエストをオリジンサーバーに転送する前 (オリジンリクエスト)<br />
* CloudFront がオリジンからレスポンスを受信した後 (オリジンレスポンス)<br />
* CloudFront がビューワーにレスポンスを転送する前 (ビューワーレスポンス)</p>
<p><a href="https://crieit.now.sh/upload_images/47b4cad07dd3d07a60ea9ee3d38d59665c23022c30410.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/47b4cad07dd3d07a60ea9ee3d38d59665c23022c30410.png?mw=700" alt="a.png" /></a></p>
<h2 id="作った仕組み"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E4%BB%95%E7%B5%84%E3%81%BF">作った仕組み</a></h2>
<p>CloudFront のオリジンサーバーに S3 を指定し、S3 からの画像レスポンスを Lambda@Edge で変換する仕組みを構築しました。</p>
<p>URLのパラメータを変えることでリサイズされた画像を取得できます。<br />
例えば下記のように幅と高さを指定してリクエストするとそれぞれのサイズの画像が返却されるようにしています。</p>
<p>・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=100x100 → 100x100の画像<br />
・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=300x300 → 300x300の画像<br />
・https://cdn.example.com/images/user/1234/avatar/001.jpg?d=1000x1000 → 許可しないサイズはエラー</p>
<h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2>
<p>いろいろ説明をはしょってコードだけ貼っときます。。</p>
<h3 id="ビューワーリクエスト"><a href="#%E3%83%93%E3%83%A5%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">ビューワーリクエスト</a></h3>
<pre><code>'use strict';
const querystring = require('querystring');
const allowedDimensions = [ {w: 100, h: 100}, {w: 300, h: 300} ];
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
// URIが特定の画像パスに一致するかチェック(特定の画像以外はクエリストリングを削除してリクエスト)
const match = request.uri.match(/\/images\/user\/\d+\/avatar\/\d+\.jpg/);
if (match == null) {
request.querystring = '';
callback(null, request);
return;
}
// クエリストリングを key-value のペアにパース
const params = querystring.parse(request.querystring);
// dimensionパラメータがない場合、クエリストリングを削除してリクエスト
if (!params.d) {
request.querystring = '';
callback(null, request);
return;
}
// dimensionパラメータを'x'で分割
const dimensionMatch = params.d.split('x');
const width = dimensionMatch[0];
const height = dimensionMatch[1];
// dimensionパラメータが、許可リストに一致するかチェック
let matchFound = false;
for (let dimension of allowedDimensions) {
if (width == dimension.w && height == dimension.h) {
matchFound = true;
break;
}
}
// 許可リストにない場合、クエリストリングを削除してリクエスト
if (!matchFound) {
request.querystring = '';
callback(null, request);
return;
}
// クエリストリングにd={width}x{height}を設定してリクエスト
request.querystring = querystring.stringify({ d: width + 'x' + height });
callback(null, request);
};
</code></pre>
<h3 id="オリジンレスポンス"><a href="#%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%B3%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9">オリジンレスポンス</a></h3>
<pre><code>'use strict';
const querystring = require('querystring');
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
const Sharp = require('sharp');
const BUCKET = 'XXXXXXXX';
exports.handler = (event, context, callback) => {
let response = event.Records[0].cf.response;
if (response.status == 200) {
const request = event.Records[0].cf.request;
// クエリストリングを key-value のペアにパース
const params = querystring.parse(request.querystring);
// dimensionパラメータがない場合、そのままレスポンスを返す
if (!params.d) {
callback(null, response);
return;
}
// S3キー用にuriの最初のスラッシュを除いた文字列を取得
const key = request.uri.substring(1);
// dimensionパラメータを'x'で分割
const dimensionMatch = params.d.split("x");
const width = parseInt(dimensionMatch[0], 10);
const height = parseInt(dimensionMatch[1], 10);
// S3から画像を読み込み
S3.getObject({ Bucket: BUCKET, Key: key }).promise()
// リサイズ
.then(data => Sharp(data.Body)
.resize(width, height)
.toBuffer()
)
.then(buffer => {
// レスポンスを生成
response.status = 200;
response.body = buffer.toString('base64');
response.bodyEncoding = 'base64';
response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/jpeg' }];
callback(null, response);
})
.catch( err => {
callback(null, response);
});
} else {
// 200以外のステータスコードの場合、そのままレスポンスを返す
callback(null, response);
}
};
</code></pre>
<h2 id="なぜ作ったか"><a href="#%E3%81%AA%E3%81%9C%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B">なぜ作ったか</a></h2>
<p>普通なら「非同期ジョブでリサイズ処理してS3に複数サイズ保存」とかで事足りるのではと思います。</p>
<p>今回は、<br />
* アップロード時間を短くしたい(アップロード時はバリデートのみで変換処理しない)<br />
* 画像をアップロードした直後の画面でリサイズされた画像を表示したい<br />
* リサイズ画像をキャッシュしたい<br />
* 画像サイズあとで変えたいかも</p>
<p>といった理由で、CDNを通してリアルタイム変換という選択肢を選びました。</p>
<h2 id="感想"><a href="#%E6%84%9F%E6%83%B3">感想</a></h2>
<p>AWS Lambda 自体初めて触りましたが、サーバレスは便利だな〜と感じました。<br />
ただ、Lambdaの開発やテストやデプロイをどうやるのが正解なのかよくわからず、追加開発があるような比較的規模の大きいプロジェクトだとLambdaは大変そう(?)と思いました。</p>
<p>今回は画像リサイズだけでしたが、透かしを入れたり合成したり、画像以外にも動的にキャッシュできるので、いろいろ使い所ありそうです。</p>
<p>早くサービスリリースできるよう頑張ります。。</p>
オクムラダイキ