把 HTTP 當黑盒子來學

本篇梳理 HTTP 的相關知識,也分享個人學習經驗。有認識不對的地方,歡迎指正!

前端開發的很多工作,似乎都是 “所見即所得” 的,這種對知識強大的 “既視感”,是前端容易入門的一個關鍵因素。但越往后走,一些底層而重要的知識,就沒那么容易觸及了,比如 HTTP 協議。

一開始,可能就只知道 Ajax 是瀏覽器端 HTTP 強相關的一套 API;另外,無論是瀏覽器端,還是服務器端,都有一套對 HTTP 的解析、處理的機制。HTTP可以說是前、后端通信的載體。在我們工作中、或者我們面試時,經常涉及的性能優化問題、跨域問題、安全問題等等,都和它緊密相關。

就是因為它更底層,邏輯對外不可見,知識的 “既視感” 也比較難實現(如果有哪個團隊做一款這樣的產品,那肯定會在前端圈里圈粉無數)。但無論如何,HTTP 是前端進階應該攻破的一道坎。

盡管它不像頁面 DOM 元素、或者一個 react 組件那樣觸手可及,但我們仍然應該有辦法學習它。我們可以暫且不理會內幕細節,把它當做一個 “黑盒子”,而通過一些其他的途徑,旁敲側擊。比如使用一些抓包工具(本文用的是 Fiddler ),也能很好的窺視 HTTP 的形貌。

1、認識 HTTP 報文

以訪問“簡書”站點首頁http://www.lxweimin.com/ 為例,通過 Fiddler 工具抓取到以下報文信息。

請求報文

