Python: HTTP PUT с некодированными двоичными данными

Я не могу на всю жизнь выяснить, как выполнить HTTP-запрос PUT с дословными двоичными данными в Python 2.7 со стандартными библиотеками Python.

Я думал, что смогу сделать это с помощью urllib2, но не работает, потому что urllib2.Request ожидает его данные в формате application/x-www-form-urlencoded. Я не хочу кодировать двоичные данные, я просто хочу передать его дословно, после заголовков, которые включают

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

Это кажется таким простым, но я продолжаю крутиться и не могу понять, как это сделать.

Как я могу это сделать? (помимо открытия сырого двоичного сокета и записи на него)

Ответ 1

Я выяснил свою проблему. Кажется, что есть неясное поведение в urllib2.Request/urllib2.urlopen() (по крайней мере, в Python 2.7)

Конструктор urllib2.Request(url, data, headers), кажется, ожидает, что тот же тип строки будет отображаться в параметрах url и data.

Я передавал исходные данные параметров данных из вызова read() файла (который в Python 2.7 возвращает его в форме "простой" строки), но мой url был случайно Unicode, потому что я конкатенировал часть URL-адреса из результата другой функции, которая возвращает строки Unicode.

Вместо того, чтобы пытаться "downcast" url из Unicode → простых строк, он пытался "ускорить" параметр data в Unicode, и это дало мне ошибку кодека. (как это ни странно, это происходит при вызове функции urllib2.urlopen(), а не в конструкторе urllib2.Request)

Когда я изменил свой вызов функции на

# headers contains `{'Content-Type': 'application/octet-stream'}`
r = urllib2.Request(url.encode('utf-8'), data, headers)

он работал нормально.

Ответ 2

Вы неправильно читаете документацию: 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

Ответ 3

Рассматривали/пытались использовать httplib?

HTTPConnection.request(метод, url [, body [, headers]])

Это отправит запрос на сервер с использованием метода HTTP-запроса метод и URL-адрес селектора. Если аргумент body присутствует, он должна быть строка данных для отправки после завершения заголовков. В качестве альтернативы, это может быть объект открытого файла, и в этом случае содержимое файла отправляется; этот файл должен поддерживать fileno() и read(). Заголовок Content-Length автоматически устанавливается на правильное значение. Аргумент заголовков должен быть отображением дополнительных HTTP-заголовки для отправки с запросом.

Ответ 4

Этот снимок сработал у меня, чтобы PUT изображение:

на сайте 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()