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中的不同東西,我們怎麼分割呢?比如你傳多個文件?
類似於,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 不可以做的?