应用程序/ x-www-form-urlencoded还是multipart / form-data?


1334

在HTTP中,有两种发布数据的方式:application/x-www-form-urlencodedmultipart/form-data。我了解大多数浏览器仅multipart/form-data在使用后才能上传文件。在API上下文中使用一种编码类型(不涉及浏览器)时,是否还有其他指导?例如,这可能基于:

  • 资料大小
  • 存在非ASCII字符
  • 存在于(未编码的)二进制数据上
  • 需要传输其他数据(如文件名)

到目前为止,我基本上没有找到关于使用不同内容类型的正式指南。


74
应该提到的是,这是HTML表单使用的两种MIME类型。HTTP本身没有这样的限制...人们可以通过HTTP使用他想要的任何MIME类型。
tybro0103 2014年

Answers:


2012

TL; DR

摘要; 如果您要传输二进制(非字母数字)数据(或有效载荷大小很大),请使用multipart/form-data。否则,请使用application/x-www-form-urlencoded


您提到的MIME类型Content-Type是用户代理(浏览器)必须支持的HTTP POST请求的两个标头。这两种类型的请求的目的都是将名称/值对的列表发送到服务器。根据要传输的数据的类型和数量,其中一种方法将比另一种更为有效。要了解原因,您必须查看每个任务的幕后工作。

对于application/x-www-form-urlencoded,发送到服务器的HTTP消息的主体实质上是一个巨大的查询字符串-名称/值对用&符(&)分隔,名称与值用等号(=)分隔。例如: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

根据规格

[保留和]非字母数字字符替换为'%HH',百分号和两个十六进制数字,代表该字符的ASCII码

这意味着对于我们的值之一中存在的每个非字母数字字节,将需要三个字节来表示它。对于大的二进制文件,将有效载荷增加三倍将是非常低效的。

就是这样multipart/form-data。通过这种传输名称/值对的方法,每个对在MIME消息中都表示为“部分”(如其他答案所述)。部件由特定的字符串边界分隔(经过专门选择,以使该边界字符串不会出现在任何“值”有效负载中)。每个部分都有其自己的MIME标头集,例如Content-Type和,特别是Content-Disposition,可以为每个部分赋予其“名称”。每个名称/值对的值片是MIME消息的每个部分的有效负载。MIME规范在表示值有效载荷时为我们提供了更多选项-我们可以选择更有效的二进制数据编码来节省带宽(例如,基本64位甚至原始二进制)。

为什么不一直使用multipart/form-data?对于短的字母数字值(像大多数Web表单一样),添加所有MIME标头的开销将大大超过更有效的二进制编码所节省的费用。


84
x-www-form-urlencoded是否有长度限制,还是没有限制?
Pacerier 2013年

34
@Pacerier限制由接收POST请求的服务器强制执行。请参阅此线程以获取更多讨论:stackoverflow.com/questions/2364840/…–
马特·布里奇斯

5
@ZiggyTheHamster JSON和BSON分别对不同类型的数据更有效。对于这两种序列化方法,Base64都不如gzip。Base64根本没有带来任何优势,HTTP支持二进制pyload。
Tiberiu-Ionuț Stan

16
还要注意,如果表单包含上载的命名文件,则唯一的选择就是表单数据,因为urlencoded无法放置文件名(在表单数据中,它是content-disposition的name参数)。
Guido van Rossum 2014年

4
@EML看到我的括号“(特别选择,以便在任何“值”有效负载中都不会出现此边界字符串)”
Matt Bridges 2014年

151

在这里至少阅读第一段!

我知道这已经晚了3年,但是Matt(接受)的答案不完整,最终会给您带来麻烦。这里的关键是,如果选择使用multipart/form-data,则边界不得出现在服务器最终接收到的文件数据中。

对于而言application/x-www-form-urlencoded,这不是问题,因为没有边界。x-www-form-urlencoded通过将一个任意字节变成三个7BIT字节的简单权宜之计,也可以始终处理二进制数据。效率低下,但有效(并且请注意,关于无法发送文件名和二进制数据的评论是不正确的;您只是将其作为另一个键/值对发送)。

问题multipart/form-data在于边界分隔符一定不能出现在文件数据中(请参阅RFC 2388;第5.2节还包括一个相当la脚的借口,因为它没有避免此问题的适当的汇总MIME类型)。

因此,乍看multipart/form-data之下,在任何文件上传(二进制或其他方式)中都没有任何价值。如果不正确地选择你的边界,那么你最终有一个问题,不管你是发送纯文本或原始二进制-服务器会发现放错了地方的边界,你的文件将被截断,或POST将失败。

