AWS Lambda Ruby を利用して 1時間で作る画像のリサイズ API 

[Ateam Lifestyle x cyma Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ateam-lifestyle-cyma)の13日目は、株式会社エイチームライフスタイルのエンジニア 新卒1年目の@kiitan が担当します。

2018年11月30日 に Amazon Web Services の 年次イベント「AWS re:Invent 2018」でついに Lambda Ruby Runtime が発表されました。そこで早速触ってみました。

## やりたいこと
今回やりたいことは、アクセスされるURLごとに動的に画像をリサイズして配信する機構を Lambda Ruby を利用して構築してみたいと思います。

以下のような構成で構築していきます。

<img width="911" alt="スクリーンショット 2018-12-12 19.29.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/11db235f-265e-c586-bd2d-ac77fcf03c24.png">


## Lambda の作成

AWSコンソールにてLambdaの関数作成で「一から作成」で ruby を選択し S3 の読み取りアクセス権を付与しておきます。

<img width="883" alt="スクリーンショット 2018-12-12 5.17.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/ba97520d-f26c-a528-8641-2feec89200ba.png">

### リサイズ関数の作成
Lambda は AWSコンソール上でも書けるのですが、 標準ライブラリ以外 を利用する場合は 他の言語同様ローカルでビルドする必要があります。
今回は、Gem を利用するので `Gemfile` を以下のように作成します。

``` Gemfile
source 'https://rubygems.org'
gem 'aws-sdk-s3'
gem "mini_magick"
```
`bundle install` 時にローカルにインストールされるように `--path` を指定してインストールします。

``` bash
bundle install --path vendor/bundle
```

次に画像リサイズするための関数 `handler.rb` を作りましょう。

``` handler.rb

require 'aws-sdk-s3'
require 'base64'
require 'mini_magick'


def resize_image(event:, context:)
  s3_client = Aws::S3::Client.new(
                           :region => ENV['REGION'],
                           :access_key_id => ENV['ACCESS_KEY'],
                           :secret_access_key => ENV['SECRET_ACCESS_KEY']
                          )
  # S3 から　画像を取り込み
  image_file = s3_client.get_object(:bucket => ENV['BUCKET'], :key => event['pathParameters']['filename']).body.read
  image = MiniMagick::Image.read(image_file)
  # 今回は写真を扱う前提なので jpeg に変換
  image.format('jpeg') unless image.details['Mime type'] === "image/jpeg"

  # アスペクト比を維持してリサイズ
  width = event['queryStringParameters']['width'].to_i
  height = (width/image.width.to_f * image.height).round.to_i
  image.resize("#{width}x#{height}")

  # レスポンス作成
  {
    statusCode: 200,
    headers: { 'Content-Type': 'image/jpeg' },
    body:  Base64.encode64(image.to_blob),
    isBase64Encoded: true,
  }
end

```

`/filename?width=100` のようなURLでアクセスされることを想定しています。
ファイル名を示すURLパラメータは、 `event['pathParameters']['filename']` から取得できます。 通常のパラメータは `event['queryStringParameters']`に格納されます。

次にレスポンスの部分を解説します。画像を文字列に変換して必要があるので BLOB型 に変換した画像を Base64エンコード しています。
必ず忘れずに `isBase64Encoded: true` を付与してあげてください（ここではまりました...)。

作った関数ファイルと `vendor` 以下を zip 圧縮します。



``` bash
zip -r handler.zip handler.rb vendor
```

zip 圧縮したものを AWSコンソール 上からアップロードしましょう。この時にハンドラを `file_name`.`function_name`に合わせるのを忘れないようにしましょう。

<img width="1289" alt="スクリーンショット 2018-12-12 6.06.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/a653a267-56c8-e635-59b4-55473af7e03b.png">


ちなみに Lambda 環境変数はコンソール上で設定します。
<img width="1020" alt="スクリーンショット 2018-12-12 6.25.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/cf560a42-ccc1-f83c-16ca-c3c63cf7d182.png">


## API Gateway の設定
次に Lambda の関数を呼び出すための　API Gateway を作成します。 
Lambda のページの左側の API Gateway を押せば作成できます。

<img width="1521" alt="スクリーンショット 2018-12-12 6.30.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/c30c43c5-6b59-c23d-3d00-9633e6c9535b.png">

「新規API作成」 で今回は画像を扱うのでバイナリメディアタイプに　`image/jpeg`　を設定して作成します。

<img width="1140" alt="スクリーンショット 2018-12-12 6.29.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/90ea8d2f-e97c-7dab-2b60-7cdf52daf391.png">

