Python POST 二进制数据
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14365027/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Python POST binary data
提问by Mac
I am writing some code to interface with redmine and I need to upload some files as part of the process, but I am not sure how to do a POST request from python containing a binary file.
我正在编写一些代码来与 redmine 交互,我需要上传一些文件作为该过程的一部分,但我不确定如何从包含二进制文件的 python 执行 POST 请求。
I am trying to mimic the commands here:
我试图模仿这里的命令:
curl --data-binary "@image.png" -H "Content-Type: application/octet-stream" -X POST -u login:password http://redmine/uploads.xml
In python (below), but it does not seem to work. I am not sure if the problem is somehow related to encoding the file or if something is wrong with the headers.
在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
I have access to the server and it looks like a encoding error:
我可以访问服务器,它看起来像一个编码错误:
...
invalid byte sequence in UTF-8
Line: 1
Position: 624
Last 80 unconsumed characters:
7z?ˉ'?DD·2^???4g?R<süeí6k?¤a?!?=}jcdjSPúá-o#??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)
采纳答案by Piotr Dobrogost
Basically what you do is correct. Looking at redmine docs you linked to, it seems that suffix after the dot in the url denotes type of posted data (.json for JSON, .xml for XML), which agrees with the response you get - Processing by AttachmentsController#upload as XML. I guess maybe there's a bug in docs and to post binary data you should try using http://redmine/uploadsurl instead of http://redmine/uploads.xml.
基本上你所做的都是正确的。查看您链接到的 redmine 文档,url 中点之后的后缀似乎表示发布数据的类型(JSON 为 .json,XML 为 .xml),这与您得到的响应一致 - Processing by AttachmentsController#upload as XML。我想文档中可能存在错误,要发布二进制数据,您应该尝试使用http://redmine/uploadsurl 而不是http://redmine/uploads.xml.
Btw, I highly recommend very good and very popular Requestslibrary for http in Python. It's much better than what's in the standard lib (urllib2). It supports authentication as well but I skipped it for brevity here.
顺便说一句,我强烈推荐非常好的和非常流行的 Python 中的 http请求库。它比标准库(urllib2)中的要好得多。它也支持身份验证,但为了简洁起见我在这里跳过了它。
import requests
data = open('./x.png', 'rb').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
UPDATE
更新
To find out why this works with Requests but not with urllib2 we have to examine the difference in what's being sent. To see this I'm sending traffic to http proxy (Fiddler) running on port 8888:
要找出为什么这适用于 Requests 而不适用于 urllib2,我们必须检查发送内容的差异。为了看到这一点,我将流量发送到在端口 8888 上运行的 http 代理(Fiddler):
Using Requests
使用请求
import requests
data = 'test data'
res = requests.post(url='http://localhost:8888',
data=data,
headers={'Content-Type': 'application/octet-stream'})
we see
我们看
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
and using urllib2
并使用 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)
we get
我们得到
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
I don't see any differences which would warrant different behavior you observe. Having said that it's not uncommon for http servers to inspect User-Agentheader and vary behavior based on its value. Try to change headers sent by Requests one by one making them the same as those being sent by urllib2 and see when it stops working.
我没有看到任何可以保证您观察到不同行为的差异。话虽如此,http 服务器检查User-Agent标头并根据其值改变行为的情况并不少见。尝试一一更改请求发送的标头,使它们与 urllib2 发送的标头相同,并查看它何时停止工作。
回答by mrkafk
you need to add Content-Disposition header, smth like this (although I used mod-python here, but principle should be the same):
你需要添加Content-Disposition header,像这样(虽然我这里用的是mod-python,但原理应该是一样的):
request.headers_out['Content-Disposition'] = 'attachment; filename=%s' % myfname
回答by Josh Liptzin
This has nothing to do with a malformed upload. The HTTP error clearly specifies 401 unauthorized, and tells you the CSRF token is invalid. Try sending a valid CSRF token with the upload.
这与格式错误的上传无关。HTTP 错误明确指出 401 未授权,并告诉您 CSRF 令牌无效。尝试在上传时发送有效的 CSRF 令牌。
More about csrf tokens here:
更多关于 csrf 令牌的信息:
What is a CSRF token ? What is its importance and how does it work?
回答by gvir
You can use unirest, It provides easy method to post request. `
您可以使用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()
`
`
You can check complete example at http://stackandqueue.com/?p=57
您可以在http://stackandqueue.com/?p=57查看完整示例

