【AWS勉強メモ】Serverless Frameworkでバイナリデータを処理するアーキテクチャを構築

# 1. まとめ

Serverless Frameworkを利用しバイナリデータを処理するアーキテクチャを構築する．[API Gateway+Lambdaでバイナリデータを処理](https://qiita.com/akatsukaha/items/024fbabc9a321665cb23)で実装したクライアントからAPI GatewayにPOSTされた画像データをLambdaでS3に保存する環境をServerless Frameworkで実現する．

# 2. 目次

- 1 まとめ
- 2 目次
- 3 事前環境確認
- 4 環境構築
    - 4.1 プラグインのインストール
    - 4.2 プロジェクトの作成・構築
    - 4.3 デプロイ
    - 4.4 動作確認
- 5 補足
    - 5.1 serverless-apigw-binaryが設定できない
    - 5.2 serverless-plugin-custom-binaryのymlでの設定
- 6 Clean up
- 7 参考リンク

# 3. 事前環境確認

各種バージョンの確認

```bash
$ npm -v
5.6.0

$ node -v
v10.0.0

$ aws --version
aws-cli/1.15.10 Python/3.6.5 Darwin/17.5.0 botocore/1.10.10

$ sls -v
1.27.1
```

# 4. 環境構築

## 4.1. プラグインのインストール

API Gatewayでバイナリデータを処理したい場合，バイナリサポートを有効にする必要がある．具体的には，API Gatewayにて適切なバイナリメディアタイプを設定する必要がある．Serverless Frameworkはデフォルトではバイナリサポートの設定ができないため，プラグインを利用する．

プラグインは下記の2つがある模様
- [serverless-apigw-binary](https://www.npmjs.com/package/serverless-apigw-binary)
- [serverless-plugin-custom-binary](https://www.npmjs.com/package/serverless-plugin-custom-binary)

結論からいうと，serverless-plugin-custom-binaryを利用した．

理由は，serverless-apigw-binaryを利用してdelopyができなかったため．serverless-apigw-binaryがdeploy出来なかったことについては最後の方に補足する．

下記でプラグインをインストール

```bash
$ npm install --save-dev serverless-plugin-custom-binary
```

## 4.2. プロジェクトの作成・構築

```
$ sls create --template aws-nodejs --path upload
$ cd upload
$ tree
.
├── handler.js
└── serverless.yml

0 directories, 2 files
```

```yml:serverless.yml
service: upload

plugins:
  - serverless-plugin-custom-binary

custom:
  apigatewayBinary:
    types:
      - image/*

provider:
  name: aws
  runtime: nodejs6.10 # 環境を設定
  profile: serverless-admin # 利用する権限を指定
  region: ap-northeast-1 # 東京リージョン
  stage: v1 # サービスのステージ

  iamRoleStatements: # IAMを追加．S3を読み書き出来るようにする
    - Effect: Allow
      Action:
        - s3:*
      Resource: "arn:aws:s3:::s3-xxxxxxxxxxxxxx-20180702/*"

functions:
  upload:
    handler: handler.upload
    events:
      - http:
          path: /upload
          method: post
          cors: true
          private: false
```

```javascript:handler.js
'use strict';

const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01'});

exports.handler = (event, context, callback) => {

    // add logging level
    console.log = console.log.bind(null, '[LOG]');
    console.info = console.info.bind(null, '[INFO]');
    console.warn = console.warn.bind(null, '[WARN]');
    console.error = console.error.bind(null, '[ERROR]');

    // variables
    var response = {}

    // check event
    console.log(event)

    // if data is encoded with base64, then decode with base64 and save it in S3
    if (event.isBase64Encoded === true) {

        // decode
        var decode = new Buffer(event.body,'base64');

        // save data in s3
        var params = {
                        Bucket: 's3-xxxxxxxxxxxxxxx-20180702', 
                        Key: 'ramen.jpg', 
                        ContentType: 'image/jpeg', 
                        Body: decode
                    };
        s3.putObject(params, function(err, data) {
            if (err) {
                console.error(err);
                console.error('image upload failed');

                // create response
                response.statusCode = 400
                response.body = JSON.stringify({
                    message: 'fail.'
                })
                callback(null, response);

            } else {
                console.log('image upload success');

                // create response
                response.statusCode = 200
                response.body = JSON.stringify({
                    message: 'image has been uploaded to S3'
                })
                callback(null, response);
            }
        });

    } else {

        // create response
        response.statusCode = 400
        response.body = JSON.stringify({
            message: 'fail.'
        })
        callback(null, response);
    }
};
```

## 4.3. デプロイ

```
$ sls deploy -v
```

## 4.4. 動作確認

cURLで実行してみる

```bash
$ curl -X POST -H 'Content-type: image/jpeg' --data-binary "@scene_01.jpeg" https://xxxxxxxxxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/upload

{"message":"image has been uploaded to S3"}
```

S3を確認すると画像ファイルが保存されている．


# 5.　補足

## 5.1. serverless-apigw-binaryが設定できない

serverless-apigw-binaryが利用できなかった事象について補足する．

詳細は補足に記載することとする．

下記でプラグインをインストール

```bash
$ npm install --save-dev serverless-apigw-binary
```

下記がdeploy時のエラー．

```
$ sls deploy -v

  ...省略...

  Serverless Error ---------------------------------------

  Too Many Requests

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com

  Your Environment Information -----------------------------
     OS:                     darwin
     Node Version:           10.0.0
     Serverless Version:     1.27.1
```

ググってみると，作成者が[issue](https://github.com/serverless/serverless/issues/4957)を上げていた．

## 5.2. serverless-plugin-custom-binaryのymlでの設定

ymlでプラグインを設定している箇所

```yml
service: upload

plugins:
  - serverless-plugin-custom-binary

custom:
  apigatewayBinary:
    types:
      - image/*
```

# 6. Clean up

環境を削除

```
$ sls remove
```

# 7. 参考リンク

- [Binary responses with Serverless Framework and API Gateway](https://medium.com/nextfaze/binary-responses-with-serverless-framework-and-api-gateway-5fde91376b76)
- [Serverless FrameworkでAPI Gatewayバイナリサポートを設定する](https://qiita.com/ykarakita/items/6e49e33eb363add56f0b)
- [serverless-apigw-binary](https://www.npmjs.com/package/serverless-apigw-binary)
- [serverless-plugin-custom-binary](https://www.npmjs.com/package/serverless-plugin-custom-binary)