multipart/form-data
ํ์ด์ฌ์ผ๋ก ์์ฒญ ์ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ ๋ฌด์์
๋๊น? ํ์ผ์ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ ์ดํดํ์ง๋ง์ด ๋ฐฉ๋ฒ์ผ๋ก ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ ์ดํดํ ์ ์์ต๋๋ค.
๋ต๋ณ
๊ธฐ๋ณธ์ ์ผ๋ก files
๋งค๊ฐ ๋ณ์ (์ฌ์ ) ๋ฅผ ์ง์ requests
ํ๋ฉด multipart/form-data
POST ๋์ POST๋ฅผ ๋ณด๋
๋๋ค application/x-www-form-urlencoded
. ๊ทธ๋ฌ๋ ํด๋น ์ฌ์ ์์ ์ค์ ํ์ผ์ ์ฌ์ฉํ๋ ๊ฒ์ผ๋ก ์ ํ๋์ง๋ ์์ต๋๋ค.
>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200
httpbin.org๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒ์ ํ ํค๋๋ฅผ ์ ์ ์์ต๋๋ค. ์ response.json()
์ฐ๋ฆฌ๊ฐ ์์ต๋๋ค
>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'close',
'Content-Length': '141',
'Content-Type': 'multipart/form-data; '
'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.21.0'}
๋ ๋์ ๋ฐฉ๋ฒ์ ๋จ์ผ ๋ฌธ์์ด ๋๋ ๋ฐ์ดํธ ๊ฐ์ฒด ๋์ ํํ์ ์ฌ์ฉํ์ฌ ํ์ผ ์ด๋ฆ, ์ปจํ ์ธ ์ ํ ๋ฐ ๊ฐ ๋ถ๋ถ์ ๋ํ ์ถ๊ฐ ํค๋๋ฅผ ์ถ๊ฐ๋ก ์ ์ด ํ ์ ์์ต๋๋ค. ํํ์ 2 ~ 4 ๊ฐ์ ์์๋ฅผ ํฌํจํด์ผํฉ๋๋ค. ํ์ผ ์ด๋ฆ, ์ปจํ ์ธ , ์ ํ์ ์ผ๋ก ์ปจํ ์ธ ์ ํ ๋ฐ ์ถ๊ฐ ํค๋์ ์ ํ์ ์ฌ์ .
None
ํ์ผ ์ด๋ฆ์ผ๋ก ํํ ํ์์ ์ฌ์ฉํ์ฌ filename="..."
ํด๋น ๋ถ๋ถ์ ๋ํ ์์ฒญ์์ ๋งค๊ฐ ๋ณ์๊ฐ ์ญ์ ๋ฉ๋๋ค.
>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"
bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"
bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--
files
๋์ผํ ์ด๋ฆ์ ๊ฐ์ง ์ฃผ๋ฌธ ๋ฐ / ๋๋ ์ฌ๋ฌ ํ๋๊ฐ ํ์ํ ๊ฒฝ์ฐ ๋ ๊ฐ ํํ์ ๋ชฉ๋ก์ด ๋ ์๋ ์์ต๋๋ค.
requests.post(
'http://requestb.in/xucj9exu',
files=(
('foo', (None, 'bar')),
('foo', (None, 'baz')),
('spam', (None, 'eggs')),
)
)
๋น์ ์ด ๋ชจ๋๋ฅผ ์ง์ ํ๋ ๊ฒฝ์ฐ files
์ data
, ๊ทธ๋ฐ ๋ค์์ ๋ฐ๋ผ ๊ฐ ์ data
POST๊ฐ ๋ชธ์ ๋ง๋๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค ๋ฌด์. ๊ฒฝ์ฐ data
๋ฌธ์์ด ์ธ ๋ง์ด ์ฌ์ฉํ ์ ์ก์ ์ค๊ป; ๊ทธ๋ ์ง ๋ชจ๋ data
์ files
์ ์์์ ํจ๊ป ์ฌ์ฉ๋๋ data
์ ๋์ด.
๊ณ ๊ธ ๋ฉํฐ ํํธ ์ง์requests-toolbelt
์ ํฌํจ ํ๋ ์ฐ์ํ ํ๋ก์ ํธ ๋ ์์ต๋๋ค . ๋งค๊ฐ ๋ณ์ ์ ๋์ผํ ํ์์ผ๋ก ํ๋ ์ ์๋ฅผ ์ฌ์ฉ ํ์ง๋ง, ๋ฌ๋ฆฌ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ผ ์ด๋ฆ ๋งค๊ฐ ๋ณ์๋ฅผ ์ค์ ํ์ง ์์ต๋๋ค. ๋ํ ์ด๋ฆฐ ํ์ผ ๊ฐ์ฒด์์ ์์ฒญ์ ์คํธ๋ฆฌ๋ฐ ํ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์ ๋จผ์ ์์ฒญ ๋ณธ๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ์ ๊ตฌ์ฑํฉ๋๋ค.files
requests
requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
mp_encoder = MultipartEncoder(
fields={
'foo': 'bar',
# plain file object, no filename or mime type produces a
# Content-Disposition header with just the part name
'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
}
)
r = requests.post(
'http://httpbin.org/post',
data=mp_encoder, # The MultipartEncoder is posted as data, don't use files=...!
# The MultipartEncoder provides the content-type header with the boundary:
headers={'Content-Type': mp_encoder.content_type}
)
ํ๋๋ ๋์ผํ ๊ท์น์ ๋ฐ๋ฆ
๋๋ค. ํ์ผ ์ด๋ฆ, ๋ถ๋ถ MIME ํ์ ๋๋ ์ถ๊ฐ ํค๋๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด 2 ~ 4 ๊ฐ์ ์์๊ฐ์๋ ํํ์ ์ฌ์ฉํ์ญ์์ค. files
๋งค๊ฐ ๋ณ์ ์ ๋ฌ๋ฆฌ filename
ํํ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ์ ์ฐพ์ผ๋ ค๊ณ ํ์ง ์์ต๋๋ค.
๋ต๋ณ
์ด์ ๋ต๋ณ์ด ์์ฑ๋์์ผ๋ฏ๋ก ์์ฒญ์ด ๋ณ๊ฒฝ๋์์ต๋๋ค. ์์ธํ ๋ด์ฉ ์ Github ์ ๋ฒ๊ทธ ์ค๋ ๋ ๋ฅผ ํ์ธํ๊ณ ์์ ๋ ์ด ์ฃผ์ ์ ์ฐธ์กฐํ์ญ์์ค.
์์ปจ๋, files ๋งค๊ฐ ๋ณ์๋ ์์ฒญ์ ๋ฉํฐdict
ํํธ ์ธ์ฝ๋ฉ ํ์ผ POST ์น์
์ ์ค๋ช
๋๋๋ก ํค๊ฐ ์์ ํ๋์ ์ด๋ฆ์ด๊ณ ๊ฐ์ด ๋ฌธ์์ด ๋๋ 2, 3 ๋๋ 4 ๊ธธ์ด ํํ ์ธ a๋ฅผ ์ฌ์ฉ ํฉ๋๋ค. ๋น ๋ฅธ ์์:
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
์์ ํํ์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑ๋ฉ๋๋ค.
(filename, data, content_type, headers)
๊ฐ์ด ๋ฌธ์์ด ์ธ ๊ฒฝ์ฐ ํ์ผ ์ด๋ฆ์ ๋ค์๊ณผ ๊ฐ์ด ํค์ ๋์ผํฉ๋๋ค.
>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}
Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id"
Content-Type: application/octet-stream
72c2b6f406cdabd578c5fd7598557c52
๊ฐ์ด ํํ์ด๊ณ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ด None
ํ์ผ ์ด๋ฆ ์ธ ๊ฒฝ์ฐ ํฌํจ๋์ง ์์ต๋๋ค.
>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}
Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream
72c2b6f406cdabd578c5fd7598557c52
๋ต๋ณ
๋น์ ์ ์ฌ์ฉํ ํ์๊ฐ files
๋ฉํฐ ํํธ ํผ POST ์์ฒญ์ ๋ณด๋ผ ๋งค๊ฐ ๋ณ์๋ฅผ ์ฌ์ง์ด ์ด๋ค ํ์ผ์ ์
๋ก๋ ํ ํ์๊ฐ ์์ต๋๋ค.
์๋ ์์ฒญ ์์ค์์ :
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
...
:param files: (optional) Dictionary of ``'name': file-like-objects``
(or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``,
3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``,
where ``'content-type'`` is a string
defining the content type of the given file
and ``custom_headers`` a dict-like object
containing additional headers to add for the file.
๊ด๋ จ ๋ถ๋ถ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค file-tuple can be a
2-tuple
, .3-tuple
or a
4-tuple
์์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ก๋ ํ ํ์ผ๊ณผ ์์ ํ๋๋ฅผ ๋ชจ๋ ํฌํจํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฉํฐ ํํธ ์์ ์์ฒญ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
multipart_form_data = {
'file2': ('custom_file_name.zip', open('myfile.zip', 'rb')),
'action': (None, 'store'),
'path': (None, '/path1')
}
response = requests.post('https://httpbin.org/post', files=multipart_form_data)
print(response.content)
โ ์ผ๋ฐ ํ
์คํธ ํ๋์ ํํ์์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ฃผ๋ชฉํ์ญ์์ค. None
ํ์ผ ์
๋ก๋์๋ง ์ฌ์ฉ๋๋ ํ์ผ ์ด๋ฆ ํ๋์ ์๋ฆฌ ํ์ ์์ด์ง๋ง None
๋ฐ์ดํฐ๋ฅผ ์ ์ถํ๊ธฐ ์ํด ์ฒซ ๋ฒ์งธ ๋งค๊ฐ ๋ณ์๋ก ์ ๋ฌ ๋๋ ํ
์คํธ ํ๋์ ๊ฒฝ์ฐ .
์ด๋ฆ์ด ๊ฐ์ ์ฌ๋ฌ ํ๋
์ฌ์ ๋์ ๋์ผํ ์ด๋ฆ์ผ๋ก ์ฌ๋ฌ ํ๋๋ฅผ ๊ฒ์ํด์ผํ๋ ๊ฒฝ์ฐ ํ์ด๋ก๋๋ฅผ ํํ ๋ชฉ๋ก (๋๋ ํํ)์ผ๋ก ์ ์ ํ ์ ์์ต๋๋ค.
multipart_form_data = (
('file2', ('custom_file_name.zip', open('myfile.zip', 'rb'))),
('action', (None, 'store')),
('path', (None, '/path1')),
('path', (None, '/path2')),
('path', (None, '/path3')),
)
์คํธ๋ฆฌ๋ฐ ์์ฒญ API
์์ API๋ ๋น์ ์์ํ ํ์ด์ฌ ์ ๋๊ฐ ์๋ ๊ฒฝ์ฐ, ์ฌ์ฉ์ ๊ณ ๋ ค ์์ฒญ์ด ํด ๋ฒจํธ ( pip install requests_toolbelt
์ ํ์ฅ ์ธ) ์ ํต์ฌ ์๊ตฌ๋ ๊ทธ ํ์ผ ์
๋ก๋๊ฐ๋ฟ๋ง ์๋๋ผ ์คํธ๋ฆฌ๋ฐ์ ๋ํ ์ง์์ ์ ๊ณตํ๋ ๋ชจ๋ MultipartEncoder ๋์ ์ฌ์ฉํ ์ ์์ต๋๋ค files
, ์ด๋๋ ์์ต๋๋ค ํ์ด๋ก๋๋ฅผ ์ฌ์ , ํํ ๋๋ ๋ชฉ๋ก์ผ๋ก ์ ์ํฉ๋๋ค.
MultipartEncoder
์ค์ ์
๋ก๋ ํ๋๊ฐ ์๊ฑฐ๋์๋ ๋ฉํฐ ํํธ ์์ฒญ์ ๋ชจ๋ ์ฌ์ฉ๋ ์ ์์ต๋๋ค. data
๋งค๊ฐ ๋ณ์์ ์ง์ ๋์ด์ผํฉ๋๋ค .
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
multipart_data = MultipartEncoder(
fields={
# a file upload field
'file': ('file.zip', open('file.zip', 'rb'), 'text/plain')
# plain text fields
'field0': 'value0',
'field1': 'value1',
}
)
response = requests.post('http://httpbin.org/post', data=multipart_data,
headers={'Content-Type': multipart_data.content_type})
๋์ผํ ์ด๋ฆ์ ๊ฐ์ง ์ฌ๋ฌ ํ๋๋ฅผ ๋ณด๋ด๊ฑฐ๋ ์์ ํ๋์ ์์๊ฐ ์ค์ํ ๊ฒฝ์ฐ ์ฌ์ ๋์ ํํ ๋๋ ๋ชฉ๋ก์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
multipart_data = MultipartEncoder(
fields=(
('action', 'ingest'),
('item', 'spam'),
('item', 'sausage'),
('item', 'eggs'),
)
)
๋ต๋ณ
๋ค์์ ์์ฒญ์ ์ฌ์ฉํ์ฌ ์ถ๊ฐ ๋งค๊ฐ ๋ณ์๊ฐ์๋ ๋จ์ผ ํ์ผ์ ์ ๋ก๋ํ๋ ๊ฐ๋จํ ์ฝ๋ ์ค ๋ํซ์ ๋๋ค.
url = 'https://<file_upload_url>'
fp = '/Users/jainik/Desktop/data.csv'
files = {'file': open(fp, 'rb')}
payload = {'file_id': '1234'}
response = requests.put(url, files=files, data=payload, verify=False)
์ฝํ ์ธ ์ ํ์ ๋ช ์ ์ ์ผ๋ก ์ง์ ํ ํ์๋ ์์ต๋๋ค.
์ฐธ๊ณ : ์์ ๋ต๋ณ ์ค ํ๋์ ๋ํด ์ธ๊ธํ๊ณ ์ถ์์ง๋ง ํํ์ด ๋ฎ์์ ์ฌ๊ธฐ์ ์๋ก์ด ๋ต๋ณ์ ์์ฑํ์ง ๋ชปํ์ต๋๋ค.
๋ต๋ณ
name
์ฌ์ดํธ์ HTML์์๋ ์
๋ก๋ ํ์ผ ์ ์์ฑ ์ ์ฌ์ฉํด์ผํฉ๋๋ค . ์:
autocomplete="off" name="image">
์ name="image">
๊ฒ ์ด? ํ์ผ์ ์
๋ก๋ํ๊ธฐ ์ํด ์ฌ์ดํธ์ HTML์์ ์ฐพ์ ์ ์์ต๋๋ค. ํ์ผ์ ์
๋ก๋ํ๋ ค๋ฉด ํ์ผ์ ์ฌ์ฉํด์ผํฉ๋๋คMultipart/form-data
์คํฌ๋ฆฝํธ:
import requests
site = 'https://prnt.sc/upload.php' # the site where you upload the file
filename = 'image.jpg' # name example
์ฌ๊ธฐ์์ ์ด๋ฏธ์ง ๋์ HTML๋ก ์ ๋ก๋ ํ์ผ ์ด๋ฆ์ ์ถ๊ฐํ์ญ์์ค.
up = {'image':(filename, open(filename, 'rb'), "multipart/form-data")}
์ ๋ก๋์ ์ ๋ก๋ ๋ฒํผ์ ํด๋ฆญํด์ผํ๋ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
data = {
"Button" : "Submit",
}
๊ทธ๋ฐ ๋ค์ ์์ฒญ์ ์์ํ์ญ์์ค.
request = requests.post(site, files=up, data=data)
๊ทธ๋ฆฌ๊ณ , ํ์ผ์ด ์ฑ๊ณต์ ์ผ๋ก ์ ๋ก๋๋์์ต๋๋ค
๋ต๋ณ
๋ฉํฐ ํํธ / ํผ ๋ฐ์ดํฐ ํค ๋ฐ ๊ฐ ๋ณด๋ด๊ธฐ
์ปฌ ๋ช ๋ น :
curl -X PUT http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F taskStatus=1
ํ์ด์ฌ ์์ฒญ-๋ ๋ณต์กํ POST ์์ฒญ :
updateTaskUrl = "http://127.0.0.1:8080/api/xxx"
updateInfoDict = {
"taskStatus": 1,
}
resp = requests.put(updateTaskUrl, data=updateInfoDict)
๋ฉํฐ ํํธ / ํผ ๋ฐ์ดํฐ ํ์ผ ๋ณด๋ด๊ธฐ
์ปฌ ๋ช ๋ น :
curl -X POST http://127.0.0.1:8080/api/xxx ...
-H 'content-type: multipart/form-data; boundary=----xxx' \
-F file=@/Users/xxx.txt
ํ์ด์ฌ ์์ฒญ-๋ฉํฐ ํํธ ์ธ์ฝ๋ฉ ํ์ผ POST :
filePath = "/Users/xxx.txt"
fileFp = open(filePath, 'rb')
fileInfoDict = {
"file": fileFp,
}
resp = requests.post(uploadResultUrl, files=fileInfoDict)
๊ทธ๊ฒ ๋ค์ผ.
๋ต๋ณ
๋ค์์ ํ๋์ ํฐ ๋จ์ผ ํ์ผ์ ๋ฉํฐ ํํธ formdata๋ก ์ ๋ก๋ํ๋ ๋ฐ ํ์ํ Python ์ค ๋ํซ์ ๋๋ค. ์๋ฒ ์ธก์์ NodeJs Multer ๋ฏธ๋ค์จ์ด๊ฐ ์คํ ์ค์ ๋๋ค.
import requests
latest_file = 'path/to/file'
url = "http://httpbin.org/apiToUpload"
files = {'fieldName': open(latest_file, 'rb')}
r = requests.put(url, files=files)
์๋ฒ ์ธก์ ๊ฒฝ์ฐ https://github.com/expressjs/multer ์์ multer ๋ฌธ์๋ฅผ ํ์ธ ํ์ญ์์ค.
์ฌ๊ธฐ์์ single ( โfieldNameโ) ํ๋๋ ๋ค์๊ณผ ๊ฐ์ด ํ๋์ ๋จ์ผ ํ์ผ์ ํ์ฉํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
var upload = multer().single('fieldName');