关键是选择一种编码和边界,以使您选择的边界字符不会出现在编码输出中。一种简单的解决方案是使用base64使用原始二进制文件)。在base64中,将 3个任意字节编码为四个7位字符,其中输出字符集为[A-Za-z0-9+/=](即字母数字,“ +”,“ /”或“ =”)。=是一种特殊情况,可能只在编码输出的末尾显示为single =或double ==。现在,选择您的边界作为7位ASCII字符串,该字符串不能出现在base64输出中。您在网上看到的许多选择都无法通过此测试-MDN表单文档,例如,在发送二进制数据时使用“ blob”作为边界-不好。但是,类似“!blob!”之类的东西。永远不会出现在base64输出中。


52
尽管考虑到多部分/表单数据是要确保边界不会出现在数据中,但是通过选择足够长的边界可以很容易地做到这一点。请不要使用base64编码来完成此操作。随机生成的边界和与UUID相同的长度应足够:stackoverflow.com/questions/1705008/…
Joshcodes

20
@EML,这根本没有意义。显然,边界是由http客户端(浏览器)自动选择的,并且该客户端将足够聪明,不会使用与您上传的文件的内容发生冲突的边界。就像子串匹配一样简单index === -1
Pacerier 2014年

13
@Pacerier:(A)阅读问题:“不涉及浏览器,API上下文”。(B)浏览器始终不会为您构建请求。您可以自己手动执行。浏览器中没有魔术。
EML 2014年

12
@BeniBela,他可能会建议'()+-./:=然后使用。然而,通过子字符串检查随机生成仍然是可行的方法,并且只需一行即可完成:while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}。EML的建议(为了避免匹配子字符串而转换为base64)只是很奇怪,更不用说它会带来不必要的性能下降。一站式算法同样简单明了,所有麻烦全都解决了。因为HTTP主体接受所有8位八位字节,所以不打算以这种方式使用Base64 。
Pacerier,2015年

31
这个答案不仅没有增加讨论的余地,而且给出了错误的建议。首先,每当在分开的部分中传输随机数据时,总是有可能所选的边界将出现在有效载荷中。确保不会发生这种情况的唯一方法是检查我们提出的每个边界的整个有效负载。完全不切实际。我们只接受发生碰撞的无穷小概率,并得出合理的边界,例如“ --- boundary-<< UUID here> -boundary ---”。其次,始终使用Base64会毫无原因地浪费带宽并填满缓冲区。
vagelis '16

92

我不认为HTTP限于多部分或x-www-form-urlencoded的POST。的内容类型标头是垂直于HTTP POST方法(可以补MIME类型适合你)。典型的基于HTML表示的Web应用程序也是如此(例如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中自己的field-section

二进制数据作为自己的资源

我会尝试将二进制数据表示为自己的资产/资源。它增加了另一个调用,但可以更好地解耦。图片示例:

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>

有趣。但是何时使用application / x-www-form-urlencoded以及何时multipart / form-data?
最大

3
application / x-www-form-urlencoded是请求的默认mime类型(另请参见w3.org/TR/html401/interact/forms.html#h-17.13.4)。我将其用于“常规”网络表单。对于API,我使用application / xml | json。multipart / form-data是考虑附件的钟声(在响应主体中,数个数据部分包含定义的边界字符串)。
曼努埃尔·奥尔丹娜2010年

4
我认为OP可能只是在询问HTML表单使用的两种类型,但我很高兴指出了这一点。
tybro0103 2014年

30

我同意曼努埃尔所说的话。实际上,他的评论指的是这个网址...

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的工具支持,以及与另一种上传机制相比,容纳一种上载机制有多么容易。


1
嗨,这是否意味着每次我们向Web服务器发布内容时,都必须提及什么是Content-type,以便让Web服务器知道它应该对数据进行解码?即使我们自己编写http请求,也必须提及Content-type吗?
GMsoF 2013年

2
@GMsoF,这是可选的。请参阅stackoverflow.com/a/16693884/632951。在为特定服务器设计特定请求时,您可能要避免使用content-type以避免通用开销。
Pacerier,2014年

2

在我这边,有关上传HTML5画布图像数据的一些提示:

我正在为一家印刷厂进行项目,由于将图像从HTML5 canvas元素上传到服务器,因此出现了一些问题。我苦苦挣扎了至少一个小时,但没有将其正确保存在服务器上。

一旦我设置了contentTypejQuery ajax调用的 选项,application/x-www-form-urlencoded一切都将正确进行,并且base64编码的数据将被正确解释并成功保存为图像。


也许可以帮助某人!


4
在更改之前,它发送的是什么内容类型?此问题可能是由于服务器不支持您将其发送为的内容类型。
catorda

1

如果您需要使用Content-Type = x-www-urlencoded-form,则不要使用FormDataCollection作为参数:在asp.net Core 2+中,FormDataCollection没有格式化程序所需的默认构造函数。改用IFormCollection:

 public IActionResult Search([FromForm]IFormCollection type)
    {
        return Ok();
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.