【问题标题】:Python POST binary dataPython POST 二进制数据
【发布时间】:2012-12-31 03:54:06
【问题描述】:

我正在编写一些代码来与 redmine 交互,我需要上传一些文件作为该过程的一部分,但我不确定如何从包含二进制文件的 python 发出 POST 请求。

我正在尝试模仿命令here:

curl --data-binary "@image.png" -H "Content-Type: application/octet-stream" -X POST -u login:password http://redmine/uploads.xml

在python(下)中,但它似乎不起作用。我不确定问题是否与文件编码有关,或者标题是否有问题。

import urllib2, os

FilePath = "C:\somefolder\somefile.7z"
FileData = open(FilePath, "rb")
length = os.path.getsize(FilePath)

password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, 'http://redmine/', 'admin', 'admin')
auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)
request = urllib2.Request( r'http://redmine/uploads.xml', FileData)
request.add_header('Content-Length', '%d' % length)
request.add_header('Content-Type', 'application/octet-stream')
try:
    response = urllib2.urlopen( request)
    print response.read()
except urllib2.HTTPError as e:
    error_message = e.read()
    print error_message

我可以访问服务器,但它看起来像一个编码错误:

...
invalid byte sequence in UTF-8
Line: 1
Position: 624
Last 80 unconsumed characters:
7z¼¯'ÅÐз2^Ôøë4g¸R<süðí6kĤª¶!»=}jcdjSPúá-º#»ÄAtD»H7Ê!æ½]j):

(further down)

Started POST "/uploads.xml" for 192.168.0.117 at 2013-01-16 09:57:49 -0800
Processing by AttachmentsController#upload as XML
WARNING: Can't verify CSRF token authenticity
  Current user: anonymous
Filter chain halted as :authorize_global rendered or redirected
Completed 401 Unauthorized in 13ms (ActiveRecord: 3.1ms)

【问题讨论】:

    标签: python rest post urllib2 redmine


    【解决方案1】:

    基本上你所做的是正确的。查看您链接到的 redmine 文档,似乎 url 中点后的后缀表示发布数据的类型(.json 表示 JSON,.xml 表示 XML),这与您得到的响应一致 - Processing by AttachmentsController#upload as XML。我想也许文档中存在错误,要发布二进制数据,您应该尝试使用 http://redmine/uploads url 而不是 http://redmine/uploads.xml

    顺便说一句,我强烈推荐 Python 中用于 http 的非常好的和非常流行的 Requests 库。它比标准库(urllib2)中的要好得多。它也支持身份验证,但为了简洁起见,我在这里跳过了它。

    import requests
    with open('./x.png', 'rb') as f:
        data = f.read()
    res = requests.post(url='http://httpbin.org/post',
                        data=data,
                        headers={'Content-Type': 'application/octet-stream'})
    
    # let's check if what we sent is what we intended to send...
    import json
    import base64
    
    assert base64.b64decode(res.json()['data'][len('data:application/octet-stream;base64,'):]) == data
    

    更新

    要找出为什么这适用于 Requests 但不适用于 urllib2,我们必须检查发送内容的差异。为了看到这一点,我将流量发送到在端口 8888 上运行的 http 代理 (Fiddler):

    使用请求

    import requests
    
    data = 'test data'
    res = requests.post(url='http://localhost:8888',
                        data=data,
                        headers={'Content-Type': 'application/octet-stream'})
    

    我们看到

    POST http://localhost:8888/ HTTP/1.1
    Host: localhost:8888
    Content-Length: 9
    Content-Type: application/octet-stream
    Accept-Encoding: gzip, deflate, compress
    Accept: */*
    User-Agent: python-requests/1.0.4 CPython/2.7.3 Windows/Vista
    
    test data
    

    并使用 urllib2

    import urllib2
    
    data = 'test data'    
    req = urllib2.Request('http://localhost:8888', data)
    req.add_header('Content-Length', '%d' % len(data))
    req.add_header('Content-Type', 'application/octet-stream')
    res = urllib2.urlopen(req)
    

    我们得到

    POST http://localhost:8888/ HTTP/1.1
    Accept-Encoding: identity
    Content-Length: 9
    Host: localhost:8888
    Content-Type: application/octet-stream
    Connection: close
    User-Agent: Python-urllib/2.7
    
    test data
    

    我看不出有任何差异可以保证您观察到不同的行为。话虽如此,http 服务器检查 User-Agent 标头并根据其值改变行为并不罕见。尝试一一更改 Requests 发送的 headers,使其与 urllib2 发送的 headers 相同,看看它何时停止工作。

    【讨论】:

    • 不知道为什么,但是使用 requests 模块,完全相同的代码可以正常工作...非常感谢。虽然,现在我很想知道为什么 urllib 不起作用...
    • 有请求的请看这里:stackoverflow.com/questions/12385179/…
    【解决方案2】:

    这与格式错误的上传无关。 HTTP 错误明确指出 401 未授权,并告诉您 CSRF 令牌无效。尝试在上传时发送有效的 CSRF 令牌。

    更多关于 csrf 令牌的信息:

    What is a CSRF token ? What is its importance and how does it work?

    【讨论】:

      【解决方案3】:

      你需要添加Content-Disposition头,像这样(虽然我在这里使用了mod-python,但原理应该是一样的):

      request.headers_out['Content-Disposition'] = 'attachment; filename=%s' % myfname
      

      【讨论】:

      • curl 不需要,为什么 python 需要?
      • 我认为 curl 正在默默地做这件事,尽管我不会在这方面打赌 - 你的实际选择是使用 Wireshark 并简单地查看 curl 和服务器之间的线路上运行的内容(这并不容易但是,要在 localhost 上使用 wireshark,你必须有单独的机器)。
      • 哎呀,显然 curl 使用了 urlencoded 格式,至少在我的小文件 pastie.org/5704526 上是这样。这是我没有想到的另一个选择。
      • 我只发现上面使用了wireshark(你用了什么?)我敦促你做同样的事情,因为所有其他跟踪工具都会解释(阅读:扭曲)真正发送和接收的内容。在我的情况下,我欠你更正 - 显然我使用的工具为 POST 的内容创建了多部分 MIME 消息:pastie.org/5703946。这就是 Content-Disposition 显然属于的地方。
      • P.S.我使用了curl --data-binary "@users.csv" -b cookie.txt -X POST http://myhost/site.py 之类的命令,wireshark 说它是 HTTP/POST,所以我认为 curl 确实使用了 POST,但它使用了带有数据包内容的 urlencoded 文件,就像我在上面评论中链接的第一个馅饼一样。
      【解决方案4】:

      您可以使用unirest,它提供了简单的方法来发布请求。 `

      import unirest
       
      def callback(response):
       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)
       
      # consume async post request
      def consumePOSTRequestASync():
       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
       unirest.post(url, headers = headers,params = params, callback = callback)
       
       
      # post async request multipart/form-data
      consumePOSTRequestASync()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-28
        • 2010-12-08
        • 2013-12-20
        • 2014-11-12
        相关资源
        最近更新 更多