在HTTP中,发布数据有两种方法:application/x-www-form-urlencoded和multipart/form-data。我知道大多数浏览器只能在使用multipart/form-data的情况下上载文件。当在API上下文中使用其中一种编码类型(不涉及浏览器)时,是否有其他指导?这可能是基于:
- 数据大小
- 非ASCII字符的存在
- 存在于(未编码)二进制数据上
- 需要传输附加数据(如文件名)
到目前为止,我基本上没有在网络上找到关于使用不同内容类型的正式指导。
- 应该提到,这是HTML表单使用的两种MIME类型。HTTP本身没有这样的限制…你可以通过HTTP使用任何他想要的mime类型。
DR
摘要:如果您有二进制(非字母数字)数据(或大容量的有效负载)要传输,请使用multipart/form-data。否则,使用application/x-www-form-urlencoded。
您提到的mime类型是用户代理(浏览器)必须支持的HTTP POST请求的两个Content-Type头。这两种类型的请求的目的都是向服务器发送名称/值对的列表。根据传输的数据类型和数量,其中一种方法将比另一种方法更有效。为了理解原因,你必须看看每个人都在做什么。
对于application/x-www-form-urlencoded来说,发送到服务器的HTTP消息的主体本质上是一个巨大的查询字符串——名称/值对用与号(&分隔,名称与值用等号(=分隔)。例如:
埃多克斯1〔6〕
根据规范:
[Reserved and] non-alphanumeric characters are replaced by `%HH', a percent sign and two hexadecimal digits representing the ASCII code of the character
号
这意味着对于我们其中一个值中存在的每个非字母数字字节,需要三个字节来表示它。对于大型二进制文件,将有效负载增加三倍将非常低效。
这就是multipart/form-data的切入点。使用这种传输名称/值对的方法,每对都被表示为mime消息中的"部分"(如其他答案所描述)。部分由特定的字符串边界分隔(特别选择,这样边界字符串就不会出现在任何"值"有效负载中)。每个部分都有自己的一组mime头,如Content-Type,尤其是Content-Disposition,可以为每个部分赋予"名称"。每个名称/值对的值块是mime消息每个部分的有效负载。mime规范在表示值有效负载时为我们提供了更多的选项——我们可以选择更有效的二进制数据编码来节省带宽(例如base 64甚至原始二进制)。
为什么不一直使用multipart/form-data?对于短字母数字值(像大多数Web表单一样),添加所有mime头的开销将远远超过更高效的二进制编码所带来的任何节省。
- X-WWW-FORM-URLENCODED是否有长度限制,或者是无限的?
- base64可能比url编码更有效,但是直接二进制是最有效的。
- @Pacrier限制由接收POST请求的服务器强制执行。更多讨论请参见此线程:stackoverflow.com/questions/2364840/…
- @ZiggyThehamster JSON和BSON对于不同类型的数据都更有效。对于这两种序列化方法,base64都低于gzip。base64并没有带来任何优势,HTTP支持二进制挂架。
- @提比略爱奴?斯坦·齐吉的评论是对我最初的回答的回应,我的回答没有提到原始二进制。
- 另外请注意,如果表单包含命名文件上载,则您唯一的选择是表单数据,因为URLEncoded无法放置文件名(在表单数据中,它是内容处置的名称参数)。
- 你的答案应该更新吗?我听说不鼓励使用x-格式的mimes类型。
- @fractaliste并不是真正的——它是HTML401规范的一部分,而且HTML5文档仍然使用x-mimetype。w3.org/tr/html5/forms.html dom fs enctype
- 此答案不完整,可能会给您带来麻烦-请参阅下面的我的答案。
- @eml请参阅我的附加说明(特别选择,以便此边界字符串不会出现在任何"值"有效负载中)
- @MB:当然,我很感激,但是您的答案只关注有效负载的大小,您建议使用"甚至原始二进制"编码,这不起作用。您必须同时选择边界和编码,这是这个问题的难点部分。下面我没有提到的另一个问题是,为form-data编写解析器比为URL编码编写解码器困难得多,后者可能与通用API上下文相关(至少对我来说是这样)。
- @嗯,一点也不难。任何一个1周大的编码员都能做到。
- 您还应该提到默认值是"application/x-www-form-urlencoded"w3.org/tr/html401/interact/forms.html adef enctype
- @Roland这个问题是关于没有浏览器的API上下文的(即HTTP,不一定是HTML)
- 这真的帮助了我!回答得很好。
- 使用multipart,您也可以发送"form urlencoded"的部件:p
- 大多数用户代理("浏览器")不支持上载文件,除非使用multipart/form-data。multipart/form-data的每个nn字节长参数的最大开销(以字节为单位)约为2*n + 2(百分比编码加"&;"加=),而multipart/form-data的开销约为50。
至少在这里读第一段!
我知道这已经晚了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在任何文件上传(二进制或其他)中都没有任何价值。如果您没有正确选择边界,那么无论您发送的是纯文本还是原始二进制文件,最终都会出现问题-服务器将在错误的位置找到边界,并且您的文件将被截断,否则发布将失败。
关键是选择一个编码和一个边界,这样您选择的边界字符就不会出现在编码的输出中。一个简单的解决方案是使用base64(不要使用原始二进制)。在base64中,任意3个字节被编码为4个7位字符,其中输出字符集为[A-Za-z0-9+/=](即字母数字、"+"、"/"或"=")。=是一种特殊情况,只能作为单个=或双==出现在编码输出的末尾。现在,选择边界作为不能出现在base64输出中的7位ASCII字符串。在网上看到的许多选择都没有通过这个测试——例如,MDN表单文档在发送二进制数据时使用"blob"作为边界——不好。不过,有点像"!大便!"不会出现在base64输出中。
- 虽然考虑到多部分/表单数据是确保数据中不会出现边界,但通过选择足够长的边界来实现这一点非常简单。请不要使用base64编码来完成此操作。随机生成的与UUID长度相同的边界应该足够:stackoverflow.com/questions/1705008/…。
- 迟到总比不迟到好。
- 当结合马特的回答时,这确实清楚了一个人的概念……正确!荣誉!
- @嗯,这根本没道理。显然,边界是由HTTP客户机(浏览器)自动选择的,客户机将足够智能,不会使用与上载文件内容冲突的边界。很简单,一个子串匹配index === -1。
- @Pacerier:(a)阅读问题:"不涉及浏览器,API上下文"。(B)浏览器不会为您构建请求。你自己动手。浏览器没有魔力。
- @嗯,好的,然后我们手动操作。这么难做吗?
- @这是什么语言?你是说Math.random()吗?如果你想用随机数,好的。生成一个随机的6字节序列,使用它作为分隔符,不必像joshcodes建议的那样检查它。只要它是随机的,而不是你刚输入的任意文本序列,你就可以了。另一方面,如果您不喜欢统计编程,并且可以承担base64 17%的开销,那么只需使用一个有保证的分隔符,如!blob!。我不是在告诉你怎么做-大多数人在实践中会使用随机字节序列。你的电话。
- @EML,不需要base64。如我上面的代码所示,Random做得很好。你说这是个棘手的问题,所以我问你,这么做是不是很困难?
- @帕塞尔:在回复之前,你什么都没读过。我没有说这是个难题。我说过,为表单数据编写解析器比其他东西更困难。你一开始就错误地说浏览器会自动选择一个边界,然后继续试图让我陷入一个毫无意义的争论中。如果你想发布一个答案,只需发布它。
- @嗯,再看一次你的第一段。你说"最终会给你带来麻烦"。关键是我们不会惹上麻烦。我是正确的指出,你的上述要求是错误的,但你仍然坚持我们会陷入麻烦。2)我正确地指出,浏览器会自动选择一个边界,并用websniffer验证它。3)没有人说你说这是个难题。我问了一个问题,还是没有得到答复:这么难做吗?
- 关于!blob!:RFC1341说!不是边界中允许的字符。只能使用0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+,-./:=?。
- @贝尼贝拉,那么他可能会建议使用'()+-./:=。然而,带子串检查的随机生成仍然是一条路要走,它可以用一条线来完成:while(true){r = rand(); if(data.indexOf(r) === -1){doStuff();break;}}。EML的建议(转换为base64只是为了避免匹配子字符串)非常奇怪,更不用说它会带来不必要的性能下降。所有的麻烦都是白费力气的,因为单线算法同样简单明了。base64并不打算以这种方式使用(ab),因为HTTP主体接受所有8位八位字节。
- 这个答案不仅没有增加讨论的内容,而且给出了错误的建议。首先,当以分离的部分传输随机数据时,所选择的边界总是可能出现在有效载荷中。确保这不会发生的唯一方法是检查我们提出的每个边界的整个有效载荷。完全不切实际。我们只接受碰撞的极小概率,并提出一个合理的边界,比如"——边界————边界——"。其次,总是使用base64将浪费带宽,毫无理由地填满缓冲区。
- 由于使用足够长的同步标记(apache avro.679487.n3.nabble.com/…)可以确保avro数据文件的安全性,因此,与128位真正随机数据发生数据冲突的可能性非常小,这一点不值得担心,因为数据中心可能会被灾难所消耗。
- 您可以简单地使用UUID v4作为边界,并且在对数据进行编码时,如果在编码期间在数据中发现相同的字符串,请从头重试。除非要传输的数据比36*2^63字节长,否则即使恶意攻击者也无法强制编码部分需要多次重试。并且考虑到数据和边界是通过相同的过程选择的,所以尝试攻击这一部分几乎没有什么意义。(如果您知道您发送的是相当于zebibytes的uuid字符串,那么您可能可以找到合适的替代边界字符串。)
- 请不要使用rand()在php中生成边界。默认情况下,这将生成一个范围为[0, getrandmax()]的整数,在某些平台上,该整数可以小于[0, 32767]。攻击者可以非常琐碎地上传一个文件,以这种方式生成所有可能的边界,并炸毁您的应用程序。我同意这个机制是愚蠢的,它的持久性是不可原谅的,但是请使用一个guid或者其他一些被认为是如此强大的东西,意外碰撞是不可能的。
我认为HTTP不局限于以multipart或x-www-form-urlencoded方式发布。Content-Type头与HTTP Post方法是正交的(您可以填充适合您的mime类型)。这也是典型的基于HTML表示的webapps的情况(例如,json负载对于为Ajax请求传输负载变得非常流行)。
关于HTTP上的RESTfulAPI,我接触到的最流行的内容类型是application/xml和application/json。
应用程序/XML:
- 数据大小:XML非常冗长,但在使用压缩和认为写访问情况(如通过POST或PUT)比读访问(在许多情况下,它小于所有通信量的3%)更为罕见时,通常不是问题。很少有我必须优化写性能的情况
- 非ASCII字符的存在:可以在XML中使用UTF-8作为编码
- 二进制数据的存在:需要使用base64编码
- 文件名数据:您可以将这个内部字段封装为XML
应用程序/JSON
- 数据大小:比XML更紧凑,仍然是文本,但您可以压缩
- 非ASCII字符:json是utf-8
- 二进制数据:base64(另见json二进制问题)
- 文件名数据:在JSON中封装为自己的字段部分
二进制数据作为自己的资源
我会尝试将二进制数据表示为自己的资产/资源。它添加了另一个调用,但更好地分离了内容。示例图像:
1 2 3 4 5 6
| <wyn>POST /images
Content-type: multipart/mixed; boundary="xxxx"
... multipart data
201 Created
Location: http://imageserver.org/../foo.jpg</wyn> |
在后面的资源中,您可以简单地将二进制资源内联为链接:
1 2 3 4
| <wyn><main-resource>
...
<link href="http://imageserver.org/../foo.jpg"/>
</main-resource></wyn> |
号
- 有趣。但是何时使用application/x-www-form-urlencoded以及何时使用多部分/表单数据?
- application/x-www-form-urlencoded是您请求的默认mime类型(另请参见w3.org/tr/html401/interact/forms.html h-17.13.4)。我把它用于"普通"的网络表单。对于API,我使用application/xml json。多部分/表单数据在考虑附件时是一个钟(在响应主体内,多个数据段与定义的边界字符串相连)。
- 我认为OP可能只是在问HTML表单使用的两种类型,但我很高兴有人指出这一点。
我同意曼纽尔所说的许多话。事实上,他的评论提到了这个网址…
http://www.w3.org/tr/html401/interact/forms.html h-17.13.4
…其中规定:
The content type
"application/x-www-form-urlencoded" is
inefficient for sending large
quantities of binary data or text
containing non-ASCII characters. The
content type"multipart/form-data"
should be used for submitting forms
that contain files, non-ASCII data,
and binary data.
号
但是,对我来说,这取决于工具/框架支持。
- 你用什么工具和框架希望您的API用户正在构建他们的应用程序?
- 有吗他们可以使用的框架或组件有一种方法比其他?
如果你对你的用户有一个清晰的概念,以及他们将如何使用你的API,那么这将帮助你做出决定。如果你让你的API用户很难上传文件,那么他们就会离开,而你会花很多时间来支持他们。
其次是对编写API的工具支持,以及如何轻松地将一个上传机制容纳到另一个上传机制中。
- 嗨,这是否意味着每次我们向Web服务器发布某些内容时,都必须提到内容类型,以便让Web服务器知道它是否应该对数据进行解码?即使我们自己设计HTTP请求,我们也必须提到内容类型,对吗?
- @GMSOF,可选。请参阅stackoverflow.com/a/16693884/632951。您可能希望在为特定服务器创建特定请求时避免使用内容类型,以避免一般性开销。
只是我这边上传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:
1 2 3 4
| public IActionResult Search([FromForm]IFormCollection type)
{
return Ok();
} |