Python: 以未编码的二进制数据进行HTTP PUT请求

11

我无论如何都无法想出如何在 Python 2.7 中使用标准 Python 库执行带有逐字二进制数据的 HTTP PUT 请求。

我曾尝试使用 urllib2,但是这失败了,因为urllib2.Request期望数据以application/x-www-form-urlencoded格式提供。我不想对二进制数据进行编码,我只想在包含头信息的情况下逐字传输它。

Content-Type: application/octet-stream
Content-Length: (whatever my binary data length is)

这似乎很简单,但我一直在打转,似乎无法弄清楚如何做到。

除了打开原始二进制套接字并写入它之外,我该怎么做呢?

4个回答

12
我找到了问题所在。似乎在urllib2.Request / urllib2.urlopen()中存在一些模糊的行为(至少在Python 2.7中)。 urllib2.Request(url, data, headers)构造函数似乎期望其url和data参数具有相同类型的字符串。
我正在将来自read()调用的原始数据作为data参数提供(在Python 2.7中以“普通”字符串形式返回),但我的url意外地是Unicode,因为我将另一个返回Unicode字符串的函数的一部分URL与其连接起来。
它试图将data参数从Unicode ->普通字符串进行“降级”,但实际上它尝试将url升级为Unicode,并给出了编解码器错误。(奇怪的是,这发生在urllib2.urlopen()函数调用而不是urllib2.Request构造函数中)。
当我将函数调用更改为
# headers contains `{'Content-Type': 'application/octet-stream'}`
r = urllib2.Request(url.encode('utf-8'), data, headers)

它正常运行。


我今天刚好遇到了requests库,将来你可能会想要使用它。 - Spencer Rathbun

9
您误读了文档:urllib2.Request 期望数据已经被编码,对于POST请求通常意味着 application/x-www-form-urlencoded 格式。您可以自由地关联任何其他的二进制数据,例如:
import urllib2

data = b'binary-data'
r = urllib2.Request('http://example.net/put', data,
                    {'Content-Type': 'application/octet-stream'})
r.get_method = lambda: 'PUT'
urllib2.urlopen(r)

这将生成你想要的请求:
PUT /put HTTP/1.1
Accept-Encoding: identity
Content-Length: 11
Host: example.net
Content-Type: application/octet-stream
Connection: close
User-Agent: Python-urllib/2.7

binary-data

1
但是我得到的是UnicodeDecodeError: 'ascii'编解码器无法解码位置0处的字节0xc2:序数不在范围内(128) - Jason S
顺便问一下,你是怎么从urllib2发送的原始请求中获取到的呢? - Jason S
@JasonS 这是因为你的数据是一个字符串(一个unicode对象),而不是一个bytes对象。使用encode将其编码为正确的编码格式。在Python 3中编写程序,然后再将其移植到2.x版本中可能会有所帮助。 - phihag
@JasonS 抱歉,我不明白你的问题。我在答案中使用的代码就在那里。请注意b'binary-data'前面的b,它使其成为一个bytes字面量(虽然在2.x中没有影响)。你能否发布引发UnicodeDecodeError的代码? - phihag
我会尝试发布一个测试用例。为了记录,我没有误读文档。我理解urllib2.Request的使用方法;这在我的其他问题中已经很清楚了。我的问题是如何使用任何Python库来PUT未编码的二进制数据。 - Jason S
@JasonS 当然,这取决于你使用的库,但是使用urllib(2),你只需将Request的“data”参数设置为“bytes”对象(=二进制数据)。 你的问题在于你有一个字符串(unicode),需要将其转换为“bytes”(在Python 2.x中与“str”混淆,尽管名称相同,但它不是一个字符串)。 - phihag

4

你考虑过/尝试使用httplib吗?

HTTPConnection.request(method, url[, body[, headers]])

这将使用HTTP请求方法method和选择器url向服务器发送请求。如果存在body参数,则应该是一个在headers完成后发送的数据字符串。或者,它可以是一个打开的文件对象,在这种情况下,发送文件的内容;此文件对象应支持fileno()和read()方法。标题Content-Length自动设置为正确值。headers参数应该是要随请求发送的额外HTTP头的映射。


你考虑过/尝试使用httplib吗?-- 是的。不行,它会在非ASCII数据上出错。 - Jason S
1
嗯...打开文件对象,这可能会有所帮助。 - Jason S

1

以下代码片段可以帮助我上传图片:

适用于HTTPS网站。如果您不需要HTTPS,请使用httplib.HTTPConnection(URL)。

import httplib
import ssl
API_URL="api-mysight.com"
TOKEN="myDummyToken"
IMAGE_FILE="myimage.jpg"
imageID="myImageID"
URL_PATH_2_USE="/My/image/" + imageID +"?objectId=AAA"
headers = {"Content-Type":"application/octet-stream", "X-Access-Token": TOKEN}
imgData = open(IMAGE_FILE, "rb")
REQUEST="PUT"
conn = httplib.HTTPSConnection(API_URL, context=ssl.SSLContext(ssl.PROTOCOL_TLSv1))
conn.request(REQUEST, URL_PATH_2_USE, imgData, headers)
response = conn.getresponse()
result = response.read()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接