HTTP Multipart 简介

参考文档:https://blog.adamchalmers.com/multipart/

Multipart 或者 "form-encoded data" 是个什么东西,我从来没有深入研究过,但是我却经常使用,主要是 http 库已经封装了他的使用。正确的使用能够使上传文件更快,使用更少的内存。

为什么使用 Multipart ?

Multipart 使上传文件更加高效。在 Multipart 之前,上传文件的标准是 "application/x-www-form-urlencoded",其实这种方式要求客户端在上传文件之前先进行 url 编码。如果文件是 ASCII 文本,编码有效,但如果是二进制数据,必须对每一个字节进行编码,相应的服务器端也会进行解码,这使得上传效率极低。如果想要上传多个文件,这就需要发起多个 http 请求。很显然,多个请求会增加连接时间,也就会有更多的延迟。

在 1998 年, RFC 2388 提出一个新的标准,"multipart/form-data",它允许一个 http 主体中发送许多文件,而且无需编码。不编码意味着可以节省大量的 CPU 时间,并且整体的大小并不会膨胀。

这个协议是为从 HTML 表单中上传文件而设计的,"multipart/form-data" 因此的名。但实际上可以用来上传任何想要上传的文件。规范中没有任何要求必须使用 <form> 或者 其他 HTML 标签。也就是说可以用这个协议从任何 Http 客户端向任何 Http 服务器上传文件。

multipart 的另一个好处是,服务器可以单独对每个部分进行流处理。例如:你如果将 5 个文件编码为 JSON 对象来上传(JSON 不能直接处理二进制文件,所以需要将每个文件转换为 base64 字符串),服务器需要把整个 JSON 对象缓冲到内存中,然后解码。如果是 multipart 可以流式处理每个文件,减少内存的使用并改善延迟,因为第一个文件传输完成的时候,就可以直接处理,不需要等待其他四个传输完成。

什么是 multipart ?

MIME 类型分为两种:discrete and multipart.

discrete 包含一个文件。例如 application/ (binary), image/, text/ 等。

multipart 是有许多文件,并且每个文件都可以有自己的 MIME 类型。

有两种 multipart 类型: message/ and multipart/(multipart 可以是一种类别,也可以是一种类型,但是无需困惑,常说的 multipart 指的就是 "multipart/form-data")。message/ 类型基本上不再用于任何东西,但 multipart/ 却非常重要。"multipart/form-data" 用于通过 HTML 表单将文件从浏览器发送到服务器。

注意: multipart 不一定要包含多个文件,也可以只包含一个。主要是使用 multipart 进行有效的二进制编码。

multipart 是如何实现的?

如果内容类型为 "multipart/form-data",那么在 HTTP 主体中包含多个部分(也就是多个文件)。每个部分都会由 "边界分隔符(boundary delimiter)"来分隔。而在 Http 消息中有一个 header 定义了 分隔符,所以服务器可以知道每个文件的边界在何处。

每个部分也有定义一些特殊的 header :

  • Content-Disposition 定义了每个部分的文件名或包含该部分的表单字段的名称(只有在使用实际的HTML表单元素时才有意义)。
  • Content-Type 定义了每个部分的文件类型(技术上说是MIME类型,但大致相当)。它默认为text/plain。非结构化的二进制数据应该使用application/octet-stream,但如果知道其类型,应该使用具体类型,例如application/zip,application/pdf,等等。

不能使用其他的 Header !

引用 RFC 7578

"The multipart/form-data media type does not support any MIME header fields in parts other than Content-Type, Content-Disposition, and (in limited circumstances) Content-Transfer-Encoding. Other header fields MUST NOT be included and MUST be ignored.".

下面是一个来自 Stack Overflow 的关于 HTTP 正文的实际例子。使用 multipart ,包含3个 GIF 文件。

POST /cgi-bin/qtest HTTP/1.1
Content-Type: multipart/form-data; boundary=2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Length: 514

--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Disposition: form-data; name="datafile1"; filename="r.gif"
Content-Type: image/gif

GIF87a.............,...........D..;
--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Disposition: form-data; name="datafile2"; filename="g.gif"
Content-Type: image/gif

GIF87a.............,...........D..;
--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f
Content-Disposition: form-data; name="datafile3"; filename="b.gif"
Content-Type: image/gif

GIF87a.............,...........D..;
--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f--

压缩

可以对整个 Multipart 响应进行 gzip 压缩,但是不能指定其中某一部分进行压缩。这是因为 HTTP 主体定义了整个消息的压缩头 。因此客户端没办法告诉服务器 哪个部分是压缩的,哪个部分不是。而且 Multipart 只允许有 3 个特定的 HTTP header,而压缩标识不在其中。

总结

"multipart "或 "form-encoded data "是一种包含多个文件的MIME类型。每个文件都有自己的MIME类型和名称。从历史上看,这比其他上传多个文件的方式有很大的改进,因为它可以将每个文件作为原始二进制文件发送,不需要额外的编码或转义。

在这片文章之前,我并不觉得这是什么很先进的东西,毕竟自首次发布以来,已经过去 25 年。也许我们现在有更好的方法来上传文件了。

其实 JSON 的方式也能满足日常的需求,上传文件也很容易组成 JSON。如果每个文件都是一个 JSON 体,并且也可以很容易进行组合,只需要把 n 个独立的 JSON 体 组合成一个 有 n 个字段的大体,但是这样性能不好。

必须对文件内容进行base64处理,因为JSON只能处理文本,不能处理二进制。在解码之前,服务器必须将整个JSON体缓冲到RAM中。

multipart/form-data 可以有效地将多个文件上传组合在一起。而这种权衡带来了一些复杂性,比如边界和内容处置。显然,multipart/form-data 已经足够好,使用它的地方也有很多。

期待 能够了解替代 multipart 的方案,或者更好的上传方式。如您知道,请联系我。

# java  http 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×