銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

AWSでランダムな画像を返すURLを作りました(Stravaのワークアウトのシェアのため)

f:id:takamints:20190524130539j:plain

HTMLのIMG要素のSRC属性に設定しておけば、ランダムに画像を表示するURLです。 複数の画像をAWS S3のバケットに置いておき、CloudFront - API Gateway - Lambda Function というルートで画像を返します。

当初は上の画像をそのURLから取ってきていたのですが、AWSの請求額がグイグイ上がっていくので(CloudFrontのせいだと思う)怖くなって固定画像に変えました。スンマセン。

Stravaのワークアウト完了時に、IFTTTを使って結果(距離、時間など)をTwitterに流していて、走行経路の地図(GoogleMaps)を添付しているのですが、常に「Image not found」となっており(今思えばセキュリティ設定に関連しているのかもしれない)、アクセスするたびに画像が入れ替わるURLを作ればいいやと、初夏の風吹く4月の土曜日に、朝メシ食わずにAWSでポチポチ作業・・・

API Gateway、Lambda Function、S3 Bucket で簡単にできると思っていたのですが、ブラウザのIMGタグで表示させようとするとリクエストヘッダにAcceptで受け入れ可能なMIME Typeを指定しておかなければならなくて、そのためには CloudFront を使わなければならないらしい。なるほど勉強になりました。

目次

Lambda Function

Lambda Functionは、S3のバケットの特定のフォルダー以下から画像ファイルをリストして、その中からランダムに選んだ画像を返します。 バケット名とフォルダの名前は環境変数で設定しています。

const AWS = require("aws-sdk");
const S3 = new AWS.S3();
const bucket = process.env["BUCKET"];
const keyPrefix = process.env["KEYPREFIX"];// `strava-tweet-photos`

exports.handler = async (event) => {

    //S3の画像をリスト
    const listResult = await S3ListObjects({
        Bucket: bucket,
        Prefix: keyPrefix
    });

    //フォルダ以外のキーの配列に変換
    const keys = listResult.Contents
        .map(item => item.Key)
        .filter(key => (key !== keyPrefix));

    //ランダムに一つ選ぶ
    const index = randomInt(keys.length);
    const key = keys[index];

    //画像を取得
    const image = await S3GetObject({
        Bucket: bucket,
        Key: key
    });

    //HTTPレスポンスを返す
    const response = {
        statusCode: 200,
        headers: {
            'Content-Type': image.ContentType
        },
        body: image.Body.toString('base64'),
        isBase64Encoded: true,
    };
    return response;
};

const S3ListObjects = params =>
    new Promise( (resolve, reject) =>
        S3.listObjects(params,
            (err, data) => (err ?
                reject(err) :
                resolve(data))));

const S3GetObject = params =>
    new Promise( (resolve, reject) =>
        S3.getObject(params,
            (err, data) => (err ?
                reject(err) :
                resolve(data))));

const randomInt = max =>
    Math.floor(
        Math.random() * Math.floor(max));

API Gateway REST API

適当なリソースを作ってGETメソッドを作り、「統合リクエスト」で「Lambda統合」を選択して、上のLambdaを指定します。 Lambdaの戻り値をそのままメソッドレスポンスへ渡すので、「統合リクエスト」の「Lambdaプロキシ統合」にチェックを入れます。 ブラウザで表示するだけなら、CORSは対応しなくて良いですね。

CloudFront Distribution

上のREST APIをデプロイし、そのエンドポイント(例: xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com)を Origin に指定してCloudFrontディストリビューションを作ります。 Origin Path にはAPIをデプロイしたステージ名を指定(例:/production)して、Origin Protocol PolicyHTTPS Only とします(API GatewayREST APIなので)。

今後の話

画像のサイズを指定できたり、画像のEXIFを読み取って、アクセスした日に近い画像を返すとか、いろんな機能拡張が思い浮かびます。 ちょっとずつ楽しみながら実装していきたいなーと思いました。

参考リンク

qiita.com

qiita.com