透視HTTP協議-HTTP的傳輸、鏈接、重定向及Cookie機制

HTTP傳輸大文件的方法

1、數據壓縮

一般瀏覽器在發送請求時都會帶着「Accept-Encoding」頭字段,裏面是瀏覽器支持的壓縮格式列表,例如 gzip、deflate、br 等,這樣服務器就能夠從中選擇一種壓縮算法,放進「Content-Encoding」響應頭裏,再把原數據壓縮後發給瀏覽器。html

2、分塊傳輸

將傳輸的文件分解成多個小塊分批發給瀏覽器,瀏覽器收到後再組裝復原。算法

在HTTP協議裏就是「chunked」分塊傳輸編碼,在響應報文裏用頭字段「Transfer-Encoding:chunked」來表示報文裏的body部分不是一次性發過來的,而是分紅了許多的塊(chunk)逐個發送。瀏覽器

注:「Transfer-Encoding:chunked」和「Content-Length」這兩個字段是互斥的,一個響應報文的傳輸長度要麼已知,要麼長度未知(chunked)。安全

下面咱們來看下分塊傳輸的編碼規則,其實也很簡單,一樣採用了明文的方式,很相似響應頭。性能優化

  1. 每一個分塊包含兩個部分,長度頭和數據塊;
  2. 長度頭是以CRLF(回車換行,即\r\n)結尾的一行明文,用16進制數字表示長度;
  3. 數據塊緊跟在長度頭後,最後也用CRLF結尾,但數據不包含CRLF;
  4. 最後用一個長度爲0的塊表示結束,即「0\r\n\r\n」。

以下圖:服務器

3、範圍請求

有的時候,不須要所有的數據只需求其中的部分請求時,可以使用範圍請求。容許客戶端在請求頭裏使用專用字段來表示只獲取文件的一部分。cookie

範圍請求不是Web服務器必備的功能,因此服務器必須在響應頭裏使用字段「Accept-Range:bytes」明確告知客戶端:我是支持範圍請求的。不支持的話就發送「Accept-Range:none」或者不發送Accept-Range字段。併發

請求頭Range是HTTP範圍請求的專用字段,格式是「bytes=x-y」,其中x,y是以字節爲單位的數據範圍。x,y表示的是「偏移量」,範圍必須從0計數。例:前10個字節是0-9dom

Range的格式很靈活,起點x和終點y能夠省略:性能

  • 「0-」表示從文檔起點到文檔終點,即整個文件;
  • 「10-」從第10個字節開始到文檔末尾
  • 「-1」是文檔的最後一個字節
  • 「-10」是從文檔末尾倒數10個字節

服務器收到Range字段後,須要作四件事:

  1. 它必須檢查範圍是否合法,好比文件只有100個字節,但請求「200-300」,這就是範圍越界了。服務器就會返回狀態碼416,意思是「你請求的範圍有誤,我沒法處理,請再檢查一下」。
  2. 若是範圍正確,服務器就能夠根據Range頭計算偏移量,讀取文件的片斷了,返回狀態碼「206 Partical Content」,跟200的意思差很少,但表示body只是原數據的一部分。
  3. 服務器要添加一個響應頭字段Content-Range,告訴片斷的實際偏移量和資源的總大小,格式是「bytes x-y/length」,與Range頭區別在沒有「=」,範圍後多了總長度。例如,對於「0-10」的範圍請求,值就是「bytes 0-10/100」。
  4. 最後就是發送數據了,直接把片斷用TCP發給客戶端,一個範圍請求就算是處理完了。

4、多段數據

在Range頭裏使用多個「x-y」,一次性獲取獲取多個片斷數據。

這種狀況須要使用一種特殊的MIME類型:「multipart/byteranges」,表示報文的boby是由多段字節序列組成的,而且還要用一個參數「boundary=xxx」給出段之間的分隔標記。

多段數據的格式與分塊傳輸也比較相似,但它須要用分隔標記boundary來區分不一樣的分段,能夠經過圖來對比下:

每個分段必須以「- -boundary」開始(前面加兩個「-」),以後要用「Content-Type」和「Content-Range」標記這段數據的類型和所在範圍,而後就像普通的響應頭同樣以回車換行結束,再加上分段數據,最後用一個「- -boundary- -」(先後各有兩個「-」)表示全部的分段結束。

要注意這四種方法不是互斥的,而是能夠混合起來使用,例如壓縮後再分塊傳輸,或者分段後再分塊。

HTTP的鏈接管理

短鏈接

每次發送請求前須要先與服務器創建鏈接,收到響應報文後會當即關係鏈接。由於客戶端與服務器的整個鏈接過程很短暫,不會與服務器保持長時間的鏈接狀態,因此就被稱爲「短鏈接」(short-lived connections)

