【发布时间】:2012-11-10 23:17:24
【问题描述】:
简介
背景
我正在编写一个脚本来上传内容,包括使用RFC 2388 中定义的multipart/form-data 内容类型的文件。从长远来看,我正在尝试提供一个简单的 Python 脚本来执行 uploads of binary packages for github,这涉及将类似表单的数据发送到 Amazon S3。
相关
This question 已经询问过如何做到这一点,但到目前为止还没有一个公认的答案,并且它目前有两个答案中的the more useful 指向these recipes,这反过来又手动构建了整个消息。我有点担心这种方法,尤其是在字符集和二进制内容方面。
还有this question,其currently highest-scoring answer 暗示MultipartPostHandler 模块。但这与我提到的食谱没有太大区别,因此我的担忧也适用于此。
疑虑
二进制内容
RFC 2388 Section 4.3 明确指出,除非另有声明,否则内容应为 7 位,因此可能需要 Content-Transfer-Encoding header。这是否意味着我必须对二进制文件内容进行 Base64 编码?或者Content-Transfer-Encoding: 8bit 对于任意文件是否足够?还是应该读为Content-Transfer-Encoding: binary?
标题字段的字符集
一般的标头字段,尤其是filename 标头字段,默认情况下仅为 ASCII。我希望我的方法也能够传递非 ASCII 文件名。我知道对于我当前为 github 上传内容的应用程序,我可能不需要它,因为文件名是在单独的字段中给出的。但我希望我的代码是可重用的,所以我宁愿以一种一致的方式对文件名参数进行编码。 RFC 2388 Section 4.4 建议在 RFC 2231 中引入的格式,例如filename*=utf-8''t%C3%A4st.txt.
我的方法
使用 python 库
由于multipart/form-data 本质上是一种MIME 类型,我认为应该可以使用标准python 库中的email package 来撰写我的帖子。特别是对非 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.headerpackage 来格式化标题值?email.utils.encode_rfc2231这样做。) - 是否有一些实现已经完成了我想做的所有事情?
这些问题密切相关,可以概括为“您将如何实施”。在许多情况下,回答一个问题要么回答要么废弃另一个问题。所以我希望你同意为所有这些人发一篇帖子是合适的。
【问题讨论】:
-
HTTP 中没有 Content-Transfer-Encoding 头域。只是不要发送它。
标签: python http amazon-s3 mime multipartform-data