HTTPfile upload如何工作?

当我用附件提交一个简单的表格时,

<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的内部工作。

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

 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编码表单参数,而是作为请求主体中的多部分文档中的部分发送。

在上面的例子中,您可以看到inputMAX_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

创build要上传的文件:

 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 'a\xCF\x89b' > binary 

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

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

在浏览器中打开HTML,select文件并点击提交并检查terminal。

nc打印收到的请求。

经testing:Ubuntu 14.04.3, nc BSD 1.105,Firefox 40。

多部分/格式数据

火狐发送:

 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来validation 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将内容types设置为multipart/form-data并表示字段由给定的boundarystring。

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

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

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

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

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

    如何确切地被问到: 如何由浏览器确定上传文件的MIMEtypes?

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

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

火狐发送:

 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是以一个字节发送的,而不可打印的0xCF0xCF0x89分别占用3个字节%CF%89

对照

file upload通常包含大量不可打印的字符(例如图像),而文本表单几乎从不做。

从我们看到的例子来看:

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

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

因此,即使我们可以用application/x-www-form-urlencoded发送文件,我们也不想,因为效率太低了。

但是对于在文本字段中find的可打印字符,这并不重要,生成的开销较小,所以我们只是使用它。

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

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

如果你想发送文件作为唯一的内容,那么你可以直接将它添加为请求正文,并将Content-Type头设置为您正在发送的文件的MIMEtypes。 文件名称可以添加到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消息可能会在标题行之后发送一个数据体。 在一个响应中,这就是被请求的资源返回给客户端的地方(消息主体最常见的用途),或者如果有错误的话可能是解释性文本。 在请求中,这是用户input的数据或上传的文件被发送到服务器的地方。

http://www.tutorialspoint.com/http/http_messages.htm

我有这个示例Java代码:

 <!-- language: java --> 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文件:

 <!-- language: html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>File Upload!</title> </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> 

最后是我将用于testing目的的文件,名为a.dat的文件具有以下内容:

 0x39 0x69 0x65 

如果你将上面的字节解释为ASCII或UTF-8字符,他们实际上将代表:

 9ie 

所以让我们运行我们的Java代码,打开chrome中的test.html ,上传a.dat并提交表单,看看我们的服务器收到了什么:

 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字符。 你也可以select以原始字节读取它们。

 Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

实际上是这里的最后一个HTTP头。 之后是HTTP Body,其中可以看到我们上传的文件的元数据和内容。