AWS API Gateway + Lambda から multipart/form-dataを用いてバイナリデータ(wav)をS3にputする

# やりたいこと
API GatewayとLambdaを使って、S3にWAVファイルをPUTする。

# 実現方法
API Gatewayはmultipart/form-dataを使用したPOST可能なので、
WAVファイルを
API Gateway -> Lambda -> S3
にてPUTする。

# 環境
AWS
API Gateway
Lambda
S3

# 手順

## テスト用S3Bucketを作成する
テスト用のBucketを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/c3fb9290-5326-7e65-1740-8ef2cd90a06c.png)

## Lambda関数を作成する
今回はpython3.6で作成してみました。
body-jsonにBase64でエンコードされたWAVファイルが送られていることを想定しています。
(よくあるサンプルですが・・・)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/2ff3b70f-d05d-2096-53ba-f0cca2b73ad0.png)

```
import json
import boto3
import base64

def lambda_handler(event, context):
    
    s3 = boto3.resource('s3')
    bucket = s3.Bucket('multipart-test-*****')
    
    # バイナリがBase64にエンコードされているので、ここでデコード
    wavBody = base64.b64decode(event['body-json'])    
    
    key = "aaa.wav"
    
    bucket.put_object(
        Body = wavBody,
        Key = key
    )    
```

## APIを作成する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/085d974e-9af8-1537-e38b-aabd521cdf87.png)

さらに統合リクエストの編集をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/3b65ba00-150b-2c51-1825-18c693d74fbb.png)

マッピングテンプレートを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/395fae83-8479-0f86-a455-c8d449d04838.png)

マッピングテンプレートで
「テンプレートが定義されていない場合 (推奨) 」を選択し、
Content-Typeに
multipart/form-data
を追加して、
テンプレートの生成を行います。
テンプレートは「メソッドリクエストのパススルー」はデフォルトのままです。

バイナリメディアタイプにも忘れずに
multipart/form-data
を追加しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/90414b0d-f44d-69e9-aca1-abcf3ae7a843.png)

この状態で、
APIをデプロイすれば完成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/a5d176b4-acf9-76b2-6e3a-8ffa6bb9a41e.png)


早速URLにて呼び出してみましょう。

https://*********.execute-api.ap-northeast-1.amazonaws.com/test/wav


## POSTMANで実験

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/50be9be1-870e-daa5-f1ea-b9366639cb8d.png)

WAVファイルをform-dataからPOSTしてみるとWAVファイルがS3に
作成されてます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143249/f2d36fbf-035c-bd31-45eb-bd4eac8eab81.png)
aaa.wav

これで問題はありません。
と言いたいところですが、
aaa.wavは正常に再生できません。
他のサンプルコードでは、問題なくできてたのに！

私がはまった個所ですが、
body-jsonにてlambdaに送られる情報にはもともとのWAVファイルの情報以外も付与されており、
このままdecodeしてもwavファイルの形式に問題が発生してしまいます。
送信されているbytes文字列をデコードして確認するとこんな感じでした。

```
b'----------------------------648504498822251077984882\r\nContent-Disposition: form-data; name="file"; filename="0340.wav"\r\nContent-Type: audio/wave\r\n\r\nRIFFDs\x00\x00WAVEfmt 
~(wavdataが続く)~
\r\n----------------------------648504498822251077984882--\r\n'

```

ファイルParse用のパラメータや、content情報が付与されています。
WAVファイルのみを抽出したいので、Bytes文字列で
**b'RIFF 以下の情報が必要です。**

lambda側を編集して、

```
import json
import boto3
import base64

def lambda_handler(event, context):    
    
    s3 = boto3.resource('s3')
    bucket = s3.Bucket('multipart-test-matsuo')
    
    # バイナリがBase64にエンコードされているので、ここでデコード
    wavBody = base64.b64decode(event['body-json'])
    wavs = wavBody.split(b'\r\n')    
    
    key = "aaa.wav"
    
    bucket.put_object(
        Body = wavs[4],
        Key = key
    )    
    
```

とします。
送信側のWAVファイルと同様のファイルがS3にPUTされ、
問題なく再生できました。
(ファイルのバイト数も同じでした)

## 2019/05/08 追記
wavファイルの中で、/r/nがあると、
分割されてしまうので、
boundaryを使用して、
分割された配列を結合するようにしました。
ファイル名をkeyにしてParamsに付与するようにして、
ファイル名をそのまま保存できるようにしました。
```

    key = event['params']['querystring']['filename']

    # Boundary文字列の使用
    boundary = jsons[0].replace(b'-', b'')
    wavBody = jsons[4]
    for i in range(5, len(jsons)):
        if jsons[i].find(boundary) != -1:
            break
        else:
            wavBody = wavBody + b'\r\n' + jsons[i]

    bucket.put_object(
        Body = wavBody,
        Key = key
    )  


## 参考
https://www.youtube.com/watch?v=BrYJlR0yRnw
https://dev.classmethod.jp/cloud/aws/sugano-013-api-gateway/
https://qiita.com/is_ryo/items/966f720227cab2fff7f9