代码之家  ›  专栏  ›  技术社区  ›  Miguel Lattuada

HTTP多部分/表单数据。当二进制数据没有字符串表示时会发生什么?

  •  9
  • Miguel Lattuada  · 技术社区  · 7 年前

    我想写一个HTTP实现。

    几天来,我一直在寻找通过HTTP发送文件的方法 Content-Type: multipart/form-data ,我对浏览器(或任何HTTP客户端)如何创建此类请求非常感兴趣。

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

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

    在大多数文章和答案中,我发现了这段请求字符串,这对我来说很容易解释,所有这些都记录在RFCs中。。。

    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 回复  |  直到 7 年前
        1
  •  9
  •   regilero    7 年前

    你不应该试图处理 字符串 在正文的这一部分,您应该发送二进制数据,将其视为从资源中读取字节,然后不加更改地发送这些字节。

    因此,尤其是没有应用编码、没有utf-8、没有base64,HTTP不是一个具有ascii7限制的协议,就像smtp一样,在smtp中应用base64编码是为了确保只使用ascii7字符。

    根据定义,此数据没有字符串版本,查看原始HTTP传输(例如wireshark),您应该会看到二进制数据、字节等。

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

    现在,关于 示例

    当您使用 内容长度 而且没有多部分内容正文的长度正好是(内容长度)字节,因此解析您发送的数据的客户端只会读取这个字节数,并将整个原始数据视为正文内容(可能有mime类型和和编码信息,但这只是设置在HTTP协议之上的层的信息)。

    当您使用 传输编码:分块 ,原始二进制体被分成几部分,然后每个部分都以十六进制数(块的大小)和行尾标记作为前缀。最后有一个空标记。

    如果我们采取 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中的a):

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

    事实上 11000011:10101001 在utf-8中,两个字节: \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服务器的工作并不是转换源文档,您可以获取您所拥有的,还可以添加一个头来告诉客户端源文档上定义了什么编码。

    最后我们有了 多部分 传输正文的方式,就像在你的问题中一样,它与分块版本非常相似,除了这里使用了边界和中间标题,但对于这些边界、标题和行尾控制字符之间的二进制数据,这是相同的规则,里面的所有内容都是字节。。。