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を作成します。

Lambda関数を作成する

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

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を作成する

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

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

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

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

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

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

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

POSTMANで実験

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


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