上傳文件multipart/form-data深刻解析

上傳文件是一個前端常見的需求,可是爲何上傳文件必須使用content-type:multipart/form-data做爲請求頭?html

起源:multipart/form-data定義源頭

multipart/form-data最初由 《RFC 1867: Form-based File Upload in HTML》文檔提出。前端

Since file-upload is a feature that will benefit many applications, this proposes an extension to HTML to allow information providers to express file upload requests uniformly, and a MIME compatible representation for file upload responses.linux

1867文檔簡介中說明文件上傳做爲一種常見的需求,在目前(1995年)的html中的form表單格式中還不支持,所以提出了一種兼容此需求的mime type。express

The encoding type application/x-www-form-urlencoded is inefficient for sending large quantities of binary data or text containing non-ASCII characters. Thus, a new media type,multipart/form-data, is proposed as a way of efficiently sending the values associated with a filled-out form from client to server.windows

1867文檔中也寫了爲何要新增一個類型,而不使用舊有的application/x-www-form-urlencoded:由於此類型不適合用於傳輸大型二進制數據或者包含非ASCII字符的數據。日常咱們使用這個類型都是把表單數據使用url編碼後傳送給後端,二進制文件固然沒辦法一塊兒編碼進去了。因此multipart/form-data就誕生了,專門用於有效的傳輸文件。後端

On the other hand, the 'multipart' mechanisms are well established, simple to implement on both the sending client and receiving server, and as efficient as other methods of dealing with multiple combinations of binary data.瀏覽器

文檔中還解釋了爲何要沿用multipart這個機制。multipart機制的定義在《RFC 1314 - A File Format for the Exchange of Images in the Internet》的7.2 The Multipart Content-Type小節bash

但這個1867文檔中對 multipart/form-data 的具體格式並無寫的很是詳細,只在第六部分的【Examples】當中給了一個很基本的範例,因此 1998 年又有了一份新的《RFC 2388:Returning Values from Forms: multipart/form-data 》,來闡明不只在HTTP協議下傳輸文件、並且使用郵件傳輸文件時 multipart/form-data 中的各個部分的具體格式[1]。分別對3.Definition of multipart/form-data multipart/form-data的定義;4.Use of multipart/form-data具體使用格式和方法作了更詳細的闡述,其中第四章包括:服務器

4.1封裝線的格式
4.2多文件時應使用multipart/mixed

(編者注:實驗了一下任何瀏覽器發送多個文件時都沒有實現這個配置[2019-4-5])但若是發送一封郵件,並使用fiddler抓包,能夠看出郵件源碼格式仍是有嚴格遵循2388文檔規範的。網絡

POST https://outlook.office365.com/Microsoft-Server-ActiveSync?jAEECBBv8Usp+29k8LM6azAOecgZBAAAAAALV2luZG93c01haWz/AwEMAQ== HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: application/vnd.ms-sync
Authorization: ...
X-MS-WL: WindowsPhone/8.0
X-WLAS-Tracing: true
Content-Length: 1498610
Host: outlook.office365.com
Cookie: DefaultAnchorMailbox=yyyy@qq.com

j  EQoid{0:b} P ۻWMIME-Version: 1.0
To: "xxxx@gmail.com" <xxxx@gmail.com>
From: 
Subject: =?utf-8?Q?test=E9=82=AE=E4=BB=B6=E6=A0=BC=E5=BC=8F?=
Date: Fri, 5 Apr 2019 19:43:35 +0800
Importance: normal
X-Priority: 3
Content-Type: multipart/mixed;
	boundary="_8A0F7060-4F31-4015-9BED-E2D8315920AF_"

--_8A0F7060-4F31-4015-9BED-E2D8315920AF_
Content-Type: multipart/alternative;
	boundary="_997001FE-F8E8-4F97-ACC0-BBA66B0710A5_"

--_997001FE-F8E8-4F97-ACC0-BBA66B0710A5_
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset="utf-8"

