深刻解析 multipart/form-data

一個 HTML 表單中的 enctype 有三種類型

  • application/x-www-urlencoded
  • multipart/form-data
  • text-plain

默認狀況下是 application/x-www-urlencoded,當表單使用 POST 請求時,數據會被以 x-www-urlencoded 方式編碼到 Body 中來傳送,
而若是 GET 請求,則是附在 url 連接後面來發送。html

GET 請求只支持 ASCII 字符集,所以,若是咱們要發送更大字符集的內容,咱們應使用 POST 請求。git

注意

若是要發送大量的二進制數據(non-ASCII),"application/x-www-form-urlencoded" 顯然是低效的,由於它須要用 3 個字符來表示一個 non-ASCII 的字符。所以,這種狀況下,應該使用 "multipart/form-data" 格式。編程

The content type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.瀏覽器

application/x-www-urlencoded

咱們在經過 HTTP 向服務器發送 POST 請求提交數據,都是經過 form 表單形式提交的,代碼以下:服務器

<FORM method="post" action="http://w.sohu.com" >
    <INPUT type="text" name="txt1">
    <INPUT type="text" name="txt2">
 </FORM>

提交時會向服務器端發出這樣的數據(已經去除部分不相關的頭信息),數據以下:session

POST / HTTP/1.1
Content-Type:application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: w.sohu.com
Content-Length: 21
Connection: Keep-Alive
Cache-Control: no-cache
 
txt1=hello&txt2=world

對於普通的 HTML Form POST請求,它會在頭信息裏使用 Content-Length 註明內容長度。
請求頭信息每行一條,空行以後即是 Body,即「內容」(entity)。內容的格式是在頭信息中的 Content-Type 指定的,如上是 application/x-www-form-urlencoded,這意味着消息內容會通過 URL 格式編碼,就像在 GET請 求時 URL 裏的 QueryString 那樣。txt1=hello&txt2=worldapp

multipart/form-data

multipart/form-data 定義在 rfc2388 中,最先的 HTTP POST 是不支持文件上傳的,給編程開發帶來不少問題。可是在1995年,ietf 出臺了 rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上傳。因此 Content-Type 的類型擴充了multipart/form-data 用以支持向服務器發送二進制數據。所以,發送 POST 請求時候,表單 <form> 屬性 enctype 共有二個值可選,這個屬性管理的是表單的 MIME 編碼:curl

① application/x-www-form-urlencoded (默認值)
② multipart/form-datagitlab

注:form 表單中 enctype 的默認值是 enctype="application/x- www-form-urlencoded".post

經過 form 表單提交文件操做以下:

<FORM method="POST" action="http://w.sohu.com/t2/upload.do" enctype="multipart/form-data">
    <INPUT type="text" name="city" value="Santa colo">
    <INPUT type="text" name="desc">
    <INPUT type="file" name="pic">
 </FORM>

瀏覽器將會發送如下數據:

POST /t2/upload.do HTTP/1.1
User-Agent: SOHUWapRebot
Accept-Language: zh-cn,zh;q=0.5
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Content-Length: 60408
Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Host: w.sohu.com

--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data; name="city"

Santa colo
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name="desc"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
 
...
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name="pic"; filename="photo.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
 
... binary data of the jpg ...
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC--

從上面的 multipart/form-data 格式發送的請求的樣式來看,它包含了多個 Parts,每一個 Part 都包含頭信息部分,
Part 頭信息中必須包含一個 Content-Disposition 頭,其餘的頭信息則爲可選項, 好比 Content-Type 等。

Content-Disposition 包含了 type 和 一個名字爲 name 的 parameter,type 是 form-data,name 參數的值則爲表單控件(也即 field)的名字,若是是文件,那麼還有一個 filename 參數,值就是文件名。

好比:

Content-Disposition: form-data; name="user"; filename="hello.txt"

上面的 "user" 就是表單中的控件的名字,後面的參數 filename 則是點選的文件名。
對於可選的 Content-Type(若是沒有的話),默認就是 text/plain

注意:

若是文件內容是經過填充表單來得到,那麼上傳的時候,Content-Type 會被自動設置(識別)成相應的格式,若是無法識別,那麼就會被設置成 "application/octet-stream"
若是多個文件被填充成單個表單項,那麼它們的請求格式則會是 multipart/mixed。

若是 Part 的內容跟默認的 encoding 方式不一樣,那麼會有一個 "content-transfer-encoding" 頭信息來指定。

下面,咱們填充兩個文件到一個表單項中,行程的請求信息以下:

Content-Type: multipart/form-data; boundary=AaB03x

--AaB03x
Content-Disposition: form-data; name="submit-name"

Larry
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed; boundary=BbC04y

--BbC04y
Content-Disposition: file; filename="file1.txt"
Content-Type: text/plain

... contents of file1.txt ...
--BbC04y
Content-Disposition: file; filename="file2.gif"
Content-Type: image/gif
Content-Transfer-Encoding: binary

...contents of file2.gif...
--BbC04y--
--AaB03x--

Boundary 分隔符

每一個部分使用 --boundary 分割開來,最後一行使用 --boundary-- 結尾。

實驗

To see exactly what is happening, use nc -l and an user agent like a browser or cURL.

Save the form to an .html file:

<FORM action="http://localhost:8000" method="post" enctype="multipart/form-data">
    <p><INPUT type="text" name="text" value="text default">
    <p><INPUT type="file" name="file1">
    <p><INPUT type="file" name="file2">
    <p><BUTTON type="submit">Submit</BUTTON>
</FORM>

Create files to upload:

echo 'Content of a.txt.' > a.txt
echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html

Run:

nc -l localhost 8000

Open the HTML on your browser, select the files and click on submit and check the terminal.

nc prints the request received. Firefox sent:

POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: 554

-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"

text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain

Content of a.txt.

-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html

<!DOCTYPE html><title>Content of a.html.</title>

-----------------------------9051914041544843365972754266--

Aternativelly, cURL should send the same POST request as your a browser form:

nc -l localhost 8000
curl -F "text=default" -F "file1=@a.html" -F "file1=@a.txt" localhost:8000

You can do multiple tests with:

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

參考:

https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
https://stackoverflow.com/questions/4526273/what-does-enctype-multipart-form-data-mean/28380690#28380690
https://tools.ietf.org/html/rfc2388
https://stackoverflow.com/questions/913626/what-should-a-multipart-http-request-with-multiple-files-look-like

相關文章
相關標籤/搜索