协慌网

登录 贡献 社区

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

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

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

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

答案

TL; DR

摘要; 如果要传输二进制(非字母数字)数据(或大小有效的有效负载),请使用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 头的开销将大大超过更有效的二进制编码所带来的节省。

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

我知道这已经太晚了 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任何文件上传,二进制或其他方面都没有任何价值。如果不正确地选择你的边界,那么你最终有一个问题,不管你是发送纯文本或原始二进制 - 服务器会发现放错了地方的边界,你的文件将被截断,或 POST 将失败。

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

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

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

应用程序 / XML:

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

应用程序 / JSON

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