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

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

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

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

1626229 次浏览

我不认为HTTP仅限于multipart或x-loo-form-urlencoded中的POST。内容类型页眉与HTTP POST方法正交(您可以填写适合您的MIME类型)。典型的基于超文本标记语言表示的Web应用程序也是如此(例如,json payload在传输ajax请求的payload时变得非常流行)。

关于基于HTTP的RESTful API,我接触到的最流行的内容类型是应用程序/xml和应用程序/json。

应用程序/xml:

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

应用程序名称

  • data-size:比XML更紧凑,仍然是文本,但你可以压缩
  • 非ascii字符:json是utf-8
  • 二进制数据:bas64(也见二进制问题
  • 文件名数据:在json内封装为自己的字段节

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

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

POST /imagesContent-type: multipart/mixed; boundary="xxxx"... multipart data
201 CreatedLocation: 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-loo-form-urlencoded"是发送大型二进制数据或文本的数量包含非ASCII字符。该内容类型“multipart/form-data”应用于提交表格包含文件、非ASCII数据、和二进制数据。

然而,对我来说,这将归结为工具/框架支持。

  • 您使用哪些工具和框架期望您的API用户正在构建他们的应用程序与?
  • 他们有他们可以使用的框架或组件偏爱一种方法而不是其他?

如果您清楚地了解您的用户以及他们将如何使用您的API,那么这将有助于您做出决定。如果您让API用户难以上传文件,那么他们就会离开,您将花费大量时间来支持他们。

其次是您编写API的工具支持,以及您适应一种上传机制而不是另一种上传机制的容易程度。

太长别读

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


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

对于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标头的开销将大大超过更有效的二进制编码所节省的任何费用。

至少在这里阅读第一段!

我知道这晚了3年,但Matt(已接受)的答案是不完整的,最终会让你陷入困境。这里的关键是,如果你选择使用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没有使用原始二进制)。在bas64中,3个任意字节被编码成四个7位字符,其中输出字符集是[A-Za-z0-9+/=](即字母数字, '+', '/' 或 '='). =是特例,并且可能只出现在编码输出的末尾,作为单个=或双==。现在,选择您的边界作为不能出现在base64输出中的7位ASCII字符串。您在网上看到的许多选择都未通过此测试-例如,MDN表单文档在发送二进制数据时使用“blob”作为边界-不好。然而,类似“!blob!”的东西永远不会出现在base64输出中。

只是我这边的一点提示,用于上传HTML5画布图像数据:

我正在为一家印刷厂做一个项目,由于将来自HTML5canvas元素的图像上传到服务器而遇到一些问题。我挣扎了至少一个小时,我没有把图像正确保存在我的服务器上。

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


也许这有助于别人!

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

 public IActionResult Search([FromForm]IFormCollection type){return Ok();}

在我的情况下,问题是响应contentTypeapplication/x-www-form-urlencoded,但实际上它包含一个JSON作为请求的主体。当我们在Django中访问request.data时,它无法正确转换它,因此访问request.body

请参考此答案以更好地理解:异常:从请求的数据流读取后无法访问主体