在 HTTP 中有两种 POST 数据的方法:application/x-www-form-urlencoded
和 multipart/form-data
。我了解大多数浏览器只有在使用 multipart/form-data
时才能上传文件。在 API 上下文(不涉及浏览器)中使用其中一种编码类型时是否有任何其他指导?这可能例如基于:
数据大小
非 ASCII 字符的存在
存在于(未编码的)二进制数据上
需要传输其他数据(如文件名)
到目前为止,我基本上没有在网上找到关于使用不同内容类型的正式指南。
libcurl
的任何人使用的选项:curl_easy_setopt(myCurlEasyhandle, CURLOPT_MIMEPOST, mimeHandle);
:curl.se/libcurl/c/CURLOPT_MIMEPOST.html:“此选项是发布 HTTP 表单的首选方式,替换和扩展已弃用的 { 5} 选项。” 完整示例:curl.se/libcurl/c/smtp-mime.html
TL;博士
概括;如果您有二进制(非字母数字)数据(或相当大的有效负载)要传输,请使用 multipart/form-data
。否则,使用 application/x-www-form-urlencoded
。
您提到的 MIME 类型是用户代理(浏览器)必须支持的 HTTP POST 请求的两个 Content-Type
标头。这两种类型的请求的目的都是向服务器发送名称/值对列表。根据所传输数据的类型和数量,其中一种方法将比另一种更有效。要了解原因,您必须查看每个人在幕后所做的事情。
对于 application/x-www-form-urlencoded
,发送到服务器的 HTTP 消息的主体本质上是一个巨大的查询字符串——名称/值对由 & 号 (&
) 分隔,名称与值由等号 ({ 3})。这方面的一个例子是:
MyVariableOne=ValueOne&MyVariableTwo=ValueTwo
[保留和] 非字母数字字符替换为 `%HH'、一个百分号和两个表示字符 ASCII 码的十六进制数字
这意味着对于我们的值中存在的每个非字母数字字节,将需要三个字节来表示它。对于大型二进制文件,将有效负载增加三倍将非常低效。
这就是 multipart/form-data
的用武之地。使用这种传输名称/值对的方法,每对都表示为 MIME 消息中的“部分”(如其他答案所述)。部分由特定的字符串边界分隔(特别选择以便此边界字符串不会出现在任何“值”有效负载中)。每个部分都有自己的一组 MIME 标头,例如 Content-Type
,尤其是 Content-Disposition
,它可以为每个部分提供“名称”。每个名称/值对的值片段是 MIME 消息每个部分的有效负载。 MIME 规范在表示值有效负载时为我们提供了更多选择——我们可以选择更有效的二进制数据编码来节省带宽(例如 base 64 甚至原始二进制)。
为什么不一直使用 multipart/form-data
?对于较短的字母数字值(如大多数 Web 表单),添加所有 MIME 标头的开销将大大超过更有效的二进制编码所节省的成本。
在这里至少阅读第一段!
我知道这已经晚了 3 年,但马特(已接受)的答案不完整,最终会让你陷入困境。这里的关键是,如果您选择使用 multipart/form-data
,则边界必须不出现在服务器最终接收的文件数据中。
这对 application/x-www-form-urlencoded
来说不是问题,因为没有边界。 x-www-form-urlencoded
也总是可以处理二进制数据,通过将一个任意字节转换为三个 7BIT
字节的简单权宜之计。效率低下,但它有效(请注意,关于无法发送文件名和二进制数据的评论是不正确的;您只需将其作为另一个键/值对发送)。
multipart/form-data
的问题是边界分隔符不能出现在文件数据中(请参阅 RFC 2388;第 5.2 节还包括一个相当蹩脚的借口,即没有适当的聚合 MIME 类型来避免这个问题)。
因此,乍一看,multipart/form-data
在 any 文件上传中没有任何价值,无论是二进制文件还是其他文件。如果您没有正确选择边界,那么无论您发送纯文本还是原始二进制文件, 最终都会遇到问题 - 服务器会在错误的位置找到边界,并且您的文件将被截断,否则 POST 将失败。
关键是选择编码和边界,以使您选择的边界字符不能出现在编码输出中。一种简单的解决方案是使用 base64
(not 使用原始二进制文件)。在 base64 中,3 个任意字节被编码为 4 个 7 位字符,其中输出字符集为 [A-Za-z0-9+/=]
(即字母数字、'+'、'/' 或 '=')。 =
是一种特殊情况,可能只出现在编码输出的末尾,作为单 =
或双 ==
。现在,选择您的边界作为不能出现在 base64
输出中的 7 位 ASCII 字符串。您在网上看到的许多选择都未能通过此测试 - 例如,MDN 表单 docs 在发送二进制数据时使用“blob”作为边界 - 不好。但是,像“!blob!”之类的东西永远不会出现在 base64
输出中。
index === -1
。
'()+-./:=
。然而,带有子串检查的随机生成仍然是要走的路,它可以用一行来完成:while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}
。 EML 的建议(转换为 base64 只是为了避免匹配子字符串)很奇怪,更不用说它带来了不必要的性能下降。由于单线算法同样简单明了,因此所有的麻烦都是徒劳的。 Base64 不应该以这种方式(ab)使用,因为 HTTP 正文 accept all 8-bit 八位字节。
我不认为 HTTP 仅限于多部分或 x-www-form-urlencoded 中的 POST。 Content-Type Header 与 HTTP POST 方法正交(您可以填写适合您的 MIME 类型)。这也是典型的基于 HTML 表示的 webapps 的情况(例如,json 有效负载在传输 ajax 请求的有效负载方面变得非常流行)。
关于基于 HTTP 的 Restful API,我接触到的最流行的内容类型是 application/xml 和 application/json。
应用程序/xml:
数据大小:XML 非常冗长,但在使用压缩并认为写访问情况(例如通过 POST 或 PUT)比读访问更罕见时通常不是问题(在许多情况下,它小于所有流量的 3% )。很少有我必须优化写入性能的情况
非 ascii 字符的存在:您可以使用 utf-8 作为 XML 中的编码
二进制数据的存在:需要使用base64编码
文件名数据:您可以将此内部字段封装在 XML 中
应用程序/json
数据大小:比 XML 更紧凑,仍然是文本,但您可以压缩
非 ascii 字符:json 是 utf-8
二进制数据:base64(另见 json-binary-question)
文件名数据:封装为 json 中自己的字段部分
二进制数据作为自己的资源
我会尝试将二进制数据表示为自己的资产/资源。它增加了另一个调用,但更好地解耦了东西。示例图像:
POST /images
Content-type: multipart/mixed; boundary="xxxx"
... multipart data
201 Created
Location: http://imageserver.org/../foo.jpg
在以后的资源中,您可以简单地将二进制资源作为链接内联:
<main-resource>
...
<link href="http://imageserver.org/../foo.jpg"/>
</main-resource>
我同意曼努埃尔所说的很多。事实上,他的评论指的是这个网址......
http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
...其中指出:
内容类型“application/x-www-form-urlencoded”对于发送大量二进制数据或包含非 ASCII 字符的文本效率低下。内容类型“multipart/form-data”应用于提交包含文件、非 ASCII 数据和二进制数据的表单。
但是,对我来说,这将归结为工具/框架支持。
您希望您的 API 用户使用哪些工具和框架来构建他们的应用程序?
他们是否有可以使用的框架或组件来支持一种方法而不是另一种方法?
如果您清楚地了解您的用户,以及他们将如何使用您的 API,那么这将有助于您做出决定。如果您让 API 用户难以上传文件,那么他们就会离开,您将花费大量时间来支持他们。
其次是您编写 API 的工具支持,以及适应一种上传机制而不是另一种上传机制的难易程度。
关于上传 HTML5 画布图像数据的一点提示:
我正在为一家印刷店开发一个项目,并且由于将来自 HTML5 canvas
元素的图像上传到服务器而遇到了一些问题。我挣扎了至少一个小时,但我没有将图像正确保存在我的服务器上。
将 jQuery ajax 调用的 contentType
选项设置为 application/x-www-form-urlencoded
后,一切正常,base64 编码的数据被正确解释并成功保存为图像。
也许这对某人有帮助!
如果您需要使用 Content-Type=x-www-urlencoded-form 则不要使用 FormDataCollection 作为参数:在 asp.net Core 2+ 中,FormDataCollection 没有格式化程序所需的默认构造函数。改用 IFormCollection:
public IActionResult Search([FromForm]IFormCollection type)
{
return Ok();
}
在我的情况下,问题是响应 contentType
是 application/x-www-form-urlencoded
,但实际上它包含一个 JSON
作为请求的正文。当我们在 Django 中访问 request.data
时,Django 无法正确转换它,因此访问 request.body
。
请参阅此答案以更好地理解:Exception: You cannot access body after reading from request's data stream
不定期副业成功案例分享