記載の経緯
multipart/form-dataでPOSTされたデータをPythonでパースする方法として、Python標準モジュールのcgi.FieldStorageでパースする記事をいくつか見かけましたが、cgiモジュールは Python3.11で非推奨、Python3.13で削除されました。
FieldStorageの代替方法として、公式ドキュメントにてmultipartモジュールが上げられており、multipartを使った記事があまりなかったため、記載します。
Deprecated since version 3.11, will be removed in version 3.13:
The cgi module is deprecated (see PEP 594 for details and alternatives).The FieldStorage class can typically be replaced with urllib.parse.parse_qsl() for GET and HEAD requests, and the email.message module or multipart for POST and PUT. Most utility functions have replacements.
※公式ドキュメントから引用:https://docs.python.org/ja/3.12/library/cgi.html
multipartモジュール
確認したバージョンは、記載時最新のv1.1.0
です。
ライセンス | MIT License |
依存するモジュール | なし |
Python バージョン | 3.5以上 |
参考までに、snyk Adviserの結果は 82点 でした(2024/11/3時点)。
https://snyk.io/advisor/python/multipart
multipartモジュールは、multipart/form-dataでPOSTされたボディを直にパースする関数に加えて、WSGIアプリケーション向けの高レベルのヘルパー関数も用意されています。
本記事で紹介するのは前者のmultipart/form-dataのボディを直にパースする関数のサンプルです。
後者のWSGI向けのサンプルはモジュールのreadmeに記載があるのでそちらを参照ください。
サンプルコード
import io
from multipart import MultipartParser
multipart_body_data = """\
----------------------------892683388101143002038604
Content-Disposition: form-data; name="jsonfile"; filename="test.json"
Content-Type: application/json
{
"testStr": "testValue",
"testInt": 1
}
----------------------------892683388101143002038604
Content-Disposition: form-data; name="test_text"
This is test.
----------------------------892683388101143002038604--
""".replace("\n", "\r\n").encode("utf-8")
def parse_multipart(body):
stream = io.BytesIO(body)
boundary = "--------------------------892683388101143002038604"
# MultipartParserクラスにパースした各データを返却する__iter__が実装されているので、ループで回せる。
for part in MultipartParser(stream, boundary):
print(
{
"size": part.size,
"name": part.name,
"filename": part.filename,
"headerlist": part.headerlist,
"payload": part.value, # 文字デコードされたデータ
"raw": part.raw, # 生データ
}
)
parse_multipart(multipart_body_data)
※実行環境:python3.11.9
実行結果
{'size': 51, 'name': 'jsonfile', 'filename': 'test.json', 'headerlist': [('Content-Disposition', 'form-data; name="jsonfile"; filename="test.json"'), ('Content-Type', 'application/json')], 'payload': '{\r\n "testStr": "testValue",\r\n "testInt": 1\r\n}', 'raw': b'{\r\n "testStr": "testValue",\r\n "testInt": 1\r\n}'}
{'size': 13, 'name': 'test_text', 'filename': None, 'headerlist': [('Content-Disposition', 'form-data; name="test_text"')], 'payload': 'This is test.', 'raw': b'This is test.'}
解説
-
multipart_body_data
:
multipart/form-dataで送信されたボディデータです。サンプルのためコード上に記載しています。
実際のボディデータの改行コードはCRLFのため改行コードを変換しています。 -
parse_multipart()
:-
MultipartParser
クラスを使用してパースを行います。コンストラクタの引数にボディデータのストリーム1とバウンダリを指定します。 -
MultipartParser
クラスにはパースした各データを返却する__iter__が実装されているので、for文で各要素をループすることができます。 - パースされると、multipart/form-dataの各要素が
MultipartPart
クラスとして生成されます。メンバ変数にアクセスして各情報を取り出すことができます。
-
サンプルコード(APIGateway + Lambda)
参考までに、AWSのAPIGateway + Lambda(プロキシ統合)の構成で、multipart/form-dataでPOSTされたファイルをS3にアップロードするコードも掲載しておきます。
import base64
import io
import boto3
from multipart import MultipartParser
s3 = boto3.client("s3")
bucket_name = "bucket_name"
def lambda_handler(event, context):
# リクエストボディがbase64エンコードされてるのでデコード(生のボディを受け取る場合は不要)
body = base64.b64decode(event["body"])
stream = io.BytesIO(body)
boundary = event["headers"]["Content-Type"].split("boundary=")[1]
# MultipartParserクラスにパースした各データを返却する__iter__が実装されているので、ループで回せる。
for part in MultipartParser(stream, boundary):
print(
{
"size": part.size,
"name": part.name,
"filename": part.filename,
"headerlist": part.headerlist,
}
)
if part.filename:
# ファイルはS3アップロード(Content-DispositionヘッダーにfileNameがあるデータをアップロード)
s3.upload_fileobj(part.file, bucket_name, part.filename)
else:
# part.valueによって文字デコードされたボディが返される(生データはpart.raw)
print(f"This is not file-like data. Payload:{part.value}")
ret = {
"isBase64Encoded": "false",
"statusCode": 200,
"headers": {"test-header-result": "ok"},
"body": "Files upload to s3 is complete.",
}
return ret
APIGatewayを用いた場合にLambdaに入力されるデータの構造は以下。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
参考