Lambda+API Gatewayで画像を返す時に気をつけるべきx個のこと

画像をランダムに返したいだけのシンプルなAPIを作ろうと思ったけど、とてつもなくハマったのでメモ。

ソースは下記においてあります。
https://github.com/celeron1ghz/apigateway-random-image

lambdaからの値の返し方に作法がある

これはlambdaのコードの中身の話。

以前の API Gateway は統合レスポンスでマッピングテンプレートを色々と作らなくてはいけなかったのですが、 Lambdaプロキシ が追加され、チェックを入れるだけで共通的な処理はいろいろ設定しなくて良くなりました。

その代わりフォーマットに則ったデータの返し方をしないと自動で処理してくれないので下記のルールに従います。

lambdaからの画像データはbase64にして返す

lambdaプロキシがうまい具合にbase64からバイナリに戻してくれるので、ここではbase64で返します。var.toString('base64') とすればbase64にできるので大したことないですね。

lambdaで戻すべきキーがある

下記のキーを含まないといけません。
* statusCode
* headers
* body
* isBase64Encoded = true

bodyには画像ファイルの内容をbase64にしたもの、isBase64Encoded = true は設定しないと変換対象として見てくれないっぽいので忘れずに。

だいたいこんな感じになると思います。

callback(null, {
    statusCode: 200,
    headers: { 'Content-Type': 'image/jpeg' },
    body: data.Body.toString('base64'),
    isBase64Encoded: true,
}); 

統合リクエストでLambdaプロキシ統合をチェックする

これはAPI Gatewayの設定の話。

上記で設定した内容をいい感じで処理してくれるようにするためにチェックを入れます。

バイナリサポートにMIMEを設定する

これもAPI Gatewayの設定の話。

上記ではうまい具合にバイナリに戻してくれると書きましたが、実際はリクエストのAcceptヘッダに指定されたMIMEが、このバイナリサポートで設定したMIMEの中に存在するとバイナリ変換が動きます。

自分はJPGしか使わなかったので image/jpeg しか指定しませんでしたが、そのほかのフォーマットの画像を使うときは適宜増やしましょう。

cloudfrontを使う

ここまででAPI Gatewayで自動で振られるアドレスにブラウザでアクセスすれば画像が落ちてくる!

と思いきや落ちてこないんだなこれが。

上記に書いた通り、リクエストの Accept ヘッダにMIMEを指定してあげないといけませんが、ブラウザの普通のアクセスでそんなことはできないですね。

なお、下記のようにcurlとかで Accept を指定してあげるとちゃんとファイルが落ちてきます。

curl -H "Accept: image/jpeg" "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/image" > moge.jpg

どうすんだよ、っていう解決案がCloudfrontです。

Cloudfrontがファイルを取りに行く時のヘッダを指定できるので、そこで Accept ヘッダを指定するとAPI Gatewayから画像が取れるので、cloudfrontのアドレスにアクセスすれば画像が取れます。

curl "https://xxxxxxxxxxxxxx.cloudfront.net" > moge.jpg

めでたしめでたし。

まとめ

  • Lambda + API Gatewayで画像を扱おうとすると(最初は)だいぶめんどくさい
  • なぜ画像を扱おうとするだけでCloudfrontが出てくるのか(従量課金だからそんなに気にしなくていいけど大げさすぎる感がある)