http:文件上傳背後發生了什麼?

我雖然知道,文件上傳的一般過程,即在頁面表單中加入屬性:enctype=multipart/form-data,然後通過 & 來選擇文件,即可提交文件到指定伺服器。在此過程中,提交的表單會與一般post有所不同,http主體大概是:

Content-Type: multipart/form-data; boundary=---------------------------14579331036932498511351460782
Content-Length: 418
-----------------------------14579331036932498511351460782
Content-Disposition: form-data; name="userfile1"; filename="?¤??3¨èˉ′???.txt"
Content-Type: text/plain
1.±ê×¢ò?iPhone6s ?á??3?′??a±ê×?£?
2.è?1?Dèòa2?í?3?′?μ?icon£??ù???ò?μ?£
-----------------------------14579331036932498511351460782
Content-Disposition: form-data; name="hehe"
tewtw
-----------------------------14579331036932498511351460782--

在這裡,我想探尋的是,伺服器端如何接受數據流,並將數據流作為文件保存起來,也就是說從http的層面來說,如何操作這一切?而我搜索到的資料、博客多是從語言(或框架)層面來解釋,而語言(或框架)之間多是直接做好了封裝,如PHP中的$_FILES,node中多是引用formidable,直接一步到位,而未能解釋裡面的原理。

不知有大神能否指點一二?


瀉藥

按你給的例子來說大致是這樣的:

讀取 http body 部分

根據 boundary 分析出分隔符特徵(這個串是唯一的,不會與body內其他數據衝突)

根據實際分隔符分段獲取 body 內容

遍歷分段內容

根據 Content-Disposition 特徵獲取其中值

根據值中 filename 或 name 區分是否是包含二進位流還是表單數據的 k-v

根據 filename 獲取原始文件名

從連續 兩個 newline 字元串為起始至當前分段完畢,按照二進位流讀取上傳文件流信息。

完成後即有——

  • 原始文件名信息
  • 原始文件類型信息
  • 全部文件流信息

然後該幹嘛幹嘛,比如寫文件到磁碟等。

最後建議搜索關鍵字 rfc1867 ……


https://github.com/pillarjs/multiparty

讀讀代碼就知道


題主明白文件是什麼嗎?

文件就是磁碟上的一段空間,文件的內容就是一串2進位數字(1或者0)。

文件傳輸,就是把這串數字通過http協議傳過去。

伺服器端,接到這段數據之後,按照協議規定的格式,把這串數字取出來,然後創建一個空文件(分配一段空間),然後把這段數字寫進去,就成了一個跟上傳文件完全一致的新文件。

不知道題主是不是問這個,如果不是,就摺疊吧。


首先要說一句公認的廢話,怎麼處理是伺服器做的事兒,HTTP協議本身並沒有死規定,以下說的只是解決問題的某一種思路。

有一個基本認識是,每一個請求都是一個流。而每一個用於傳輸文件的HTTP報文,都會有類似於這樣的報頭:

Content-Type: multipart/form-data; boundary=巴拉巴拉

如果報頭定義了這樣的東西,就可以判斷客戶端採用了multipart格式傳遞信息,同時我們也拿到了boundray。

再考慮文件如何處理。以問題中提到的報文為例,payload(你題干中的報文格式並不對,我根據題目意思做了相應修改)為:

-----------------------------14579331036932498511351460782
Content-Disposition: form-data; name="userfile1"; filename="?¤??3¨èˉ′???.txt"
Content-Type: text/plain

1.±ê×¢ò?iPhone6s ?á??3?′??a±ê×?£?
2.è?1?Dèòa2?í?3?′?μ?icon£??ù???ò?μ?£
-----------------------------14579331036932498511351460782
Content-Disposition: form-data; name="hehe"

tewtw
-----------------------------14579331036932498511351460782--

1. 第一次讀到定義的邊界"-----------------------------14579331036932498511351460782"

意味著一個欄位的開始;

