介绍
背景
我正在编写一个脚本,使用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模块。但这与我提到的食谱没有太大不同,因此我的担忧也是如此。
顾虑
二进制内容
Content-Transfer-Encoding
header明确声明除非另有声明,否则内容的内容预计为7位,因此可能需要RFC 2388 Section 4.4。这是否意味着我必须对Base64编码的二进制文件内容进行编码?还是Content-Transfer-Encoding: 8bit
对于任意文件而言就足够了?还是应该阅读Content-Transfer-Encoding: binary
?
header 字段的字符集
通常, header 字段(尤其是filename
header 字段)默认情况下仅是ASCII。我希望我的方法也能够传递非ASCII文件名。我知道对于我当前为github上载内容的应用程序,由于文件名是在单独的字段中给出的,因此我可能不需要它。但是我希望代码可以重用,所以我宁愿使用一致的方式对文件名参数进行编码。 RFC 2231建议在 filename*=utf-8''t%C3%A4st.txt
package中引入的格式,例如multipart/form-data
。
我的方法
使用python库
由于email
本质上是MIME类型,我认为应该可以使用标准python库中的 Content-Transfer-Encoding
package撰写我的文章。我想委托(delegate)非ASCII header 字段进行相当复杂的处理。
到目前为止工作
所以我写了下面的代码:
#!/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==--
它似乎确实可以很好地处理 header 。二进制文件内容将被base64编码,这是可以避免的,但是应该可以正常工作。让我担心的是介于两者之间的文本字段。它们也是base64编码的。我认为,按照标准,这应该可以很好地工作,但是我宁愿在其中添加纯文本,以防万一某些笨拙的框架必须在中间级别处理数据并且不了解Base64编码数据。
问题
8bit
写为binary
还是email.header
? email.utils.encode_rfc2231
来格式化标题值? (ojit_a执行此操作。)这些问题密切相关,可以概括为“您将如何实现” 。在许多情况下,回答一个问题会回答另一个问题或使另一个问题过时。因此,我希望您同意所有这些都适合发表一篇文章。
最佳答案
这是一个占位符答案,描述了我在等待一些问题的权威意见时所做的事情。如果它表明该方法在至少一个设计决策中是错误的或不合适的,我将很乐意接受不同的答案。
Here是我目前根据自己的喜好使用的代码。
我做出了以下决定:
我决定这样做。至少对于此应用程序,它确实有效。
我找不到办法,所以我正在做自己的序列化,就像我在此看到的所有other recipes一样。
简单地以二进制形式发送文件内容似乎已经足够好,至少在我的单个应用程序中是如此。
正如RFC 2045 Section 2.8所述,在LFCR对之间8bit
数据受998个八位位组的行长限制,我决定binary
更加通用,因此在这里更合适的描述。
正如已经编辑到我的问题中一样, email.utils.encode_rfc2231
在此方面非常有用。我尝试先使用ascii进行编码,但是在非ascii数据或双引号字符串中禁止使用ascii字符的情况下,请使用该方法。
不是我知道的。但是,邀请其他实现采用my code的想法。
编辑:
多亏了this comment,我现在知道RFC 2231对于 header 的使用并没有被普遍接受:HTML 5 forbids its use的当前草案。还可以看到cause problems in the wild。但是由于POST header 并不总是与特定的HTML文档相对应(例如,以Web API为例),因此我不确定在这方面我是否会信任该草案。 RFC 5987 Section 4.2建议,正确的方法可能是同时提供编码名称和未编码名称。但是RFC是用于HTTP header 的,而multipart/form-data header 在技术上是HTTP正文。因此该RFC不适用,我不知道有任何RFC明确允许(甚至鼓励)同时为multipart/form-data使用两种形式。