こちらの記事を参考にさせてもらいました。
AWS lambdaを利用してS3の画像アップロードをトリガーにサムネイル画像を自動生成する
#流れ
- S3のバケットを作る(オリジナル用とサムネイル用の二つ)
- サムネイル作成用のlambdaの関数をつくる
- 関数の動作テスト
- トリガーの有効化
- 実際に動かす
##1. S3のバケットを作る(オリジナル用とサムネイル用の二つ)
オリジナルの画像を保存するバケットとサムネイルを保存するバケットを用意します。
※一つのバケットにまとめるとプレフィックスを適切に設定しないと無限ループが起きるようです。
今回、サムネイル用のバケットは[オリジナルのバケット名]-resized
という名前の前提で話を進めます。
そのように二つのバケットを作成してください。
右上の「関数の作成」から
「設計図」を選択
「s3-get-object」を選択
「名前」と「ロール名」はなんでもOKです。
「ロール」は「テンプレートから新しいロールを作成」を選択
「ポリシーテンプレート」は「S3オブジェクトの読み取り専用アクセス権限」
「バケット」には先ほど作成したオリジナルのバケットを選択。
「イベントタイプ」にはオブジェクトの作成(すべて)を選択。
(ここをPUTにすると、プログラムから画像をアップロードした時に関数が動きませんでした。ここで小一時間詰まった...)
今は一旦チェックは入れない。
ここは編集不可なのでそのまま
「関数の作成」
すると関数ができる。
このあと関数を書いていくのですが、本家のチュートリアル通りにやっても[Stream yields empty buffer]
というエラーが出てうまくいきません。
本家チュートリアル
同じエラーに遭遇した方のこちらの記事を参考にしました。ありがとうございます。
Lambdaで画像のサムネイルを作る [Stream yields empty buffer対策済]
この後、サムネイル作成用の関数をローカルで作って、それをlambdaにアップロードします。
###ローカルにて
$ mkdir hoge
$ cd hoge
$ npm install gm
適当にディレクトリを作って、gm
をインストールします。(GraphicsMagickというライブラリ)
そしてindex.jsを作って以下のように書きます。先ほどの参考サイトとほぼ同じです。
上から7行目のkeyを作るところの処理が公式と違ったので、公式に合わせています。(decodeURIComponent
を使っている。)
また、正方形のサムネイルを作りたかったのでcropの引数を200x200に変えています。
var AWS = require('aws-sdk');
var gm = require('gm').subClass({ imageMagick: true });
var util = require('util');
var s3 = new AWS.S3();
exports.handler = function(event, context, callback) {
var bucket = event.Records[0].s3.bucket.name; // 変換元画像のバケット名
var key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' ')); // 変換元画像のキー名
var thumbnailBucket = bucket + "-resized"; // 変換後画像のバケット名(ここでは末尾にresizedを付け足すことにしてある)
var thumbnailKey = key; // 変換後画像のキー名(ここでは変換前と同じにしてある)
// 拡張子を抽出
var typeMatch = key.match(/\.([^.]*)$/);
if (!typeMatch) {
callback("Could not determine the image type.");
return;
}
// 拡張子がjpg, jpeg, pngのものだけ許可する
var imageType = typeMatch[1];
if (imageType != "jpg" && imageType != "jpeg" && imageType != "png") {
callback('Unsupported image type: ${imageType}');
return;
}
// S3の画像にアクセス
s3.getObject({Bucket: bucket, Key: key}, function(err, data){
if (err) { context.done('error getting object', err); }
// 画像処理 (http://qiita.com/komakomako/items/a33ff4e610e378d986ff と同じ)
gm(data.Body)
.resize(null, '350')
.borderColor('black')
.border('245', '245')
.gravity('Center')
.crop('200', '200')
.stream(function(err,stdout,stderr){ // ストリームで出力する
if(err){
console.log("gm process error");
console.log(err,stdout,stderr);
context.fail(err);
}
var chunks = []; // ストリームのチャンクを溜め込むための配列
stdout.on('data',function(chunk){
console.log('pushed');
chunks.push(chunk); // チャンクを溜め込む
});
stdout.on('end',function(){
console.log('end');
var buffer = Buffer.concat(chunks); // 最後まで溜め込んだら結合してバッファに書き出す
var params = {
Bucket: thumbnailBucket,
Key: thumbnailKey,
ContentType: imageType,
Body: buffer
};
s3.putObject(params, function(err, data) { // S3に書き出す
if(err){
console.log("gm upload error");
context.fail(err);
}
context.succeed({
"error":false
});
});
});
stderr.on('data',function(data){
console.log('stderr data: ' + data);
});
});
});
};
index.js
とnode_module
を圧縮。
lambdaのコンソールにて、今作ったzipをアップロードする。
ここまでで関数の作成が完了。
次はテストイベントの設定。
この画像だともう設定されてしまっていますが、画面上のほうにテストを設定するところがある。
このように設定
key
・・・ここに設定した名前の画像ファイルをこのあとオリジナル用のバケットに入れます。
arn
・・・オリジナル用のバケット名に変更する
name
・・・同じくオリジナル用のバケット名に変更する
awsRegion
・・・S3のバケットが属するリージョンと同じリージョン。ほとんどの人がap-northeast-1
かと。
オリジナルのバケットにHappyFace.jpgというファイルをあげておく。
画面右上のテストボタンを押すと先ほど書いた関数が動きます。
"errorMessage": "2018-04-18T10:13:51.573Z 2ed4597f-42f1-11e8-a4de-a37fff25ed59 Task timed out after 3.00 seconds"
私の場合エラーになりました。タイムアウトです。
タイムアウトの時間はデフォルトで3秒なので、ここを15秒にしてみました。
それでもエラーになる場合はそのすぐ上にあるメモリの割当量を増やしてみてください。
無事、処理が成功しました。
サムネイル用のバケットをみてみるときちんと画像が入ってます。
テストは成功しました。
ただ今の状態だと画像をアップロードしたときにこの関数は走ってくれません。なぜならこの関数を作った際、「トリガーを有効にする」のチェックを外したからです。
ですので次にトリガーの有効化をします。
4. トリガーの有効化
トリガーの有効化の場所を発見するのに地味に時間がかかりました...
lambdaコンソールの上の方の「S3」を選択し、スライダーを押して有効化します。そして忘れずに保存。
5.実際に動かす
S3に何か画像をアップロードしてみてください。
サムネイル用のバケットにサムネイルがきちんと入りました!!
前回の記事のサムネイル作成のところはクリアできました。
S3に直接画像をアップロードする(Ruby on Rails)