使用 MultipartPostHandler 通过 Python 发布表单数据
Posted
技术标签:
【中文标题】使用 MultipartPostHandler 通过 Python 发布表单数据【英文标题】:Using MultipartPostHandler to POST form-data with Python 【发布时间】:2010-10-15 09:12:43 【问题描述】:问题:当使用 Python 的 urllib2 发布数据时,所有数据都经过 URL 编码并作为 Content-Type: application/x-www-form-urlencoded 发送。上传文件时,Content-Type 应该设置为 multipart/form-data 并且内容是 MIME 编码的。
为了解决这个限制,一些敏锐的编码人员创建了一个名为 MultipartPostHandler 的库,它创建了一个 OpenerDirector,您可以将其与 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..."
EDIT1:感谢您的回复。我知道 ActiveState httplib 解决方案(我在上面链接到它)。我宁愿抽象出问题并使用最少的代码来继续使用 urllib2。知道为什么没有安装和使用开启器吗?
【问题讨论】:
【参考方案1】:发现这个配方可以直接使用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'
【讨论】:
我认为这种方法更“独立”,因为它不需要新模块并且可以使用 py2exe“编译”以在 Windows 上运行,作为 *.exe 文件。添加“海报”模块也可以,但对我不起作用。这种方法最适合我。祝作者万岁! 文件“值”是否需要以某种方式编码,还是只是纯字节流? @garmoncheg 有人可以解释一下这个解决方案吗?【参考方案2】:似乎解决这个问题的最简单和最兼容的方法是使用“海报”模块。
# 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
【讨论】:
这正是我所需要的!荣誉。 我知道这是一篇旧帖子,但我是从海报中得到的:AttributeError: multipart_yielder instance has no attribute '__len__'
想知道是否还有其他人有这个问题。
@nalroff 你没有打电话给poster.streaminghttp.register_openers()
按原样使用时出现异常(TypeError:必须是 str,而不是生成器),我通过 ''.join(datagen) 修复了它
看起来不错,但如何从表单提交其他数据?比如我有auth token,文件描述、文件删除日期等参数很少。【参考方案3】:
只需使用python-requests,它会设置正确的标题并为您上传:
import requests
files = "form_input_field_name": open("filename", "rb")
requests.post("http://httpbin.org/post", files=files)
【讨论】:
指定html输入字段的名称很关键!谢谢【参考方案4】:真是巧合,我在 2 年零 6 个月前创建了这个项目
https://pypi.python.org/pypi/MultipartPostHandler2,修复了 utf-8 系统的 MultipartPostHandler。我也做了一些小改进,欢迎大家测试:)
【讨论】:
嘿,兄弟!我认为您选择的包名称让我不检查它。 抱歉没看懂,不能修改MultipartPostHandler,所以只好叫MultipartPostHandler2 pypi 支持每个包多个版本,如果名称已经被占用,您应该选择另一个好的包名。 Pypi 是我们的。我们都对我们用它做的事情负责 是的,我选择 MultipartPostHandler2 ,因为我没有访问 MultipartPostHandler 来修复它,MultipartPostHandler2 是 MultipartPostHandler 的第二个版本,代码相同,只是做了一些修复。【参考方案5】:我遇到了同样的问题,我需要在不使用外部库的情况下进行多部分表单发布。我写了一个完整的blogpost about the issues I ran into。
我最终使用了 http://code.activestate.com/recipes/146306/ 的修改版本。该 url 中的代码实际上只是将文件的内容附加为字符串,这可能会导致二进制文件出现问题。这是我的工作代码。
import mimetools
import mimetypes
import io
import http
import json
form = MultiPartForm()
form.add_field("form_field", "my awesome data")
# Add a fake file
form.add_file(key, os.path.basename(filepath),
fileHandle=codecs.open("/path/to/my/file.zip", "rb"))
# Build the request
url = "http://www.example.com/endpoint"
schema, netloc, url, params, query, fragments = urlparse.urlparse(url)
try:
form_buffer = form.get_binary().getvalue()
http = httplib.HTTPConnection(netloc)
http.connect()
http.putrequest("POST", url)
http.putheader('Content-type',form.get_content_type())
http.putheader('Content-length', str(len(form_buffer)))
http.endheaders()
http.send(form_buffer)
except socket.error, e:
raise SystemExit(1)
r = http.getresponse()
if r.status == 200:
return json.loads(r.read())
else:
print('Upload failed (%s): %s' % (r.status, r.reason))
class MultiPartForm(object):
"""Accumulate the data to be used when posting a form."""
def __init__(self):
self.form_fields = []
self.files = []
self.boundary = mimetools.choose_boundary()
return
def get_content_type(self):
return 'multipart/form-data; boundary=%s' % self.boundary
def add_field(self, name, value):
"""Add a simple field to the form data."""
self.form_fields.append((name, value))
return
def add_file(self, fieldname, filename, fileHandle, mimetype=None):
"""Add a file to be uploaded."""
body = fileHandle.read()
if mimetype is None:
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
self.files.append((fieldname, filename, mimetype, body))
return
def get_binary(self):
"""Return a binary buffer containing the form data, including attached files."""
part_boundary = '--' + self.boundary
binary = io.BytesIO()
needsCLRF = False
# Add the form fields
for name, value in self.form_fields:
if needsCLRF:
binary.write('\r\n')
needsCLRF = True
block = [part_boundary,
'Content-Disposition: form-data; name="%s"' % name,
'',
value
]
binary.write('\r\n'.join(block))
# Add the files to upload
for field_name, filename, content_type, body in self.files:
if needsCLRF:
binary.write('\r\n')
needsCLRF = True
block = [part_boundary,
str('Content-Disposition: file; name="%s"; filename="%s"' % \
(field_name, filename)),
'Content-Type: %s' % content_type,
''
]
binary.write('\r\n'.join(block))
binary.write('\r\n')
binary.write(body)
# add closing boundary marker,
binary.write('\r\n--' + self.boundary + '--\r\n')
return binary
【讨论】:
【参考方案6】:为了回答 OP 关于为什么原始代码不起作用的问题,传入的处理程序不是类的实例。线
# MIME encode the POST payload
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
应该阅读
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler())
【讨论】:
如果您查看原始源代码:pipe.rcc.fsu.edu/PostHandler/MultipartPostHandler.py,您将看到如何使用该库的示例。以上是关于使用 MultipartPostHandler 通过 Python 发布表单数据的主要内容,如果未能解决你的问题,请参考以下文章