ChatGPT解决这个技术问题 Extra ChatGPT

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

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

数据大小

非 ASCII 字符的存在

存在于(未编码的)二进制数据上

需要传输其他数据(如文件名)

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

应该提到的是,这是 HTML 表单使用的两种 MIME 类型。 HTTP 本身没有这样的限制……人们可以通过 HTTP 使用他想要的任何 MIME 类型。
仅供使用 C curl 库 libcurl 的任何人使用的选项:curl_easy_setopt(myCurlEasyhandle, CURLOPT_MIMEPOST, mimeHandle);curl.se/libcurl/c/CURLOPT_MIMEPOST.html“此选项是发布 HTTP 表单的首选方式,替换和扩展已弃用的 { 5} 选项。” 完整示例:curl.se/libcurl/c/smtp-mime.html

R
Roel Van de Paar

TL;博士

概括;如果您有二进制(非字母数字)数据(或相当大的有效负载)要传输,请使用 multipart/form-data。否则,使用 application/x-www-form-urlencoded

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

对于 application/x-www-form-urlencoded,发送到服务器的 HTTP 消息的主体本质上是一个巨大的查询字符串——名称/值对由 & 号 (&) 分隔,名称与值由等号 ({ 3})。这方面的一个例子是: 

MyVariableOne=ValueOne&MyVariableTwo=ValueTwo

根据specification

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

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

这就是 multipart/form-data 的用武之地。使用这种传输名称/值对的方法,每对都表示为 MIME 消息中的“部分”(如其他答案所述)。部分由特定的字符串边界分隔(特别选择以便此边界字符串不会出现在任何“值”有效负载中)。每个部分都有自己的一组 MIME 标头,例如 Content-Type,尤其是 Content-Disposition,它可以为每个部分提供“名称”。每个名称/值对的值片段是 MIME 消息每个部分的有效负载。 MIME 规范在表示值有效负载时为我们提供了更多选择——我们可以选择更有效的二进制数据编码来节省带宽(例如 base 64 甚至原始二进制)。

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


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

在这里至少阅读第一段!

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

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

multipart/form-data 的问题是边界分隔符不能出现在文件数据中(请参阅 RFC 2388;第 5.2 节还包括一个相当蹩脚的借口,即没有适当的聚合 MIME 类型来避免这个问题)。

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

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


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

我不认为 HTTP 仅限于多部分或 x-www-form-urlencoded 中的 POST。 Content-Type Header 与 HTTP POST 方法正交(您可以填写适合您的 MIME 类型)。这也是典型的基于 HTML 表示的 webapps 的情况(例如,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 中自己的字段部分

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

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

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?
application/x-www-form-urlencoded 是您请求的默认 mime 类型(另请参阅 w3.org/TR/html401/interact/forms.html#h-17.13.4)。我将它用于“普通”网络表单。对于 API,我使用 application/xml|json。 multipart/form-data 是考虑附件的一个钟声(在响应正文中,几个数据部分与定义的边界字符串连接)。
我认为 OP 可能只是询问 HTML 表单使用的两种类型,但我很高兴有人指出了这一点。
您是否尝试过浏览器是否可以提交表单字段,例如使用 Json-Mime-type ?
J
Joe Shaw

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

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 的工具支持,以及适应一种上传机制而不是另一种上传机制的难易程度。


嗨,这是否意味着每次我们向 Web 服务器发布内容时,我们都必须提及 Content-type 是什么,以便让 Web 服务器知道它是否应该解码数据?即使我们自己制作 http 请求,我们也必须提到 Content-type 对吗?
@GMsoF,这是可选的。请参阅stackoverflow.com/a/16693884/632951。在为特定服务器制作特定请求时,您可能希望避免使用内容类型,以避免一般开销。
B
BiJ

关于上传 HTML5 画布图像数据的一点提示:

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

将 jQuery ajax 调用的 contentType 选项设置为 application/x-www-form-urlencoded 后,一切正常,base64 编码的数据被正确解释并成功保存为图像。

也许这对某人有帮助!


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

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

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

v
vikash vishnu

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

请参阅此答案以更好地理解:Exception: You cannot access body after reading from request's data stream