當我提交帶有附件的簡單表格時: 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
標頭的一部分。
詳細信息在這裏 。