在Python中使用MultipartPostHandler发送form-data

51 投票
6 回答
69551 浏览
提问于 2025-04-15 11:03

问题:当使用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 个回答

32

只需使用 python-requests,它会为你设置好合适的头信息,并帮你上传文件:

import requests 
files = {"form_input_field_name": open("filename", "rb")}
requests.post("http://httpbin.org/post", files=files)
42

找到了一个直接使用 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'
61

看起来,解决这个问题最简单、最兼容的方法就是使用“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

撰写回答