GET http://www.lxweimin.com/ HTTP/1.1
Host: www.lxweimin.com
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 Chrome/58.0.3029.110 Safari/537.36
Accept: text/html;q=0.9,image/webp,*/*;q=0.8
Referer: http://www.lxweimin.com
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

//本行及以下均為報文主體

無論請求報文,還是響應報文,都是有著嚴格的格式規范的,它們絕不是一堆毫無規范組織的字符串。正式這一套標準規范,讓報文的解析、分割、組包可以用一定的算法實現,具體內幕本文不涉及。

響應報文

HTTP/1.1 200 OK
Date: Mon, 14 Aug 2017 12:36:22 GMT
Server: Tengine
Content-Type: text/html; charset=utf-8
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
ETag: W/"fffd6ab16cec9ff5e97d58a4d293073c"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _session_id=--4007a37778519a153aad82b63--; path=/; HttpOnly
X-Request-Id: 9c7aa92b-99b8-4e14-a87a-012d88e40608
X-Runtime: 0.230320
X-Via: 1.1 edianxin19:8 (Cdn Cache Server V2.0)
Connection: keep-alive
Content-Length: 56884

<!DOCTYPE html>
<!--[if IE 6]><html class="ie lt-ie8"><![endif]-->
<!--[if IE 7]><html class="ie lt-ie8"><![endif]-->
<!--[if IE 8]><html class="ie ie8"><![endif]-->
//余下報文主體...

HTTP 報文,基本還是符合 “所見即多得” 的,因為它包含的內容的的確確就是這樣的。

值得強調的是 HTTP 報文的 “結構化”,是一個非常重要的特征,尤其是報文頭部項,不僅是下文重點關注的對象,也是今后學習的一大重點。下圖,以請求報文為例,再次感受這種 “結構化”。

請求報文結構

2、HTTP 的相關概念

兩臺計算機之間的通信是通過 TCP/IP 協議在因特網上進行的。可以借用一個 20 多年前的例子來理解,比如兩個好友用信件聯絡,他們能建立通信的條件,一個是互相明確彼此的郵箱地址(IP),然后還需要一個郵差去分類和送達(TCP)。

IP: Internet Protocol 網際協議。每臺計算機都有一個·IP,用來在 internet 上標識這臺計算機。IP 協議負責將計算機之間傳輸的每個數據包路由至它的目的地。

TCP:Transmission Control Protocol 傳輸控制協議。顧名思義,是傳輸控制。TCP 負責在數據傳送之前將它們分割為數據包,然后在它們到達的時候將它們重組,確保數據包以正確的次序到達。

HTTP 協議采用了請求/響應模型??蛻舳讼蚍掌靼l送一個請求報文,請求報文包含請求的方法、URL、協議版本、請求頭部(header)和請求體(body)數據。

服務器以一個狀態行作為響應,響應的內容包括協議的版本、成功或者錯誤代碼、服務器信息、響應頭部(header)和響應體(body)數據。

HTTP 是應用層協議,是基于 TCP/IP 協議之上的一種應用。作為應用層的 HTTP 協議,通信過程依次會穿越傳輸層、網絡層、鏈路層。詳見下圖:


http 報文的旅行

3、縮小關注范圍

毫不諱言,學習 HTTP比較麻煩,除了它不像前端的其他領域知識那樣 “既視感”強之外,還有就是面對一個理論性很強的知識,往往很難找到 “下口” 的地方,更別說輕易消化。而在一個信息泛濫的時代,閱后即焚、收集癖這些 “淺閱讀” 形式更是我們深入掌握一些知識的頭號敵人。

通常,對于獲取知識,個人經驗來看有這些層次:

  • 最淺層次莫過于瀏覽、收藏
  • 次淺層次是復制、截圖、粘貼成筆記;
  • 速成(鼓吹)的方式是聽課看視頻;
  • 較深層次則是整理知識、梳理結構,畫原理圖、思維導圖;
  • 更為深層而牢靠的則是實踐、實操中Q&A和技術交流。

我們很難掌握某些知識,包括筆者親身體會,就是大部分都停留在第 3 階段,很少越過第 4 階段。

到了第 4 階段,就應該去劃分邊界,縮小關注范圍,然后重點突破。就好比本文的梳理,至少可以明確,現在(及今后一段時間)的注意力,就完全可以集中在 HTTP 頭部項及其包含的實現原理、客戶端的 API (Ajax)、服務端的 API (完全可以通過 Nodejs 模塊之http.js 來學習)。

其中,HTTP 頭部項,可以慢慢積累,這里暫且不談。本文下部分主要探討通過客戶端的 Ajax 來了解瀏覽器和服務器間的 HTTP 通信是怎樣的。

瀏覽器端調試 HTTP

以一個問題開始

當客戶端請求被 abort(取消)掉后,瀏覽器和服務器分別會如何處理這個請求?

這是個好問題,它立刻激發我的探究欲望。對于這個問題的驗證,只需用上兩個工具——瀏覽器和 Fiddler 抓包工具——就能搭建一個很好的驗證環境。其中,瀏覽器中運行的 JavaScript 腳本非常簡單,如下:

var xhr = new XMLHttpRequest();
xhr.open("GET", 'http://www.vrstarman.com/stack.html', true);
//狀態變更
xhr.onreadystatechange = function(e){
    console.log('請求狀態值變更:', xhr.readyState);
    if(xhr.readyState == '2'){
        //xhr.abort();
    }
};
//進行中
xhr.onprogress = function(e){
    console.log('請求進行時狀態:', xhr.readyState);
};
//中斷
xhr.onabort = function(e){
    console.log('請求取消事件:', e', '此時狀態值:', xhr.readyState);
};
//加載完成
xhr.onload = function(e){
   console.log('請求完成事件:', e);
};
//錯誤
xhr.onerror = function(){
    console.log('請求錯誤時狀態:', xhr.readyState);
};
xhr.send();

問題的變量因素有:

  • 在不同狀態階段 readyState 階段 abort 掉請求;
  • 在程序的不同的位置執行 abort;
  • 在這些 abort 測試中,驗證的 url 分別為以下情形:
    • [v] 一個實際存在的 url
    • [ ] 一個不存在的 url
    • [ ] 一個跨域的 url

本文就用簡書的首頁地址http://www.lxweimin.com/ 測試了第一種情形,并且已經收獲頗豐??梢酝茰y第二種情形和第一種差不多,但第三種會不同,非常值得驗證。

驗證結果及分析

readyState 狀態含義解析 abort 位置
0 請求未初始化, 即 open() 前的狀態 此時 abord,后續方法拋出錯誤
1 請求已經建立,但是還未send(),此時打印狀態是1。 send()之后打印狀態,結果還是1 (按理說應該是 2),可見狀態變更是滯后的,說明是異步的 open()之后 send()之前 abord,會報錯,故用try{ xhr.send(); }catch(e){ }捕捉錯誤
2 請求已send(),正在處理中,并已經接收到響應數據(通常瀏覽器可從響應中解析出請求頭、響應頭) 鑒于異步執行,將 xhr.abord() 放在 onreadystatechange 事件中
3 響應數據仍在接收和處理中,響應 body 已部分解析,但服務器還未全部發送完或瀏覽器還未全部解析完 readyState == 3abord(),由于異步執行的原因,實際請求已接近(或達到)狀態 4
4 響應已完成接收和解析,可獲取并使用所有響應內容 從以上操作看,一個請求,只要不在狀態 2 之前取消,都會經歷完整的響應階段,即完成狀態 4

進一步詳細解讀

達到了不同的狀態值,瀏覽器和服務器都分別做了哪些動作,以下是上述探索的詳細解讀。

狀態為 0

狀態為 0 是被大多瀏覽器隱藏的。只在瀏覽器端 new 出了 xhr 對象,但還未建立連接就中斷請求是沒有意義的。

狀態為 1

結果是無任何請求發出,且 打印的readyState0。
為什么還是 0?雖然 open()已執行,但因立即打印狀態,還沒有收到已建立連接的返回消息,故立即打印的狀態是變更前的。
Fiddler 此時未顯示任何 http 請求。
瀏覽器顯示 Provisional headers are shown,這與瀏覽器的可視化機制有關,真實情況瀏覽器并未發送請求。

狀態為 2

readyState 狀態值變更滯后(因為異步),給判斷帶來干擾
但測試表明放在 onreadystatechange 事件中的 abord(),會導致 readyState 值從 2 “跳” 到 4。
瀏覽器已成功發送請求,并成功的解析了請求頭、響應頭。
Fiddler 監測出了 http 請求,表明服務器已成功發送請求數據。
但因為手動 abord(),導致瀏覽器接收到了(部分甚至可能全部)響應但放棄了解析響應的 body,所以頁面無數據顯示。

狀態為 3

盡管是中途 abord(),但 readyState 完整經歷 4 個狀態變化。
onprogress 事件只激發了 2 次。
瀏覽器處于一個不確定是否完全解析響應的狀態中,反復測試并打印 xhr.response 會發現有時解析了完整的數據,有時則在中間產生了中斷。
服務器已經確定發出了(幾乎)所有響應內容。
由于在狀態 4 前中斷,瀏覽器未確切是否已完成全部接收和解析,故在瀏覽器控制臺的 Preview 中無法看到內容。

狀態為 4

abord 會導致 xhr.responsexhr.responseText 被清空。
服務器端全部響應也發送完畢。
瀏覽器已經全部接受、并完成解析,這在瀏覽器控制臺的 Preview中能看到。
但還是因為 abord(),導致了對響應 body 內容的情況,所以還是無法最終顯示在 document 中。除非在 abord() 前執行 document.write(xhr.response);

其中,對 readyState == 2abord() 的情形,服務器到底有沒有響應,若有,響應了什么內容?因為從瀏覽器的控制臺無從確認,對于這一步的存疑,還好可以借助 Fiddler 的攔截,一目了然。

本圖借用了一年前對筆者個人站點 www.vrstarman.com 的抓包截圖

對于跨域的 url ,在下不同 HTTP 請求階段被 abord 情況的探索,就暫不描述了。強烈建議讀者一并探索下。

另外,HTTP 頭部項、以及 HTTP 服務端的一些特性的學習,后續找時間再寫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容