Python - HTTP多部分/form-data POST请求

7
我希望能够将文件上传到Web服务器。根据我的了解,使用HTTP POST请求的multipart/form-data编码类型是最好的方法。
我的研究表明,使用Python标准库没有简单的方法来实现这一点。我正在使用Python 3。
(注意:查看一个名为requestsPyPI Link)的软件包,可以轻松完成此操作)
我目前正在使用以下方法:
import mimetypes, http.client
boundary = 'wL36Yn8afVp8Ag7AmP8qZ0SA4n1v9T' # Randomly generated
for fileName in fileList:
    # Add boundary and header
    dataList.append('--' + boundary)
    dataList.append('Content-Disposition: form-data; name={0}; filename={0}'.format(fileName))

    fileType = mimetypes.guess_type(fileName)[0] or 'application/octet-stream'
    dataList.append('Content-Type: {}'.format(fileType))
    dataList.append('')

    with open(fileName) as f: 
        # Bad for large files
        dataList.append(f.read())

dataList.append('--'+boundary+'--')
dataList.append('')
contentType = 'multipart/form-data; boundary={}'.format(boundary)

body = '\r\n'.join(dataList)
headers = {'Content-type': contentType}

conn = http.client.HTTPConnection('http://...')
req = conn.request('POST', '/test/', body, headers)

print(conn.getresponse().read())

这可以发送文本。

有两个问题:这只是文本,整个文本文件必须作为一个巨大的字符串存储在内存中。

如何上传任何二进制文件?是否有一种方法可以在不将整个文件读入内存的情况下执行此操作?


1
你真的应该使用你已经提到的 requests 包。标准库没有处理的很多东西,比如会话管理、身份验证、SSL 证书检查等等。你不使用 request 模块的原因是什么? - Achim
你可以直接从requests模块中复制出你所需的代码。 - User
1
当然这是你的决定。但如果我可以选择打包一个高质量、经过充分测试、"任务证明"的库或者使用自己的自定义代码,我总是会选择前者。;-) - Achim
3个回答

3

看一下我翻译成Python3的小型Doug Hellmann的urllib2

我使用它的方式如下:

import urllib.request
import urllib.parse
from lib.multipart_sender import MultiPartForm

myfile = open('path/to/file', 'rb')
form = MultiPartForm()
form.add_field('token', apipost[mycgi['domain']]._token)
form.add_field('domain', mycgi['domain'])
form.add_file('file', 'logo.jpg', fileHandle=myfile)
form.make_result()

url = 'http://myurl'
req1 = urllib.request.Request(url)
req1.add_header('Content-type', form.get_content_type())
req1.add_header('Content-length', len(form.form_data))
req1.add_data(form.form_data)
fp = urllib.request.urlopen(req1)
print(fp.read()) # to view status

2
谢谢,但是想法是使用标准库。 - William

2
我看过这个模块。
class HTTPConnection:
    # ...
    def send(self, data): # line 820
        """Send `data' to the server.
        ``data`` can be a string object, a bytes object, an array object, a
        file-like object that supports a .read() method, or an iterable object.
        """

数据就是主体。

您可以像这样传递迭代器:(我没有尝试过)

def body():
  for fileName in fileList:
    # Add boundary and header
    yield('--' + boundary) + '\r\n'
    yield('Content-Disposition: form-data; name={0}; filename=    {0}'.format(fileName)) + '\r\n'

    fileType = mimetypes.guess_type(fileName)[0] or 'application/octet-stream'
    yield('Content-Type: {}'.format(fileType)) + '\r\n'
    yield('\r\n')

    with open(fileName) as f: 
        # Bad for large files
        yield f.read()
    yield('--'+boundary+'--') + '\r\n'
    yield('') + '\r\n'

这是一个好主意,在3.2或3.3中进行一些修改后可以实现。如果在HTTPConnection.requestHTTPConnection.send中给定了可迭代对象,则还必须指定内容长度标头。 然而,我正在使用Python 3.1。也许这是不可行的。 - William
你在使用Python 3.1时遇到了什么问题? - User
在3.1中,body不能是一个可迭代对象。实际上,我们已经不再使用3.1,所以这个回答解决了我的问题。 - William

-1

您可以使用unirest进行调用。示例代码:

import unirest

# consume async post request
def consumePOSTRequestSync():
 params = {'test1':'param1','test2':'param2'}

 # we need to pass a dummy variable which is open method
 # actually unirest does not provide variable to shift between
 # application-x-www-form-urlencoded and
 # multipart/form-data
 params['dummy'] = open('dummy.txt', 'r')
 url = 'http://httpbin.org/post'
 headers = {"Accept": "application/json"}
 # call get service with headers and params
 response = unirest.post(url, headers = headers,params = params)
 print "code:"+ str(response.code)
 print "******************"
 print "headers:"+ str(response.headers)
 print "******************"
 print "body:"+ str(response.body)
 print "******************"
 print "raw_body:"+ str(response.raw_body)

# post sync request multipart/form-data
consumePOSTRequestSync()

查看博客文章以获取更多细节http://stackandqueue.com/?p=57


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接