短鏈接的缺點至關嚴重,由於在 TCP 協議裏,創建鏈接和關閉鏈接都是很是「昂貴」的操做。TCP 創建鏈接要有「三次握手」,發送 3 個數據包,須要 1 個 RTT;關閉鏈接是「四次揮手」,4 個數據包須要 2 個 RTT。而 HTTP 的一次簡單「請求 - 響應」一般只須要 4 個包,若是不算服務器內部的處理時間,最可能是 2 個 RTT。這麼算下來,浪費的時間就是「3÷5=60%」,有三分之二的時間被浪費掉了,傳輸效率低得驚人。

長鏈接

長鏈接就是保持鏈接,用的就是「成本均攤」的思路,既然 TCP 的鏈接和關閉很是耗時間,那麼就把這個時間成本由原來的一個「請求 - 應答」均攤到多個「請求 - 應答」上。

鏈接相關的頭字段

HTTP/1.1中的鏈接都會默認啓用長鏈接。也能夠在請求頭裏明確地要求使用長鏈接機制,使用的字段是Connection,值是「keep-alive」。不過無論客戶端是否顯示要求長鏈接,若是服務器支持長鏈接,它總會在響應報文裏放一個「Connection:keep-alive」字段,告訴客戶端:「我是支持長鏈接的,接下來就用這個TCP一直收發數據吧」。

長鏈接的缺點是,若是TCP鏈接長時間不關閉,服務器必須在內存裏保存它的狀態,這就佔用了服務器的資源。因此長鏈接也須要在恰當的時間關閉,不能永遠保持與服務器的鏈接,這在客戶端或者服務器均可以作到。

在客戶端,能夠在請求頭裏加上「Connection:close」字段,告訴服務器:「此次通訊後就關閉鏈接」。服務器看到這個字段,就知道客戶端要主動關閉鏈接,因而在響應報文裏也加上這個字段,發送以後就調用Socket API關閉TCP鏈接。

服務器端一般不會主動關閉鏈接,但也可使用一些策略。拿 Nginx 來舉例,它有兩種方式:

  1. 使用「keepalive_timeout」指令,設置長鏈接的超時時間,若是在一段時間內鏈接上沒有任何數據收發就主動斷開鏈接,避免空閒鏈接佔用系統資源。
  2. 使用「keepalive_requests」指令,設置長鏈接上可發送的最大請求次數。好比設置成 1000,那麼當 Nginx 在這個鏈接上處理了 1000 個請求後,也會主動斷開鏈接。

另外,客戶端和服務器均可以在報文裏附加通用頭字段「Keep-Alive: timeout=value」,限定長鏈接的超時時間。但這個字段的約束力並不強,通訊的雙方可能並不會遵照,因此不太常見。

隊頭阻塞

「隊頭阻塞」(Head-of-line blocking)與短鏈接和長鏈接無關,而是由於HTTP規定報文必須是「一發一收」,這就造成了一個先進先出的「串行」隊列。若是隊首的請求由於處理的太慢耽誤了時間,那麼後面的請求也不得不跟着一塊兒等待。

性能優化

由於「請求-應答」模型不能變,因此「隊頭阻塞」問題在HTTP/1.1裏沒法解決,只能緩解。緩解的方法就是「併發鏈接」,也就是同時對一個域名發起多個長鏈接,用數據來解決質量的問題。可是,HTTP協議建議客戶端使用併發,但不能「濫用」併發,因此對併發數有限制(6~8)。

域名分片(domain sharing):因爲HTTP協議和瀏覽器限制併發鏈接數量,那我就多開幾個域名,都指向同一臺服務器,這樣實際鏈接的數量就又上去了。

HTTP的重定向和跳轉

主動跳轉:瀏覽器的使用者主動發起的; 被動跳轉:由服務器來發起的,瀏覽器使用者沒法控制,這在HTTP協議裏有個專門的名詞,叫作「重定向」(Redirection)。

重定向的過程

以下圖實驗,用Chrome訪問URI「/18-1」,它會使用302當即跳轉到「/index.html」

能夠看出,這一次「重定向」實際上發送了兩次HTTP請求,第一個請求返回了302,而後第二個請求就被重定向到了「/index.html」。再來看看第一個請求返回的響應報文:

這裏出現了一個新的頭字段「Location:/index.html」。」Location「字段屬於響應字段,必須出如今響應報文裏。但只有配合301/302狀態碼纔有意義,它標記了服務器要求重定向的URI,這裏就是要求瀏覽器跳轉到」index.html「。

注意:在重定向時若是隻是在站內跳轉,你能夠放心地使用相對URI。但若是要跳轉到站外,就必須使用絕對URI。

另外,使用重定向時須要小心性能損耗,還要避免出現循環跳轉(即A->B->A這種)

HTTP的Cookie機制

