クラウドエンジニアのノート

情報技術系全般,自分用メモを公開してます。

Pythonでmultipart/form-dataの送受信

はじめに

以下記事の通り、AWS上のLambdaを使って機械学習モデルのAPIを立てたのですが、 Pythonmultipart/form-dataのパースが大変だったので共有します。 tmyoda.hatenablog.com

送信

requestsモジュールを使用すればかんたんです。

以下記事が参考になります。

qiita.com

files = {}
mine_type = "image/jpeg"
file_name = "input_image_quart.jpg"
data = なんかバイト列
files = {'key': (file_name, data, mine_type)}
r =  requests.post(endpoint, files=files)

ちなみに、headerspostの引数に指定できますが、Content-Typeを上書きしてしまうと、boundaryも消えるのでご注意下さい。 (2時間近くハマりました)

受信

AWSのLambda

AWSのLambda限定ですが、以下のパースするスクリプトをStackoverflowで見つけました。

https://stackoverflow.com/questions/50925083/parse-multipart-request-string-in-python/60517247#60517247

cgiはデフォルトで入っているので、モジュールを追加する必要はありません。

def lambda_handler(event, context):
    if 'content-type' in event['headers'].keys():
        c_type, c_data = parse_header(event['headers']['content-type'])
    elif 'Content-Type' in event['headers'].keys():
        c_type, c_data = parse_header(event['headers']['Content-Type'])
    else:
        raise RuntimeError('content-type or Content-Type not found')

    encoded_string = event['body'].encode('utf-8')
    # For Python 3: these two lines of bugfixing are mandatory
    # see also:
    # https://stackoverflow.com/questions/31486618/cgi-parse-multipart-function-throws-typeerror-in-python-3
    c_data['boundary'] = bytes(c_data['boundary'], "utf-8")
    # c_data['CONTENT-LENGTH'] = event['headers']['Content-length']
    data_dict = parse_multipart(io.BytesIO(encoded_string), c_data)

    # 整形
    formatted_dict = {k: v[0] for k, v in data_dict.items()}

その他

上のstackoverflowからのコピペで恐縮ですが、以下のサンプルがわかりやすいです。

requests_toolbelt のインストールが別途必要です。

from requests_toolbelt.multipart import decoder

multipart_string = b"--ce560532019a77d83195f9e9873e16a1\r\nContent-Disposition: form-data; name=\"author\"\r\n\r\nJohn Smith\r\n--ce560532019a77d83195f9e9873e16a1\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example2.txt\"\r\nContent-Type: text/plain\r\nExpires: 0\r\n\r\nHello World\r\n--ce560532019a77d83195f9e9873e16a1--\r\n"
content_type = "multipart/form-data; boundary=ce560532019a77d83195f9e9873e16a1"

for part in decoder.MultipartDecoder(multipart_string, content_type).parts:
  print(part.text)

John Smith
Hello World