当我提交带有附件的简单表格时: html
<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正文发送? 在此请求的标题中,没有看到与文件名相关的任何内容。 html5
我只是想知道发送文件时HTTP的内部工做原理。 java
它如何在内部发送文件? web
该格式称为multipart/form-data
,请参见 : enctype ='multipart / form-data'是什么意思? 算法
我要去: 浏览器
enctype
有三种可能性 : 服务器
x-www-urlencoded
multipart/form-data
(规范指向RFC2388 ) text-plain
。 这是“没法可靠地由计算机解释的”,所以永远不要在生产中使用它,咱们也不会对其进行深刻研究。 一旦您看到每种方法的示例,就会很清楚它们的工做方式以及什么时候使用每种方法。 app
您可使用如下示例生成示例: socket
nc -l
或ECHO服务器: HTTP测试服务器接受GET / POST请求 将表单保存到最小的.html
文件中: ide
<!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ω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ω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 'a\xCF\x89b' > binary
运行咱们的小回声服务器:
while true; do printf '' | nc -l 8000 localhost; done
在浏览器上打开HTML,选择文件,而后单击Submit并检查终端。
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;
,字段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类型?
如今更改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
显然,没有发送文件数据,仅发送了基名。 所以,这不能用于文件。
对于文本字段,咱们看到一般可打印的字符(例如a
和b
)以一个字节发送,而不可打印的0xCF
(例如0xCF
和0x89
)每一个占用3个字节 : %CF%89
!
文件上载一般包含许多不可打印的字符(例如图像),而文本形式几乎历来没有。
从示例中咱们能够看到:
multipart/form-data
:给消息增长了一些边界开销字节,而且必须花费一些时间来计算它,可是每一个字节以一个字节发送。
application/x-www-form-urlencoded
:每一个字段( &
)具备单个字节边界,但为每一个不可打印字符增长了3倍的线性开销因子。
所以,即便咱们能够发送带有application/x-www-form-urlencoded
,咱们也不application/x-www-form-urlencoded
,由于它效率很低。
可是对于在文本字段中找到的可打印字符来讲,这可有可无,而且产生的开销更少,所以咱们只使用它。
在给定的答案/示例中,文件(最有可能)是使用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);
若是您不(不想)使用表单,而只对上传单个文件感兴趣,这是在请求中包括文件的最简单方法。
我有如下示例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文件:
<!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>
最后,我将用于测试目的的文件a.dat具备如下内容:
0x39 0x69 0x65
若是您将上述字节解释为ASCII或UTF-8字符,则它们实际上将表示:
9ie
所以,让咱们运行咱们的Java代码,在咱们喜欢的浏览器中打开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字符。 您也能够选择将它们读取为原始字节。
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF
其实是这里的最后一个HTTP标头。 以后是HTTP正文,能够在其中实际看到咱们上传的文件的元数据和内容。
HTTP消息可能具备在标头行以后发送的数据主体。 在响应中,这是将请求的资源返回给客户端的地方(消息正文的最经常使用用法),若是有错误,也多是解释性文本。 在请求中,将用户输入的数据或上载的文件发送到服务器。
http://www.tutorialspoint.com/http/http_messages.htm
让咱们看一下选择文件并提交表单时发生的状况(为简洁起见,我已将标题删节了):
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
标头的一部分。
详细信息在这里 。