DQoNCuWPkemAgeiHqiBXaW5kb3dzIDEwIOeJiOmCruS7tuW6lOeUqA0KDQo=

--_997001FE-F8E8-4F97-ACC0-BBA66B0710A5_
Content-Transfer-Encoding: quoted-printable
Content-Type: text/html; charset="utf-8"

文件內容....

--_997001FE-F8E8-4F97-ACC0-BBA66B0710A5_--

--_8A0F7060-4F31-4015-9BED-E2D8315920AF_
Content-Type: image/png; name="upload.png"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="upload.png"

圖片內容...
複製代碼

實例分析

下面舉幾個栗子: 先使用get方法和post方法,但不寫enctype,即以默認的application/x-www-form-urlencoded表格數據格式進行表單請求:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
</head>

<body>
	使用get請求,使用application/x-www-form-urlencoded請求頭
    <form method="get" action="xxx">
        <label for="name">name</label>
        <input type="text" id="name" name="name">
        <label for="file">file</label>
        <input type="file" id="file" name="file" />
        <input type="submit" value="submit" name="submit">
    </form>
    使用post請求,使用application/x-www-form-urlencoded請求頭
    <form method="post" action="xxx">
        <label for="name">name</label>
        <input type="text" id="name" name="name">
        <label for="file">file</label>
        <input type="file" id="file" name="file" />
        <input type="submit" value="submit" name="submit">
    </form>
</body>
</html>
複製代碼

windows上使用任何一種服務器(這裏使用了iis起在了本機地址1333端口)vs code的go live也能夠,並使用了fiddler監聽網絡請求。若是是在linux能夠也可使用在終端中輸入:while :; do clear; nc -l localhost 1333; sleep 2; done方式監聽端口。 網絡請求區別以下:

get post對比圖
圖1.1

發現post方法和get方法都只是把文件名編碼進了url中,文件內容沒法獲得,這也證明了上述文檔中的內容,使用application/x-www-form-urlencoded沒法實現文件上傳。

若使用enctype='multipart/form-data',並分別使用post和get方法提交表單則會獲得以下結果:

使用get請求,使用multipart/form-data請求頭
    <form method="get" action="xxx" enctype="multipart/form-data">
        <label for="name">name</label>
        <input type="text" id="name" name="name">
        <label for="file">file</label>
        <input type="file" id="file" name="file" />
        <input type="submit" value="submit" name="submit">
    </form>
    使用post請求,使用application/x-www-form-urlencoded請求頭
    <form method="post" action="xxx" enctype="multipart/form-data">
        <label for="name">name</label>
        <input type="text" id="name" name="name">
        <label for="file">file</label>
        <input type="file" id="file" name="file" />
        <input type="submit" value="submit" name="submit">
    </form>
複製代碼
get post對比圖
圖1.2

能夠看到在上傳文件中使用get方法是無效的,依然只能獲得文件名。而post結合multipart/form-data才能真正將文件內容傳入請求體。

看到提交文件的格式使用一長串字符做爲boundtry封裝線對字段進行分割。這也很符合multipart多個部分的語義,包含了多個部分集,每一部分都包含了一個content-desposition頭,其值爲form-data,以及一個name屬性,其值爲表單的字段名,文件輸入框還可使用filename參數指定文件名。content-type非必須屬性,其值會根據文件類型進行變化,默認值是text/plain。multipart的每個part上方是邊緣,最後一個part的下方添加一個邊緣。

參考

  1. RFC 1867: Form-based File Upload in HTML
  2. RFC 2388: Returning Values from Forms: multipart/form-data
  3. 爲何上傳文件的表單須要設置enctype="multipart/form-data"
  4. 表單提交中的 x-www-form-urlencoded 和 multipart/form-data
  5. post Upload上傳文件中multipart/form-data 作的那些事
  6. 阮一峯的網絡日誌:MIME筆記
  7. W3C文檔規範:17 Forms
  8. 7.2 The Multipart Content-Type(1992)
相關文章
相關標籤/搜索