ただこのままだと `https://××××××.amazonaws.com/image/image_resize` に設定されてしまっています。今回は　`https://××××××.amazonaws.com/image/filename?width=100`　などでアクセスしたいので API Gateway のページに移動して不要な「image_resize」を削除します。上のアクションから「リソースの削除」をします。
<img width="326" alt="スクリーンショット 2018-12-12 6.44.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/0f45ccd2-b9ee-7178-2959-a4a45990fec6.png">

次に同様にアクションから「リソースを作成」していきます。
「プロキシリソースとして設定」チェックを入れてパラメータ名を「filename」にしてリソースの作成します。これで`https://××××××.amazonaws.com/image/filename`でアクセスすることができます。
<img width="1180" alt="スクリーンショット 2018-12-12 6.47.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/cfae9bb4-1adc-f663-e686-df8c184dbed7.png">

次のページで実行したい Lambda 関数名「resize_image」を記入して保存します。
<img width="1207" alt="スクリーンショット 2018-12-12 6.48.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/d0fce0ec-fbff-39b6-d793-dff87e47f0b6.png">

ここまで来るとテストができるので、テストしてみましょう。
<img width="533" alt="スクリーンショット 2018-12-12 6.51.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/171fb17e-e6b5-60d0-7ee7-fb11973550c7.png">

GETメソッドを選択して「filename」に S3 で公開されている画像のファイル名を指定する。
レスポンス本文にバイナリが返っていることが確認できれば成功です。
<img width="1187" alt="スクリーンショット 2018-12-12 7.01.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/7da9f97d-4b5b-76b5-219a-cedf87c7c71c.png">

また正しく変換されているか確認するために、 curl でアクセスしてリサイズされた画像ファイルが落ちてきていることを確認しておきましょう。アクセスするためのURLは、Lambda のページで確認できます。画像がダウンロードできていれば成功です。

```bash
curl -v --request GET -H "Accept: image/jpeg" -H "Content-Type: image/jpeg" "https://××××××.amazonaws.com/image/sample.jpg?width=100"　>> sample.jpeg
```

しかしここで終わりではありません。 
`https://××××××.amazonaws.com/image/sample.jpg?width=100` にブラウザからアクセスしても画像は表示されません。
API GateWay ではブラウザからのアクセス時に `Accept: image/jpeg` が設定されないのが原因です。
そこで CloudFront を利用します。

## CloudFront の設定
新規作成をしていきます。
「Origin Domain」 に API Gateway の `https://××××××.amazonaws.com` の部分を設定します。「Origin Path」 に `image` を指定することで `https://××××××.cloudfront.net/sample.jpeg`でアクセスすることができるようになります。
カスタムヘッダーに `Accept: image/jpeg` と `Content-Type: image/jpeg`も追加しておきます。

<img width="765" alt="スクリーンショット 2018-12-12 7.37.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/226771e2-2630-bbc7-1992-e61e78a38e7d.png">

あとは`Domain Name`に表示されている `××××××.cloudfront.net/sample.jpeg?width=100` にアクセスして画像が表示されれば成功です。 
<img width="882" alt="スクリーンショット 2018-12-12 7.43.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/316631/825f1480-aac7-64ab-7e27-98fd2e2fd177.png">

初回アクセスは重いものの CloudFront でキャッシュしてあげれば問題も解消されるので画像のリサイズ問題の一つの解決策にはなるのではないでしょうか？

## おわりに

今回初めて Lambda  触りましたがちょっとしたものをサクッと作るのには非常に良いですね。ただハマりどころとして 1MB のまでしかレスポンスが扱えない点は盛大にはまりました。何はともあれ普段から慣れ親しんでいる Ruby で Lambda が書けるのようになったのはいいことですね。いろいろ作りたい欲を掻き立てられますね！！

エイチームグループでは、serverless や マイクロサービス構成 など新しいことにチャレンジしています。エンジニアとしてチャレンジ精神旺盛な一緒に働ける仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/

[Ateam Lifestyle x cyma Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ateam-lifestyle-cyma) の14日目は @k_hajime さんです。よろしくおねがいします！

## 参考
- 構成など設定周りを参考にさせていただきました。
  - [API Gatewayでサーバレスな画像リサイズAPIを作る](https://qiita.com/akitsukada/items/e6d8fe68c49973d1edf6)

- 公式チュートリアル
 - [Announcing Ruby Support for AWS Lambda](https://aws.amazon.com/jp/blogs/compute/announcing-ruby-support-for-aws-lambda/)