你真的知道網頁上傳檔案背後的原理嗎?
今年第三季度工作上完成了一個比較有意思的專案,類似於外包的性質,主要任務就是提供一大堆API,其中一個API是上傳附件,完成開發後,對方的程式員問我,這個API怎麼呼叫,當時我就愣住了,因為自己也沒想過這個問題,一般情況下,我就是用 Curl 命令列或 Postman 測試API的。
針對檔案上傳,我使用 Curl 測試,比如:
# 使用@引用一個檔案 $ curl -F"param=value" -F"file=@/path/file.png" http://localhost/api.php
如果使用Postman測試,如下圖:
注意觀察form-data和File標籤。
看上去是不是很簡單,現在換個角度,你想以程式碼的方式上傳檔案API,怎麼辦?也非常簡單,很多開發語言有很多現成的庫,比如PHP通過Curl庫上傳檔案非常容易。再深入想一想,如果不使用這些庫,怎麼上傳檔案?可能會難倒很多人,所以這篇文章簡單講講檔案上傳的原理,其實就是根據HTTP協議的定義,封裝一個HTTP訊息體。
MIME
首先必須先講下MIME(Multipurpose Internet Mail Extensions),它並不是HTTP協議的一部分,就像我們每個人都是獨一無二的,有自己的屬性,網際網路上每個資源也有屬性,比如有些資源是圖片,有些是視訊,有些是HTML頁面,MIME規定了每種資源的型別,這個型別不是隨便定義的,由IANA負責登記和維護。
說的有點難理解,比如你看到一個URL地址,http://localhost/image.png,我們其實並不是通過.png字尾判斷資源型別的,而是通過MIME來獲知該資源型別的,這個圖片的MIME可能就是image/png(至於客戶端如何知曉資源的MIME型別,後面會講),現在是不是有了點感性的認識了。
MIME型別結構如下:
type/subtype
type相當於某些型別的集合,而subtype相當於子型別。以image/png為例,image表示圖片型別集合,png表示某種型別圖片。
讓我們看幾個比較重要的MIME型別:
text/plain text/html application/octet-stream multipart/form-data
其實本篇文章的主角就是multipart/form-data,再等一等,先彆著急,再一次說說MIME,從它的英文全稱來看,它和mail有關係,是由mail應用定義而來的,一封郵件由多種資源組成,為了將不同型別的資源組成在郵件中,MIME產生了。隨著網際網路Web的發展,MIME的作用越來越多,擴充套件也越來越多,MIME概念也逐步移到了Web。
Content Type
現在我們定義了每種資源的MIME型別,那麼客戶端如何知曉每種資源的MIME型別呢?這時候就要使用Content-Type HTTP Header 頭了,比如我們請求一個資源,Web伺服器在傳送資源的時候,傳送了“content-type:image/png” Header 頭,這樣客戶端就知道該資源是一個png圖片了。
如果客戶端傳送了一個 “Content-Type: multipart/form-data;”,代表客戶端要上傳一個附件。
也就是說 Content-Type 後面的值就是一個 MIME 型別,聰明的同學也猜到了,上傳附件和 multipart/form-data MIME 型別有關,確實是!
multipart/form-data
multipart/form-data 這個MIME型別並不是標準的MIME型別,而是因為Web的需要擴充套件而來的,我們在開發網頁的時候為了上傳一個檔案,會輸入以下的HTML標籤:
<form action="upload.php" method="post" enctype="multipart/form-data"> Select image to upload: <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="Upload Image" name="submit"> </form>
關於HTML表單上傳可以參考 https://www.w3.org/TR/html5/sec-forms.html#multipart-form-data 或 RFC 1867(Form-based File Upload in HTML,該RFC已經廢棄了)。
那麼multipart/form-data表示什麼呢?multipart網際網路上的混合資源,就是資源由多種元素組成,form-data表示可以使用HTML Forms 和 POST 方法上傳檔案,具體的定義可以參考RFC 7578。
multipart/form-data結構
說了那麼多,從HTTP協議的角度,最後看下檔案上傳的HTTP訊息體,使用Postman也容易看出,如下:
POST /api.php HTTP/1.1 Host: localhst Cache-Control: no-cache Content-Type: multipart/form-data; boundary=----FormBoundary ------FormBoundary Content-Disposition: form-data; name="file"; filename="file.png" Content-Type: image/png <圖片二進位制內容> ------FormBoundary Content-Disposition: form-data; name="param1" value1 ------FormBoundary Content-Disposition: form-data; name="param2" value2 ------FormBoundary--
訊息體什麼意思呢,如果你自行想使用程式碼實現檔案上傳,要根據定義自行封裝HTTP訊息,接下去我們簡單描述一下。
Content-Type: multipart/form-data; boundary=——FormBoundary 表示要上傳附件,其中boundary表示分隔符,如果要上傳多個表單項,就要使用boundary分割,每個表單項由———FormBoundary開始,以———FormBoundary結尾。每一個表單項又由Content-Type和Content-Disposition組成。
------FormBoundary Content-Disposition: form-data; name="param1" value1 ------FormBoundary
表示普通的一個表單元素,最重要的是理解 Content-Disposition HTTP 訊息頭,其中第一個引數總是固定不變的form-data,name表示表單元素屬性名,回車換行符後面的內容就是元素的值。
接下去重點描述和檔案有關的:
------FormBoundary Content-Disposition: form-data; name="file"; filename="file.png" Content-Type: image/png <圖片二進位制內容> ------FormBoundary
其中多了一個filename引數,表示檔名,Content-Type 告訴伺服器這是一個圖片,內容就是圖片的二進位制資料。
其實Content-Disposition這個HTTP header頭用途也很廣泛,在本文就不重點描述了。
其實要自行封裝檔案上傳,最好的辦法就是用自己熟悉的開發語言實現一下,這樣印象才更深刻,希望這篇文章對你有用。
我最近寫了一本新書 ofollow,noindex">《深入淺出HTTPS:從原理到實戰》 ,本書github地址是 https://github.com/ywdblog/httpsbook,大家可以一起討論本書。本書豆瓣地址 https://book.douban.com/subject/30250772/(或點選文末“閱讀原文”),如果你讀了本書,還請在豆瓣寫個評論。或者關注我的公眾號(ID:yudadanwx,虞大膽的嘰嘰喳喳),我會分享一些原創文章(這篇文章有彩蛋哦,可能只有我自己才能知道了:))。