HTTP multipart / form-data。二进制数据没有字符串表示时会发生什么?

时间:2018-04-25 21:21:27

标签: sockets http networking protocols

我想写一个HTTP实现。

我一直在寻找有关使用Content-Type: multipart/form-data通过HTTP发送文件的几天,我真的很感兴趣浏览器(或任何HTTP客户端)如何创建这种请求。

我已经在stackoverflow上看了很多关于它的问题,如: How does HTTP file upload work?
What does enctype='multipart/form-data' mean?

我深入研究了RFC 2616(以及更新的版本),2046等等。但我没有找到明确的答案(显然我没有得到它背后的想法)。

最多文章和答案我发现这条请求字符串,这对我来说很容易解释,所有这些都记录在RFC中......

POST /upload?upload_progress_id=12344 HTTP/1.1
Host: localhost:3000
Content-Length: 1325
Origin: http://localhost:3000
... other headers ...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L

------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="MAX_FILE_SIZE"

100000
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="uploadedfile"; filename="hello.o"
Content-Type: application/x-object

... contents of file goes here ...
------WebKitFormBoundaryePkpFF7tjBAqx29L--

...实现一个HTTP客户端以任何语言构建一个字符串都很简单。

问题出现在... contents of file goes here ...,几乎没有关于什么“文件的内容”是。我知道它是具有特定类型和编码的二进制数据,但是很难想出字符串数据,我将如何在字符串中添加一个没有字符串表示的二进制数据。

我希望看到使用任何语言的HTTP协议的低级实现的示例。也许深入解释有关通过HTTP进行二进制数据传输,客户端如何创建请求以及服务器如何读取/解析它。

PD。我知道这个问题,我看起来很复杂,但大部分答案并不专注于解释二进制数据传输(如媒体)。

1 个答案:

答案 0 :(得分:2)

您不应该尝试在正文的这一部分处理字符串,您应该发送二进制数据,将其视为从资源读取字节并发送未更改的字节。

因此,特别是没有应用编码,没有utf-8,没有base64,HTTP不是具有ascii7限制的协议,如smtp,其中应用了base64编码以确保仅使用ascii7字符。

根据定义,没有此数据的字符串版本,并且查看原始HTTP传输(例如,使用wireshark),您应该看到二进制数据,字节和内容。

这就是为什么大多数HTTP服务器使用C来管理HTTP,它们解析每个字节的HTTP通信字节(因为协议头只是ascii 7,当然不是多字节字符)并且它们也可以读/写任意 身体的二进制数据非常容易(甚至使用系统调用,如 readfile 让内核管理二进制部分)。

现在,关于示例

当你使用 Content-Length 并且没有多部分东西时,正文(内容长度)字节长,所以解析你发送的数据的客户端将只读取这个字节数并将对待这个整个原始数据作为正文内容(可能有mime类型和编码信息,但这只是在HTTP协议之上设置的层的信息)。

当您使用 Transfer-Encoding:chunked 时,原始二进制正文被分成多个部分,每个部分的前缀都是十六进制数字(块的大小)和行尾标记。最后使用最终的空标记。

如果我们选择wikipedia example

4\r\n
Wiki\r\n
5\r\n
pedia\r\n
E\r\n
 in\r\n
\r\n
chunks.\r\n
0\r\n
\r\n

我们可以用任何字节替换每个ascii7字母,甚至是一个没有ascii7表示的字节,我会为每个实体字节使用*字符:

4\r\n
****\r\n
5\r\n
*****\r\n
E\r\n
**************\r\n
0\r\n
\r\n

所有其他字符都是HTTP协议的一部分(这里是一个分块的正文传输)。我还可以使用二进制数据的\n表示,并且仅为正文的每个字节发送空字节,即:

4\r\n
\0\0\0\0\0\r\n
5\r\n
\0\0\0\0\0\0\r\n
E\r\n
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\r\n
0\r\n
\r\n

这只是一种表示,我们也可以使用\xNN\NN表示,实际上这些是字节,8位(懒得编写本机构的0/1表示:-))

如果是示例的文本,而不是:

Wikipedia in\r\n
\r\n
chunks.

它可能是一个更复杂的字符,有多字节字符(这里是utf-8中的é):

Wikipédia in\r\n
\r\n
chunks.

这实际上是éf-8中的11000011:10101001,两个字节:\xc3\xa9表示中的\xNN,而不是简单的01100101 / \x65 / e字符。 HTTP主体现在是(看到第二个块大小是6而不是5):

4\r\n
Wiki\r\n
6\r\n
p\xc3\xa9dia\r\n
E\r\n
 in\r\n
\r\n
chunks.\r\n
0\r\n
\r\n

但这仅在源数据实际上是utf-8时有效,可能是另一种编码。默认情况下,除非您的Web服务器中有一些特定的配置设置,您在其中强制执行特定编码的源文档转换,这不是转换源文档的Web服务器的工作,您可以使用所拥有的,并且您可以添加一个标题来告诉客户端在源文档中定义了什么编码。

最后我们有 multipart 传输正文的方式,就像在你的问题中一样,它很像chunked版本,除了这里使用边界和中间头,但是对于这些之间的二进制数据边界,标题和行结尾控制字符它是相同的规则,内部的所有内容都只是字节......