How does HTTP file upload work?
当我提交一个附加文件的简单表格时:
1 2 3 4 5 | <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的内部工作原理。
让我们来看看当您选择文件并提交表单时会发生什么(为简洁起见我截断了标题):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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编码表单参数,而是作为请求正文中的多部分文档中的部分发送。
在上面的示例中,您可以看到输入
详细信息在这里。
How does it send the file internally?
格式称为
我要去:
- 添加一些HTML5引用
- 用形式提交示例解释他为什么是正确的
HTML5参考
-
x-www-urlencoded -
multipart/form-data (规格指向RFC2388) -
text-plain 。这是"计算机无法可靠解释",因此不应该在生产中使用,我们不会进一步研究它。
如何生成示例
一旦你看到每个方法的一个例子,就会明白它们是如何工作的,以及何时应该使用每个方法。
您可以使用以下方法生成示
-
nc -l 或ECHO服务器:接受GET / POST请求的HTTP测试服务器 - 用户代理,如浏览器或cURL
将表单保存到最小的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> upload </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ω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> |
我们将默认文本值设置为
创建要上传的文件:
1 2 3 4 5 6 | echo 'Content of a.txt.' > a.txt echo '<!DOCTYPE html>Content of a.html.' > a.html # Binary file containing 4 bytes: 'a', 1, 2 and 'b'. printf 'a\xCF\x89b' > binary |
运行我们的小型echo服务器:
1 | while true; do printf '' | nc -l 8000 localhost; done |
在浏览器上打开HTML,选择文件,然后单击"提交"并检查终端。
测试:Ubuntu 14.04.3,
多部分/格式数据
Firefox发送:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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>Content of a.html. -----------------------------735323031399963166993862150 Content-Disposition: form-data; name="file3"; filename="binary" Content-Type: application/octet-stream aωb -----------------------------735323031399963166993862150-- |
对于二进制文件和文本字段,字面
1 | 61 CF 89 62 |
被发送(
因此很明显:
-
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266 将内容类型设置为multipart/form-data ,并表示字段由给定的boundary 字符串分隔。 -
每个字段在其数据之前获得一些子标题:
Content-Disposition: form-data; ,字段name ,filename ,后跟数据。服务器读取数据直到下一个边界字符串。浏览器必须选择不会出现在任何字段中的边界,因此这就是边界可能因请求而异的原因。
因为我们有唯一的边界,所以不需要对数据进行编码:二进制数据按原样发送。
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
现在将
Firefox发送:
1 2 3 4 5 6 | 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 |
显然,文件数据没有发送,只有基本名称。所以这不能用于文件。
至于文本字段,我们看到通常可打印的字符(如
对照
文件上传通常包含许多不可打印的字符(例如图像),而文本形式几乎从不这样做。
从我们看到的例子中可以看到:
-
multipart/form-data :向消息添加几个字节的边界开销,并且必须花一些时间计算它,但是在一个字节中发送每个字节。 -
application/x-www-form-urlencoded :每个字段(& )具有单字节边界,但为每个不可打印字符添加3x的线性开销因子。
因此,即使我们可以发送带有
但是对于在文本字段中找到的可打印字符,它无关紧要并且产生的开销较少,因此我们只使用它。
将文件作为二进制内容发送(无表格或FormData上传)
在给定的答案/示例中,文件(很可能)使用HTML表单或使用FormData API上传。该文件只是请求中发送的数据的一部分,因此
如果要将文件作为唯一内容发送,则可以直接将其添加为请求正文,并将
1 2 3 4 5 6 7 8 9 10 11 | 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); |
如果您不(想)使用表单而您只想上传一个文件,这是将您的文件包含在请求中的最简单方法。
我有这个示例Java代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import java.io.*; import java.net.*; import java.nio.charset.StandardCharsets; public class TestClass { public static void main(String[] args) throws IOException { final ServerSocket socket = new ServerSocket(8081); final Socket accept = socket.accept(); final InputStream inputStream = accept.getInputStream(); final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); char readChar; while ((readChar = (char) inputStreamReader.read()) != -1) { System.out.print(readChar); } inputStream.close(); accept.close(); System.exit(1); } } |
我有这个test.html文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> File Upload! </head> <body> <form method="post" action="http://localhost:8081" enctype="multipart/form-data"> <input type="file" name="file" id="file"> <input type="submit"> </form> </body> </html> |
最后我将用于测试目的的文件名为a.dat,其内容如下:
1 | 0x39 0x69 0x65 |
如果您将上面的字节解释为ASCII或UTF-8字符,它们实际上将代表:
1 | 9ie |
所以我们运行我们的Java代码,在我们最喜欢的浏览器中打开test.html,上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | POST / HTTP/1.1 Host: localhost:8081 Connection: keep-alive Content-Length: 196 Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Origin: null Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary06f6g54NVbSieT6y DNT: 1 Accept-Encoding: gzip, deflate Accept-Language: en,en-US;q=0.8,tr;q=0.6 Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF ------WebKitFormBoundary06f6g54NVbSieT6y Content-Disposition: form-data; name="file"; filename="a.dat" Content-Type: application/octet-stream 9ie ------WebKitFormBoundary06f6g54NVbSieT6y-- |
好吧,我看到字符9ie并不奇怪,因为我们告诉Java打印它们将它们视为UTF-8字符。您也可以选择将它们作为原始字节读取..
1 | Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF |
实际上是这里的最后一个HTTP标头。之后是HTTP Body,其中可以看到我们上传的文件的元和内容。
An HTTP message may have a body of data sent after the header lines. In a response, this is where the requested resource is returned to the client (the most common use of the message body), or perhaps explanatory text if there's an error. In a request, this is where user-entered data or uploaded files are sent to the server.
http://www.tutorialspoint.com/http/http_messages.htm