matsukaz's blog

Agile, node.js, ruby, AWS, cocos2d-xなどなどいろいろやってます

AWS Lambda + API Gateway + CloudFrontでサーバレスに画像を配信する

前回説明したLINE BOT(画像スタンプBOT)ですが、画像表示のためのアーキテクチャはいろいろ応用が効く部分なので、今回はもう少し詳しくご紹介します。

matsukaz.hatenablog.com

実現したいこと

画像スタンプBOTの場合、LINEのトーク上に画像を表示するためには

という要件を満たす必要がありました。

インターネット上の画像はHTTPであったり画像サイズや容量が大きかったりと、必ずしも上記の要件を満たしているとは限らないです。 そこでAWS Lambda + API Gateway + CloudFrontを組み合わせることで、これらの要件を満たした画像配信を実現しました。 前回の図で言うと、右下の CloudFront → API GatewayAWS Lambdaの流れです。

f:id:matsukaz:20170215012507p:plain

アーキテクチャ構築手順

では早速アーキテクチャの構築手順。 といっても、実はQiitaのこちらの手順とほとんど一緒です(ぉぃ

qiita.com

ただこの通りやってもうまくいかない点がいくつかあったので、流れを紹介しながらそちらについても触れていきます。

AWS Lambda実装

まず画像データの取得処理はLambdaを利用します。

Qiitaの記事ではS3から画像を取得していましたが、画像スタンプBOTはインターネット上の画像をHTTPリクエストを行って取得しています。 Node.jsの実装としては以下のようになります。

'use strict';

const http = require('http');
const https = require('https');
const im = require('imagemagick');
const fs = require('fs');

exports.handler = (event, context, callback) => {
    // URLエンコードされた画像のパス
    const filepath = decodeURIComponent(event.filepath);

    // リサイズの基準(w/h/なし)
    const resizeBy = event.resizeBy;
    
    let request = filepath.startsWith('https') ? https : http;
    
    request.get(filepath, (res) => {
        let data = [];
        res.on('data', (chunk) => {
            data.push(chunk);
        });
        res.on('end', (res) => {
            let binary = Buffer.concat(data);
            if (resizeBy === 'w' || resizeBy === 'h') {
                resize(resizeBy, binary, callback);
            } else {
                console.log('No need to resize');
                callback(null, binary.toString('base64'));
            }
        });
    }).on('error', (e) => {
        callback(e);
    });
};

resizeメソッドは、Lambdaの image-processing-service のblueprintの内容ほとんどそのまんまなので、そちらを参考にしてください。

このLambda実装により、表示したい画像のhttp/httpsの違い、画像サイズや容量の制約を吸収しているわけです。 リサイズの大きさについては、Google Custom Search APIを使って画像パスを取得するタイミングで画像サイズも取得できるので、リサイズの有無やリサイズする方向を判断して引数で受け取るようにしています。 またJPEG以外の画像を対象外とするため、Google Custom Search APIを使った検索時にJPEGに限定して検索しています。

ちなみにLambdaの実装ですが、手軽さを重視して aws-serverless-express を利用して実装しました。 詳しくはこちら。

qiita.com

しっかりと運用していくなら Serverless Framework の方がいいと思いますが、手軽にLamba実装するなら aws-serverless-express もオススメです。

API Gateway設定

LambdaをHTTPリクエストで実行するためにAPI Gatewayを利用します。

API Gatewayの設定としては、Qiitaの記事のまんまです。 ただ1点、APIのテストをする際にAcceptヘッダにimage/*指定してしまうと、base64のままのテキストデータしか受け取れないという問題がありました。

curl --request GET -H "Accept: image/*" -H "Content-Type: image/jpeg" https://...

ではダメで、

curl --request GET -H "Accept: image/jpeg" -H "Content-Type: image/jpeg" https://...

じゃないとバイナリデータとして受け取れませんでした。ご注意ください。 この問題が、次のCloudFrontの設定にも影響してきます。

CloudFront設定

API GatewayだけでもHTTPS配信できますが、AcceptとContent-Typeヘッダ指定をしないとbase64のテキストデータとしてしか受け取ることができないため、Qiitaの記事同様にCloudFrontのカスタムヘッダを利用してヘッダを自動的に付与するようにしました。

API Gatewayでも触れましたが、Acceptヘッダとしては image/jpeg を指定しなければならなかったため、CloudFrontのカスタムヘッダの設定も以下のようになります。

f:id:matsukaz:20170228013532p:plain

他はQiitaの記事と同様です。

CloudFrontのカスタムヘッダ利用時の謎挙動

CloudFrontのカスタムヘッダを設定していた際に、image/* から image/jpeg に変更しようとしたんですが、どう頑張っても image/* から変更できないという事象に出くわしました。

原因は分からずじまいだったのですが、このままだとCloudFront経由であってもbase64のテキストデータとしてしか取得できなくなっていたため、CloudFrontのDistributionを作り直すという対応で乗り切りました。 カスタムヘッダを利用する際はこの事象にご注意を〜。

まとめ

Qiitaの記事ではS3のファイルを、画像スタンプBOTではインターネット上の画像を配信しました。

今回のアーキテクチャを使えば、あらゆるバイナリデータを配信することができますので、複数ファイルをzip圧縮してまとめて返すとか、画像合成したものを配信するとか、なんでもサーバレスで配信することができます。

手軽でありがたいですねー。そしてお財布にも優しいです。まだともだち数が少ないのもありますが、画像スタンプBOTにかかってるコストはいまのところ $0.08 ですw

というわけで良かったら画像スタンプBOTも利用してみてくださいませ! LINE上で画像送り合うのは思った以上に楽しいですよ٩(ˊᗜˋ*)و

友だち追加

ヘボット! DXヘボット!&ボキャネジ3本と工具箱セット

ヘボット! DXヘボット!&ボキャネジ3本と工具箱セット