Python POST 二进制数据

我正在编写一些与 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)
166782 次浏览

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

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

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

顺便说一下,我强烈推荐非常好的、非常流行的用于 Python 中的 http 的 请求库。它比标准 lib (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

更新

为了找出为什么这种方法适用于 Request 而不适用于 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头并根据其值改变行为并不罕见。尝试一个一个地更改由 Request 发送的消息头,使其与由 urllib2发送的消息头相同,并查看它何时停止工作。

这和上传错误没有关系。HTTP 错误清楚地指定了401未经授权,并告诉您 CSRF 令牌无效。尝试在上传时发送一个有效的 CSRF 令牌。

更多关于 csrf 令牌的信息请点击这里:

什么是 CSRF 令牌? 它的重要性是什么? 它是如何工作的?

您可以使用 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()