HTTP文件上传如何工作?

当我提交一个附带文件的简单表单时:

<form enctype="multipart/form-data" action="http://localhost:3000/upload?upload_progress_id=12344" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>

它如何在内部发送文件? 该文件是否作为数据的HTTP身体的一部分发送? 在这个请求的头文件中,我没有看到与文件名相关的任何内容。

我只想知道发送文件时HTTP的内部工作原理。


让我们来看看当你选择一个文件并提交你的表单时(为了简洁起见,我已经截断了标题):

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--

表单参数(包括文件数据)不是URL编码表单参数,而是作为请求正文中的多部分文档中的部分发送。

在上面的示例中,您可以看到输入MAX_FILE_SIZE ,其中包含表单中MAX_FILE_SIZE的值以及包含文件数据的部分。 文件名称是Content-Disposition标题的一部分。

完整的细节在这里。


它如何在内部发送文件?

这个格式被称为multipart/form-data ,问题在于:enctype ='multipart / form-data'是什么意思?

我要去:

  • 添加更多HTML5参考
  • 用一个表单提交示例解释他为什么正确
  • HTML5参考

    enctype有三种可能性:

  • x-www-urlencoded
  • multipart/form-data (spec指向RFC2388)
  • text-plain 。 这是“不可靠的电脑解释”,所以它不应该用于生产,我们不会进一步考虑。
  • 如何生成示例

    一旦你看到每种方法的例子,就会明白它们是如何工作的以及何时应该使用每一种方法。

    您可以使用以下示例生成示例

  • nc -l或ECHO服务器
  • 像浏览器或cURL这样的用户代理
  • 将表单保存为最小的.html文件:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8"/>
      <title>upload</title>
    </head>
    <body>
      <form action="http://localhost:8000" method="post" enctype="multipart/form-data">
      <p><input type="text" name="text1" value="text default">
      <p><input type="text" name="text2" value="a&#x03C9;b">
      <p><input type="file" name="file1">
      <p><input type="file" name="file2">
      <p><input type="file" name="file3">
      <p><button type="submit">Submit</button>
    </form>
    </body>
    </html>
    

    我们将默认文本值设置a&#x03C9;b ,这意味着aωb因为ωU+03C9 ,它们是UTF-8中的字节61 CF 89 62

    创建要上传的文件:

    echo 'Content of a.txt.' > a.txt
    
    echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html
    
    # Binary file containing 4 bytes: 'a', 1, 2 and 'b'.
    printf 'axCFx89b' > binary
    

    运行我们的小回声服务器:

    while true; do printf '' | nc -l 8000 localhost; done
    

    在浏览器中打开HTML,选择文件并点击提交并检查终端。

    nc打印收到的请求。

    测试:Ubuntu 14.04.3, nc BSD 1.105,Firefox 40。

    多部分/格式数据

    Firefox发送:

    POST / HTTP/1.1
    [[ Less interesting headers ... ]]
    Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
    Content-Length: 834
    
    -----------------------------735323031399963166993862150
    Content-Disposition: form-data; name="text1"
    
    text default
    -----------------------------735323031399963166993862150
    Content-Disposition: form-data; name="text2"
    
    aωb
    -----------------------------735323031399963166993862150
    Content-Disposition: form-data; name="file1"; filename="a.txt"
    Content-Type: text/plain
    
    Content of a.txt.
    
    -----------------------------735323031399963166993862150
    Content-Disposition: form-data; name="file2"; filename="a.html"
    Content-Type: text/html
    
    <!DOCTYPE html><title>Content of a.html.</title>
    
    -----------------------------735323031399963166993862150
    Content-Disposition: form-data; name="file3"; filename="binary"
    Content-Type: application/octet-stream
    
    aωb
    -----------------------------735323031399963166993862150--
    

    对于二进制文件和文本字段,字节61 CF 89 62 (UTF-8中的aωb )以字面形式发送。 你可以使用nc -l localhost 8000 | hd来验证 nc -l localhost 8000 | hd ,它说,字节:

    61 CF 89 62
    

    被发送( 61 =='a'和62 =='b')。

    因此很明显:

  • Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266 Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266将内容类型设置为multipart/form-data并表示字段由给定的boundary字符串。

  • 每个字段在其数据之前都会获得一些子标题: Content-Disposition: form-data; ,字段namefilename ,后面跟着数据。

    服务器读取数据直到下一个边界字符串。 浏览器必须选择不会出现在任何字段中的边界,所以这就是为什么边界可能因请求而不同。

    由于我们有独特的边界,因此不需要编码数据:二进制数据按原样发送。

    TODO:什么是最佳边界大小( log(N)我打赌),以及找到它的算法的名称/运行时间? 问在:https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type由浏览器自动确定。

    如何确定它的确切位置:如何通过浏览器确定上传文件的MIME类型?

  • 应用程序/ x-WWW窗体-urlencoded

    现在将enctype更改为application/x-www-form-urlencoded ,重新加载浏览器并重新提交。

    Firefox发送:

    POST / HTTP/1.1
    [[ Less interesting headers ... ]]
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 51
    
    text1=text+default&text2=a%CF%89b&file1=a.txt&file2=a.html&file3=binary
    

    很显然,文件数据并未发送,只有基本名称。 所以这不能用于文件。

    至于文本字段,我们看到像ab这样a通常的可打印字符以一个字节发送,而不可打印的0xCF0xCF0x89分别占用3个字节%CF%89

    对照

    文件上传通常包含很多不可打印的字符(例如图像),而文本表单几乎从不做。

    从我们看到的例子来看:

  • multipart/form-data :将几个字节的边界开销添加到消息中,并且必须花费一些时间计算它,但将每个字节发送一个字节。

  • application/x-www-form-urlencoded :每个字段( & )具有单个字节边界,但为每个不可打印字符添加3x的线性开销因子。

  • 因此,即使我们可以发送带有application/x-www-form-urlencoded ,我们也不想这样做,因为它效率太低。

    但是对于在文本字段中找到的可打印字符,它无关紧要并且生成的开销较小,因此我们只是使用它。


    将文件作为二进制内容发送(无需Form或FormData即可上传)

    在给定的答案/例子中,文件(最有可能)是用HTML表单或使用FormData API上传的。 该文件只是请求中发送的数据的一部分,因此是multipart/form-data Content-Type标头。

    如果要将文件作为唯一内容发送,则可以直接将其添加为请求正文,并将Content-Type标题设置为要发送的文件的MIME类型。 文件名可以添加到Content-Disposition标题中。 你可以像这样上传:

    var xmlHttpRequest = new XMLHttpRequest();
    
    var file = ...file handle...
    var fileName = ...file name...
    var target = ...target...
    var mimeType = ...mime type...
    
    xmlHttpRequest.open('POST', target, true);
    xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
    xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
    xmlHttpRequest.send(file);
    

    如果您没有(想要)使用表单,并且您只想上传一个文件,这是将文件包含在请求中的最简单方法。

    链接地址: http://www.djcxy.com/p/7091.html

    上一篇: How does HTTP file upload work?

    下一篇: Mixing REST API plural and singular for different resources?