题 application / x-www-form-urlencoded或multipart / form-data?


在HTTP中有两种POST数据的方法: application/x-www-form-urlencoded 和 multipart/form-data。据我所知,大多数浏览器只能上传文件 multipart/form-data 用来。在API上下文中使用其中一种编码类型时是否有任何其他指导(不涉及浏览器)?这可能是例如根据:

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

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


1067
2017-10-24 11:12


起源


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


答案:


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规范在表示值有效负载时为我们提供了更多选项 - 我们可以选择更有效的二进制数据编码来节省带宽(例如base 64甚至原始二进制)。

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


1669
2017-11-01 21:59



x-www-form-urlencoded有长度限制,还是无限制? - Pacerier
@Pacerier限制由接收POST请求的服务器强制执行。有关更多讨论,请参阅此主题: stackoverflow.com/questions/2364840/... - Matt Bridges
@ZiggyTheHamster JSON和BSON对于不同类型的数据都更有效。对于两种序列化方法,Base64都不如gzip。 Base64根本没有任何优势,HTTP支持二进制pyload。 - Tiberiu-Ionuț Stan
另请注意,如果表单包含命名文件上载,则您唯一的选择是form-data,因为urlencoded无法放置文件名(在form-data中,它是content-disposition的name参数)。 - Guido van Rossum
@EML看到我的括号“(具体选择使得此边界字符串不会出现在任何”值“有效载荷中)” - Matt Bridges


在这里阅读至少第一个PARA!

我知道这已经晚了3年,但马特(接受)的答案是不完整的,最终会让你陷入困境。这里的关键是,如果你选择使用 multipart/form-data,边界必须  出现在服务器最终收到的文件数据中。

这不是问题 application/x-www-form-urlencoded,因为没有边界。 x-www-form-urlencoded 通过将一个任意字节转换为三个的简单方法,也可以始终处理二进制数据 7BIT 字节。效率低,但它有效(并注意关于无法发送文件名以及二进制数据的注释不正确;您只需将其作为另一个键/值对发送)。

这个问题 multipart/form-data 是文件数据中不得出现边界分隔符(参见 RFC2388;第5.2节还包含一个相当蹩脚的借口,因为没有适当的聚合MIME类型可以避免这个问题)。

所以,乍一看, multipart/form-data 没有任何价值 任何 文件上传,二进制或其他。如果你没有正确选择边界,那么你  最终会出现问题,无论是发送纯文本还是原始二进制文件 - 服务器都会在错误的位置找到边界,并且文件将被截断,否则POST将失败。

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


107
2018-04-18 11:08



虽然考虑多部分/表格数据是确保边界不出现在数据中,但通过选择足够长的边界来实现这是相当简单的。请不要使用我们的base64编码来完成此任务。随机生成的边界和与UUID相同的长度应该足够: stackoverflow.com/questions/1705008/...。 - Joshcodes
@EML,这根本没有意义。显然,http客户端(浏览器)会自动选择边界,客户端将足够聪明,不会使用与上传文件内容冲突的边界。它就像子串匹配一样简单 index === -1。 - Pacerier
@Pacerier :(一)阅读问题:“不涉及浏览器,API上下文”。 (B)浏览器无论如何都不会为您构建请求。你手动完成。浏览器没有魔力。 - EML
@BeniBela,他可能会建议使用 '()+-./:= 然后。然而,使用子串检查的随机生成仍然是可行的方法,可以使用一行来完成: while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}。 EML的建议(转换为base64只是为了避免匹配子串)简直就是奇怪,更不用说它带来了不必要的性能下降。而且,由于单线算法同样简单明了,所以什么都没有。 Base64并不意味着(ab)以这种方式使用,作为HTTP主体 接受所有8位 字节。 - Pacerier
这个答案不仅没有增加讨论,也提供了错误的建议。首先,每当在分开的部分中发送随机数据时,所选择的边界总是可能存在于有效载荷中。确保不会发生这种情况的唯一方法是检查我们提出的每个边界的整个有效负载。完全不切实际。我们接受了 无限 碰撞的概率,并提出一个合理的边界,如“--- boundary- <UUID here> -boundary ---”。其次,总是使用Base64会浪费带宽并毫无理由地填充缓冲区。 - vagelis


我认为HTTP不限于多部分或x-www-form-urlencoded中的POST。该 内容类型标题 与HTTP POST方法正交(您可以填写适合您的MIME类型)。对于典型的基于HTML表示的webapps也是如此(例如,json有效载荷变得非常流行以传输ajax请求的有效载荷)。

关于HTTP上的Restful API,我接触过的最流行的内容类型是application / xml和application / json。

应用程序/ XML:

  • data-size:XML非常详细,但在使用压缩时通常不会出现问题,并认为写访问案例(例如通过POST或PUT)作为读访问更为罕见(在许多情况下,它占所有流量的<3%) )。很少有我必须优化写入性能的情况
  • 存在非ascii字符:你可以使用utf-8作为XML编码
  • 存在二进制数据:需要使用base64编码
  • 文件名数据:您可以在XML中封装此内部字段

应用程序/ JSON

  • data-size:比XML更紧凑,仍然是文本,但你可以压缩
  • 非ascii chars:json是utf-8
  • 二进制数据:base64(另见 JSON-二进制问题
  • filename data:在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>

82
2017-10-24 16:46



有趣。但是什么时候使用application / x-www-form-urlencoded和multipart / form-data? - max
application / x-www-form-urlencoded是您请求的默认mime类型(另请参阅 w3.org/TR/html401/interact/forms.html#h-17.13.4)。我将它用于“普通”网页表单。对于API,我使用application / xml | json。 multipart / form-data是一个考虑附件的钟(在响应体内部,几个数据部分与定义的边界字符串连接)。 - manuel aldana
我认为OP可能只是询问HTML表单使用的两种类型,但我很高兴有人指出这一点。 - tybro0103


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

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的工具支持,以及容纳一个上传机制与另一个上载机制的容易程度。


26
2017-10-27 12:08



嗨,这是否意味着每次我们将某些东西发布到网络服务器时,我们必须提到什么是内容类型才能让网络服务器知道它应该解码数据?即使我们自己制作http请求,我们必须提到内容类型吗? - GMsoF
@GMsoF,这是可选的。看到 stackoverflow.com/a/16693884/632951 。您可能希望在为特定服务器制定特定请求时避免使用内容类型,以避免通用开销。 - Pacerier


我只是为了上传HTML5画布图像数据而提出的一点建议:

我正在为一家印刷厂开展项目,并且由于将图像上传到来自HTML5的服务器而出现了一些问题 canvas 元件。我挣扎了至少一个小时,我没有得到它在我的服务器上正确保存图像。

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


也许这有助于某人!


0
2017-12-10 15:07



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