【问题标题】:Using MultipartPostHandler to POST form-data with Python使用 MultipartPostHandler 通过 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。知道为什么没有安装和使用开启器吗?

【问题讨论】:

    标签: python file upload urllib2 multipartform-data


    【解决方案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())
      

      【讨论】:

      猜你喜欢
      • 2023-02-18
      • 1970-01-01
      • 2015-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-29
      • 2021-03-30
      • 2012-10-23
      相关资源
      最近更新 更多