Pythonでサードパーティーのライブラリを使わずにマルチパートフォームデータ送信で画像をアップロードする
以前の(それなりにJava7・Java8のAPIを使いつつSpring Bootでファイルアップロードの処理を書いてみた - 猫にWeb)で作成したサーバーサイドのコードに対して、画像をアップロードするクライアント側のPythonのコードです。
現場の制約でサードパーティーのライブラリを使うことができないので、自前でマルチパートフォームデータの構造を作って画像をアップロードしています。
マルチパートフォームデータとは、HTTPリクエストで複数のフォームデータ(マルチパート)を送るための形式です。大別するとHTTPヘッダ部、ボディ部、フッタ部で構成されています。さらに各データをバウンダリーという文字列で区切り、画像ファイルデータはバイナリデータにして送信します。
具体的には以下のような構成になります。バイナリデータ部分は割愛しています。
*1ブラウザのREST Client系の拡張機能では自動的に内部で構成して送信しています。
POST http://127.0.0.1:8080/upload HTTP/1.1 Host: 127.0.0.1:8080 Accept-Encoding: identity content-type: multipart/form-data; boundary=PpjeR7HcV42hrmr8XhuRA4xgDZRBdA content-length: 49753 --PpjeR7HcV42hrmr8XhuRA4xgDZRBdA Content-Disposition: form-data; name="id" sample.jpg --PpjeR7HcV42hrmr8XhuRA4xgDZRBdA Content-Disposition: form-data; name="image"; filename="sample.jpg" Content-Type: image/jpeg [binary data]
ソースコードはこんな感じです。
import http.client import mimetypes import string import random def post_multipart(host, port, selector, fields, files): content_type, body = encode_multipart_formdata(fields, files) if(selector.find('https') == 0): h = http.client.HTTPSConnection(host, port) else: h = http.client.HTTPConnection(host, port) h.putrequest('POST', selector) h.putheader('content-type', content_type) h.putheader('content-length', str(len(body))) h.endheaders() h.send(body) response = h.getresponse() return response.read() def encode_multipart_formdata(fields, files): BOUNDARY_STR = get_random_str(30) CHAR_ENCODING = "utf-8" CRLF = bytes("\r\n", CHAR_ENCODING) L = [] for (key, value) in fields.items(): L.append(bytes("--" + BOUNDARY_STR, CHAR_ENCODING)) L.append(bytes('Content-Disposition: form-data; name="%s"' % key, CHAR_ENCODING)) L.append(b'') L.append(bytes(value, CHAR_ENCODING)) for (key, value) in files.items(): filename = value['filename'] content = value['content'] L.append(bytes('--' + BOUNDARY_STR, CHAR_ENCODING)) L.append(bytes('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename), CHAR_ENCODING)) L.append(bytes('Content-Type: %s' % get_content_type(filename), CHAR_ENCODING)) L.append(b'') L.append(content) L.append(bytes('--' + BOUNDARY_STR + '--', CHAR_ENCODING)) L.append(b'') body = CRLF.join(L) content_type = 'multipart/form-data; boundary=' + BOUNDARY_STR return content_type, body def get_content_type(filename): return mimetypes.guess_type(filename)[0] or 'application/octet-stream' def get_random_str(length): return ''.join([random.choice(string.ascii_letters + string.digits) for i in range(length)])
使い方はこんな感じです。
if __name__ == '__main__': host = 'httpbin.org' selector = 'http://httpbin.org/post' port = '80' file_path = "sample.jpg" fields = { 'id' : file_path } content = open(file_path, 'rb').read() files = {'image': {'filename': 'sample.jpg', 'content': content}} response = post_multipart(host, port, selector, fields, files) print(response.decode('utf-8'))
コードはここです。
https://github.com/necoyama3/file-upload-client-sample/blob/master/NonLibraryFileUpload.py