Python POST二进制数据

发布于 2021-01-29 18:17:21

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

我正在尝试模仿这里的命令:

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)
关注者
0
被浏览
160
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

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

    顺便说一句,我强烈建议在Python中使用非常好的和非常受欢迎的HTTP请求库。它比标准库(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
    

    更新

    为了弄清楚为什么它适用于请求而不适用于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标头并根据其值改变行为的情况并不少见。尝试一个接一个地更改请求发送的标头,使其与urllib2发送的标头相同,然后查看何时停止工作。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看