2. 繼續讀入一行,發現這是個文件;再讀入一行,發現定義了Content-Type,也許還會定義charset之類的信息;再讀入一行發現是個 CRLF ,意味著後續的內容是文件數據,這時候可以構造一個新的臨時文件對象,將後續的數據pipe到這個臨時文件對象中。

3. 再一次讀到邊界"-----------------------------14579331036932498511351460782"意味著這一個欄位結束,這時候可以去關閉剛剛創建的臨時文件。

4. 然後開始繼續下一欄位解析過程。

ps:以上部分只是簡單的說了解決思路,並不涉及檢查、轉換等工作。比如在流的pipe過程中,可能需要根據之前定義的charset進行流的轉換,甚至如果發現Content-Type不是自己需要的,就壓根不存而是直接pipe到黑洞中去。


你要明白,http傳輸的都是二進位數據,我們可以看成傳輸的都是字元。這個你能理解嗎?

接下來把http請求分解成header和body部分,header就是請求方法,url,協議版本,content-length等等,然後是兩個
作為header和body分割。

body部分就是你看到的,一般也稱為payload。

對於body中的不同東西,我們怎麼分割呢?比如你傳多個文件?

類似於

,body中傳輸的東西都用header中的boundary來分割的,40個字元。

這樣,body中傳輸的東西都被序列化成字元(二進位)數據傳輸過去了。

我之前用XHR上傳文件,在header中自定了boundary,結果和body中的分割符不對等,後台獲取的文件流始終沒有,google了許久。我的問題就是header中的boundary和payload中的boundary怎麼變成一樣的,為什麼不使用我header中定義的boundary。別人有的方法是自己生成boundary字元串,然後把文件或者form表單值序列化後,用字元串拼接的方式構造body,然後才能得到content-length,再用xhr.send方法傳上去。太麻煩了,我用的是FormData。後來找到了答案,就是不要去動header中的content-type,用瀏覽器自動生成的。

也就是說,這個boundary起到了分割文件的作用。

在後台,你的http模塊都幫你做了很多事情了,ondata事件傳輸過來的就是body(如果你用nodejs的話就是data事件中傳過來的所有chunk,如果是Java就是一個inputStream),甚至還幫你解析成了request對象。

一般,我們用流的形式來處理一串二進位數據,body可以看作是一個流,或者傳過來的某個文件封裝成一個流。

我們拿到body後,就可以還原成表單數據。先用boundary分割,然後按照固定格式又去拆解,都是在字元層面上處理的,處理完成後,按照對應的名字附加到request對象上,如果是文件二進位數據,就會封裝為一個流拿給你隨便折騰。你看到,還有

Content-Disposition: form-data; name="hehe"

在body中,表明了這一串字元代表的是什麼東西。

具體的解析過程我不清楚,也就大概那麼回事兒,步驟上要更明智一些,比如我猜測讀取body的流可能不是分割的,而是從頭到尾讀取字元,遇到boundary再分割。

見人說人話,見鬼說鬼話,我不知道這麼通俗的講有沒有明白,如果你覺得太淺了,可以去看看socket編程的知識,實現某個協議,就會搞懂所有了。


樓主想要了解http中文件上傳的原理,其實是要了解http協議的,對於http協議的話,其實就是對socket接受到的數據進行解析,或者將按照http協議的格式把數據寫到socket中,之前由於一個很好的面試官建議我看node中http模塊,找到了一個關於http協議的解析庫https://github.com/creationix/http-parser-js(node版本,本人前端,如果樓主不熟悉js的話,建議去github上找一個自己擅長語言的庫),這個庫主要就是對http協議的進行解析,希望能夠幫助您。


推薦閱讀:

Web 開發中,用戶在表單中輸入的字元都應該經過哪些處理?
科班計算機it從業者,都學些什麼?
github上有哪些值得學習的優秀的php開源項目?
如何用php 編寫網路爬蟲?
有什麼是 Python 可以做,但是 PHP 不可以做的?

TAG:PHP | 後端技術 | Nodejs | HTTP | 文件上傳 |