Моя цель - создать HTTP-запрос (заголовки и тело) вручную . Это должно выглядеть так:

Some-Header1: some value1
Some-Header2: some value2
Some-Header3: some value3

-------------MyBoundary
Content-Disposition: form-data; name="file_content_0"; filename="123.pdf"
Content-Length: 93
Content-Type: application/pdf
Content-Transfer-Encoding: binary

  ==== here is the binary data of 123.pdf ====
  ==== here is the binary data of 123.pdf ====
  ==== here is the binary data of 123.pdf ====
  ==== here is the binary data of 123.pdf ====

-------------MyBoundary--

Я обнаружил, что это единственный способ отправить файл в веб-службу через ее API, потому что я обнюхивал трафик скрипта в Ruby, выполняя это, и оказалось, что это похоже на то, что я показал выше.

Таким образом, заголовки, такие как Some-Header1 и другие - это заголовки в виде простого текста. Обратите внимание, что есть также "-------------MyBoundary--" после "==== here is the binary data of 123.pdf ===="

Но «==== here is the binary data of 123.pdf ====» - это двоичные данные.

Вопрос в том, как связать (объединить) простые текстовые данные с двоичными данными?

P.S. Я пытался добиться этого с помощью стандартных библиотек, таких как python-запросы, и потерпел неудачу. Я не собираюсь использовать их снова на данном этапе. На данный момент мне нужно только знать, как объединить простой текст и двоичные данные.

ОБНОВЛЕНИЕ :

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

import textwrap

body_headers = textwrap.dedent(
    """
    -------------MyBoundary
    Content-Disposition: form-data; name="file_content_0"; filename="a.c"
    Content-Length: 1234
    Content-Type: image/jpeg
    Content-Transfer-Encoding: binary

                    %b ??? -> to indicate that a binary data will be placed here

    -------------MyBoundary--


    """
) % binary_data" #???

ОБНОВЛЕНИЕ2 :

text1 = textwrap.dedent(
    """
    -------------MyBoundary
    Content-Disposition: form-data; name="file_content_0"; filename="a.pdf"
    Content-Length: 1234
    Content-Type: image/jpeg
    Content-Transfer-Encoding: binary

    replace_me

    -------------MyBoundary--


    """
)

with open("test1.pdf", "rb") as file_hander:
    binary_data = file_hander.read()

print (isinstance(binary_data, str)) # True
print (isinstance("replace_me", str)) # True

print text1.replace("replace_me", binary_data) # --> [Decode error - output not utf-8]

print text1.replace("replace_me", binary_data).encode("utf-8") # exception

Ошибка:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 195: ordinal not in range(128)

И это тоже дает мне исключение:

print unicode(text1.replace("replace_me", binary_data), "utf-8")
# UnicodeDecodeError: 'utf8' codec can't decode byte 0xc4 in position 195: invalid continuation byte
1
Incerteza 20 Авг 2014 в 10:48
@hlt, я только что сказал тебе, с какой проблемой я столкнулся. Код? str1 = заголовки; str_total = str1 + open (имя_файла, «rb»). -> не компилируется.
 – 
Incerteza
20 Авг 2014 в 11:12
1
Спасибо. (Пожалуйста, добавьте это в сообщение, код в комментариях трудно найти)
 – 
hlt
20 Авг 2014 в 11:13

1 ответ

Лучший ответ

Чтобы загрузить двоичные данные из файла, вы должны сделать

with open(file_name, 'rb') as the_file:
    binary_data = the_file.read()

Теперь есть два сценария, в зависимости от вашей версии Python:

Python 2 - unicode и str

binary_data будет str, конкатенация должна работать отлично, если другая ваша строка не будет unicode, и в этом случае вам, вероятно, следует ее кодировать (почти никакая сетевая функция не требует unicode в Python 2):

normal_str = unicode_str.encode(encoding)

Где encoding обычно означает что-то вроде "utf-8", "utf-16" или "latin-1", но это может быть более экзотично.

Python 3 - str и bytes

binary_data будет объектом bytes, который нельзя просто объединить с str по умолчанию. Если все, что вы используете для отправки данных, требует bytes, вы следуете тому же подходу к кодированию, что и в Python 2. Если для этого требуется str (что для сетевых целей, вероятно, маловероятно), вы должны декодировать заданную кодировку (так как это почти невозможно угадать, вы должны проверить, какую кодировку использует ваш файл), используя

normal_str = byte_str.decode(encoding)

Снова передача кодировки в качестве аргумента ( подсказка : "latin-1" должна быть в порядке, поскольку она сохраняет байты, в то время как другие, такие как "utf-8", могут не работать с фактическими двоичными данными (которые не закодированные строки) [ HT to @SergeBallesta < / a> ])

Чтобы избежать подобных проблем в Python 3 , вы можете с самого начала определить свои заголовки как bytes, используя something = b"whatever" вместо something = "whatever" ( обратите внимание на добавленный b ) и откройте другие входные файлы в заголовке как двоичные. Тогда простое объединение строк с помощью + не должно быть проблемой.

Отправка HTTP-запроса

Чтобы отправить такие необработанные данные на сервер, у вас есть разные варианты:

КПД

Если вы выбираете необработанные сокеты и отправляете очень большие файлы, вы можете читать файл по частям (используя the_file.read(number_of_bytes)) и записывать его непосредственно в сокет (используя the_socket.send(read_binary_data)). [HT к @Teudimundo ]

Re: Обновление

Относительно обновления (которое действительно должно быть новым вопросом ...): нет синтаксиса строки формата (ни нового ("{}"), ни старого ("%s")) для bytes. Вам нужно использовать decode для объекта bytes, чтобы превратить его в строку и правильно использовать строки формата (или превратить строку в bytes с помощью encode и вместо этого использовать обычную конкатенацию ). Также обратите внимание, что textwrap.dedent не работает с bytes, потому что регулярные выражения не работают с bytes в Python.

5
hlt 20 Авг 2014 в 13:12
1
Я писал такой же ответ, но я бы добавил, что вместо добавления всех двоичных данных в строку, с точки зрения использования памяти, нужно читать двоичные данные фрагментами и записывать их непосредственно в сокет.
 – 
Teudimundo
20 Авг 2014 в 11:38
У меня есть Python2 и 3, и я использую python-запросы. Будет ли работать, если отправить такой пост: session.request(....., data = binary_data_with_headers)?
 – 
Incerteza
20 Авг 2014 в 12:32
Обычно я не использую requests, но думаю, что вы могли бы что-то сделать с session.post(....., data=the_data), если ваш запрос является запросом POST (убедитесь, что он каким-то образом не закодирован). Я не уверен, что делать с запросами GET (и другими). Лично я бы использовал сырые сокеты для такого рода вещей.
 – 
hlt
20 Авг 2014 в 12:40
Обновлено. Однако подумайте о том, чтобы задавать такие дополнительные вопросы как отдельные вопросы вместо того, чтобы редактировать свой вопрос (это упрощает выполнение вопросов и ответов для других и т. Д.)
 – 
hlt
20 Авг 2014 в 12:51
1
Хороший ответ, но я бы не осмелился преобразовать двоичные байты в юникод с многобайтовой кодировкой как utf-8! Выглядит нормально, используя latin1 с python 3.4, но пахнет недокументированной функцией. Я пробовал bytes(range(256)).decode(), и он не работает с UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 128: invalid start byte
 – 
Serge Ballesta
20 Авг 2014 в 13:02