Правильно отформатируйте multipart / form-data body
Я пишу скрипт для загрузки материалов, включая файлы, используяmultipart/form-data
тип контента, определенный вRFC 2388, В конце концов, я пытаюсь предоставить простой сценарий Python длязагрузка бинарных пакетов для github, который включает в себя отправку подобных данных в Amazon S3.
Этот вопрос уже спрашивал о том, как это сделать, но пока без ответа, итем полезнее из двух ответов он в настоящее время имеетэти рецепты которые в свою очередь строят все сообщение вручную. Я несколько обеспокоен этим подходом, особенно в отношении кодировок и двоичного содержимого.
Существует такжеэтот вопрос, с этимина данный момент самый результативный ответ предлагаяMultipartPostHandler
модуль. Но это не сильно отличается от рецептов, которые я упомянул, и поэтому мои опасения применимы и к этому.
RFC 2388, раздел 4.3 прямо указывает, что контент должен быть 7-битным, если не указано иное, и, следовательно,Content-Transfer-Encoding
заголовок может потребоваться Означает ли это, что я должен был бы кодировать Base64 содержимое двоичного файла? Или будетContent-Transfer-Encoding: 8bit
быть достаточным для произвольных файлов? Или это должно прочитатьContent-Transfer-Encoding: binary
?
Поля заголовка в целом иfilename
Поле заголовка, в частности, ASCII только по умолчанию. Мне бы хотелось, чтобы мой метод также мог передавать имена файлов, не относящиеся к ASCII. Я знаю, что для моего текущего приложения загрузки материалов для github мне, вероятно, это не понадобится, так как имя файла указано в отдельном поле. Но я бы хотел, чтобы мой код можно было многократно использовать, поэтому я бы лучше закодировал параметр имени файла соответствующим образом.RFC 2388, раздел 4.4 советует формат введен вRFC 2231например,filename*=utf-8''t%C3%A4st.txt
.
Какmultipart/form-data
по сути MIME-тип, я подумал, что должно быть возможно использоватьemail
пакет из стандартных библиотек python для написания моего поста. В частности, я бы хотел делегировать довольно сложную обработку полей заголовков, не относящихся к ASCII.
Поэтому я написал следующий код:
#!/usr/bin/python3.2
import email.charset
import email.generator
import email.header
import email.mime.application
import email.mime.multipart
import email.mime.text
import io
import sys
class FormData(email.mime.multipart.MIMEMultipart):
def __init__(self):
email.mime.multipart.MIMEMultipart.__init__(self, 'form-data')
def setText(self, name, value):
part = email.mime.text.MIMEText(value, _charset='utf-8')
part.add_header('Content-Disposition', 'form-data', name=name)
self.attach(part)
return part
def setFile(self, name, value, filename, mimetype=None):
part = email.mime.application.MIMEApplication(value)
part.add_header('Content-Disposition', 'form-data',
name=name, filename=filename)
if mimetype is not None:
part.set_type(mimetype)
self.attach(part)
return part
def http_body(self):
b = io.BytesIO()
gen = email.generator.BytesGenerator(b, False, 0)
gen.flatten(self, False, '\r\n')
b.write(b'\r\n')
b = b.getvalue()
pos = b.find(b'\r\n\r\n')
assert pos >= 0
return b[pos + 4:]
fd = FormData()
fd.setText('foo', 'bar')
fd.setText('täst', 'Täst')
fd.setFile('file', b'abcdef'*50, 'Täst.txt')
sys.stdout.buffer.write(fd.http_body())
Результат выглядит так:
--===============6469538197104697019==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: form-data; name="foo"
YmFy
--===============6469538197104697019==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: form-data; name*=utf-8''t%C3%A4st
VMOkc3Q=
--===============6469538197104697019==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: form-data; name="file"; filename*=utf-8''T%C3%A4st.txt
YWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJj
ZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVm
YWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJj
ZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVm
YWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJj
ZGVmYWJjZGVmYWJjZGVm
--===============6469538197104697019==--
Кажется, он довольно хорошо обрабатывает заголовки. Содержимое двоичного файла будет закодировано в base64, чего можно избежать, но которое должно работать достаточно хорошо. Что меня беспокоит, так это текстовые поля между ними. Они также закодированы в base64. Я думаю, что в соответствии со стандартом это должно работать достаточно хорошо, но я бы предпочел иметь простой текст, на тот случай, если какая-то тупая структура должна иметь дело с данными на промежуточном уровне и не знает о данных, закодированных в Base64.
ВопросовМогу ли я использовать 8-битные данные для своих текстовых полей и при этом соответствовать спецификации?Могу ли я получить пакет электронной почты для сериализации моих текстовых полей как 8-битных данных без дополнительной кодировки?Если мне нужно придерживаться какой-то 7-битной кодировки, могу ли я заставить реализацию использовать цитируемую печатную форму для тех частей текста, где эта кодировка короче, чем base64?Можно ли избежать кодирования base64 для содержимого двоичных файлов?Если я могу избежать этого, я должен написатьContent-Transfer-Encoding
как8bit
или какbinary
?Если бы мне пришлось самому сериализовать тело, как я мог бы использоватьemail.header
пакет самостоятельно форматировать значения заголовка? (email.utils.encode_rfc2231
Является ли это.)Есть ли реализация, которая уже сделала все, что я пытаюсь сделать?Эти вопросы очень тесно связаны и могут быть обобщены как«Как бы вы это реализовали», Во многих случаях ответ на один вопрос либо отвечает, либо устаревает на другой. Поэтому я надеюсь, что вы согласны, что один пост для всех из них подходит.