HTTP的Cookie機制,至關因而服務器給每一個客戶端都貼上一張小紙條,上面寫了一些只有服務器才能理解的數據,須要的時候客戶端把這些信息發給服務器,服務器看到Cookie,就可以認出對方是誰了。

Cookie的工做過程

  1. 當用戶經過瀏覽器第一次訪問服務器的時候,服務器確定是不知道他的身份的。因此,就要建立一個獨特的身份標識數據,格式是」key=value「,而後放進Set-Cookie字段裏,隨着響應報文一同發給瀏覽器。(服務器有時會在響應頭裏添加多個Set-Cookie,存儲多個」key=value「。但瀏覽器這邊發送時不須要用多個Cookie字段,只要在一行裏用」;「隔開就行)
  2. 瀏覽器收到響應報文,看到裏面有Set-Cookie,知道這是服務器給的身份標識,因而就保存起來,下次再請求的時候就自動把這個值放進Cookie字段裏發給服務器。
  3. 由於第二次請求裏面有了Cookie字段,服務器就知道這個用戶不是新人,以前來過,就能夠拿出Cookie裏的值,識別出用戶的身份,而後提供個性化的服務。 Cookie是」瀏覽器綁定「的,只能在本瀏覽器內生效。

Cookie的屬性

Cookie 就是服務器委託瀏覽器存儲在客戶端裏的一些數據,而這些數據一般都會記錄用戶的關鍵識別信息。因此,就須要在「key=value」外再用一些手段來保護,防止外泄或竊取,這些手段就是 Cookie 的屬性。

首先,咱們應該設置Cookie的生存週期,也就是它的有效期,可使用Expires和Max-Age兩個屬性來設置。

  • 」Expires「俗稱」過時時間「,用的是絕對時間,能夠理解爲deadline
  • 」Max-Age「用的是相對時間,單位是秒,瀏覽器用收到報文的時間點再加上Max-Age,就能夠獲得失效的絕對時間。 Expires 和 Max-Age 能夠同時出現,二者的失效時間能夠一致,也能夠不一致,但瀏覽器會優先採用 Max-Age 計算失效期。

其次,咱們須要設置Cookie的做用域,讓瀏覽器僅發送給特定的服務器和URI。「Domain」和「Path」指定了 Cookie 所屬的域名和路徑,瀏覽器在發送 Cookie 前會從 URI 中提取出 host 和 path 部分,對比 Cookie 的屬性。若是不知足條件,就不會在請求頭裏發送 Cookie。

最後考慮的就是Cookie的安全性了,儘可能不要讓服務器外的人看到。

  • 」HttpOnly「:此Cookie只能經過瀏覽器HTTP協議傳輸,禁止其餘方式訪問,瀏覽器的 JS 引擎就會禁用 document.cookie 等一切相關的 API,腳本攻擊也就無從談起了。
  • 」SameSite「:能夠防範「跨站請求僞造」(XSRF)攻擊,設置成「SameSite=Strict」能夠嚴格限定 Cookie 不能隨着跳轉連接跨站發送,而「SameSite=Lax」則略寬鬆一點,容許 GET/HEAD 等安全方法,但禁止 POST 跨站發送。
  • 」Secure」:表示這個Cookie僅能用HTTPS協議加密傳輸,明文的HTTP協議會禁止發送。但Cookie自己不是加密的,瀏覽器裏仍是以明文的形式存在。

Cookie的應用

  1. 最基本的一個用途就是身份識別,保存用戶的登陸信息,實現會話事務。
  2. 另外一個常見用途是廣告跟蹤。你上網的時候確定看過不少的廣告圖片,這些圖片背後都是廣告商網站(例如 Google),它會「偷偷地」給你貼上 Cookie 小紙條,這樣你上其餘的網站,別的廣告就能用 Cookie 讀出你的身份,而後作行爲分析,再推給你廣告。

Q&A

Q:分塊傳輸數據的時候,若是數據裏含有回車換行(\r\n)是否會影響分塊的處理呢?

Q:若是對一個被 gzip 的文件執行範圍請求,好比「Range: bytes=10-19」,那麼這個範圍是應用於原文件仍是壓縮後的文件呢?

Q:在開發基於 HTTP 協議的客戶端時應該如何選擇使用的鏈接模式呢?短鏈接仍是長鏈接?

Q:應當如何下降長鏈接對服務器的負面影響呢?

Q:301 和 302 很是類似,試着用本身的理解再描述一下二者的異同點。

Q:你能結合本身的實際狀況,再列出幾個應當使用重定向的場景嗎?

Q:若是 Cookie 的 Max-Age 屬性設置爲 0,會有什麼效果呢?

Q:Cookie 的好處已經很清楚了,你以爲它有什麼缺點呢?

相關文章
相關標籤/搜索