在Python中使用MultipartPostHandler发送form-data
问题:当使用Python的urllib2发送数据时,所有数据都会被编码成URL格式,并以Content-Type: application/x-www-form-urlencoded的形式发送。但是在上传文件时,Content-Type应该设置为multipart/form-data,并且内容需要进行MIME编码。
为了绕过这个限制,一些聪明的程序员创建了一个叫做MultipartPostHandler的库,这个库可以和urllib2一起使用,帮助你大部分自动地以multipart/form-data的格式发送POST请求。这个库的一个副本在这里:MultipartPostHandler doesn't work for Unicode files
我刚开始学Python,无法让这个库正常工作。我写了基本上如下的代码。当我在本地的HTTP代理中捕获数据时,我发现数据仍然是URL编码的,而不是多部分的MIME编码。请帮我找出我哪里做错了,或者有没有更好的方法来完成这个任务。谢谢:-)
FROM_ADDR = 'my@email.com'
try:
data = open(file, 'rb').read()
except:
print "Error: could not open file %s for reading" % file
print "Check permissions on the file or folder it resides in"
sys.exit(1)
# Build the POST request
url = "http://somedomain.com/?action=analyze"
post_data = {}
post_data['analysisType'] = 'file'
post_data['executable'] = data
post_data['notification'] = 'email'
post_data['email'] = FROM_ADDR
# MIME encode the POST payload
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
urllib2.install_opener(opener)
request = urllib2.Request(url, post_data)
request.set_proxy('127.0.0.1:8080', 'http') # For testing with Burp Proxy
# Make the request and capture the response
try:
response = urllib2.urlopen(request)
print response.geturl()
except urllib2.URLError, e:
print "File upload failed..."
编辑1:谢谢你的回复。我知道ActiveState的httplib解决方案(我在上面链接了它)。我更希望能简化这个问题,使用尽量少的代码继续使用我之前的urllib2。你知道为什么这个opener没有被安装和使用吗?
6 个回答
只需使用 python-requests,它会为你设置好合适的头信息,并帮你上传文件:
import requests
files = {"form_input_field_name": open("filename", "rb")}
requests.post("http://httpbin.org/post", files=files)
找到了一个直接使用 httplib
来发送多部分数据的做法(不需要任何外部库)
import httplib
import mimetypes
def post_multipart(host, selector, fields, files):
content_type, body = encode_multipart_formdata(fields, files)
h = httplib.HTTP(host)
h.putrequest('POST', selector)
h.putheader('content-type', content_type)
h.putheader('content-length', str(len(body)))
h.endheaders()
h.send(body)
errcode, errmsg, headers = h.getreply()
return h.file.read()
def encode_multipart_formdata(fields, files):
LIMIT = '----------lImIt_of_THE_fIle_eW_$'
CRLF = '\r\n'
L = []
for (key, value) in fields:
L.append('--' + LIMIT)
L.append('Content-Disposition: form-data; name="%s"' % key)
L.append('')
L.append(value)
for (key, filename, value) in files:
L.append('--' + LIMIT)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append('Content-Type: %s' % get_content_type(filename))
L.append('')
L.append(value)
L.append('--' + LIMIT + '--')
L.append('')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=%s' % LIMIT
return content_type, body
def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
看起来,解决这个问题最简单、最兼容的方法就是使用“poster”这个模块。
# test_client.py
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers
import urllib2
# Register the streaming http handlers with urllib2
register_openers()
# Start the multipart/form-data encoding of the file "DSC0001.jpg"
# "image1" is the name of the parameter, which is normally set
# via the "name" parameter of the HTML <input> tag.
# headers contains the necessary Content-Type and Content-Length
# datagen is a generator object that yields the encoded parameters
datagen, headers = multipart_encode({"image1": open("DSC0001.jpg")})
# Create the Request object
request = urllib2.Request("http://localhost:5000/upload_image", datagen, headers)
# Actually do the request, and get the response
print urllib2.urlopen(request).read()
这个方法效果很好,我不需要去折腾 httplib。这个模块可以在这里找到: http://atlee.ca/software/poster/index.html