一般瀏覽器在發送請求時都會帶着「Accept-Encoding」頭字段,裏面是瀏覽器支持的壓縮格式列表,例如 gzip、deflate、br 等,這樣服務器就能夠從中選擇一種壓縮算法,放進「Content-Encoding」響應頭裏,再把原數據壓縮後發給瀏覽器。html
將傳輸的文件分解成多個小塊分批發給瀏覽器,瀏覽器收到後再組裝復原。算法
在HTTP協議裏就是「chunked」分塊傳輸編碼,在響應報文裏用頭字段「Transfer-Encoding:chunked」來表示報文裏的body部分不是一次性發過來的,而是分紅了許多的塊(chunk)逐個發送。瀏覽器
注:「Transfer-Encoding:chunked」和「Content-Length」這兩個字段是互斥的,一個響應報文的傳輸長度要麼已知,要麼長度未知(chunked)。安全
下面咱們來看下分塊傳輸的編碼規則,其實也很簡單,一樣採用了明文的方式,很相似響應頭。性能優化
以下圖:服務器
有的時候,不須要所有的數據只需求其中的部分請求時,可以使用範圍請求。容許客戶端在請求頭裏使用專用字段來表示只獲取文件的一部分。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能夠省略:性能
服務器收到Range字段後,須要作四件事:
在Range頭裏使用多個「x-y」,一次性獲取獲取多個片斷數據。
這種狀況須要使用一種特殊的MIME類型:「multipart/byteranges」,表示報文的boby是由多段字節序列組成的,而且還要用一個參數「boundary=xxx」給出段之間的分隔標記。
多段數據的格式與分塊傳輸也比較相似,但它須要用分隔標記boundary來區分不一樣的分段,能夠經過圖來對比下:
每個分段必須以「- -boundary」開始(前面加兩個「-」),以後要用「Content-Type」和「Content-Range」標記這段數據的類型和所在範圍,而後就像普通的響應頭同樣以回車換行結束,再加上分段數據,最後用一個「- -boundary- -」(先後各有兩個「-」)表示全部的分段結束。
要注意這四種方法不是互斥的,而是能夠混合起來使用,例如壓縮後再分塊傳輸,或者分段後再分塊。
每次發送請求前須要先與服務器創建鏈接,收到響應報文後會當即關係鏈接。由於客戶端與服務器的整個鏈接過程很短暫,不會與服務器保持長時間的鏈接狀態,因此就被稱爲「短鏈接」(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 來舉例,它有兩種方式:
另外,客戶端和服務器均可以在報文裏附加通用頭字段「Keep-Alive: timeout=value」,限定長鏈接的超時時間。但這個字段的約束力並不強,通訊的雙方可能並不會遵照,因此不太常見。
「隊頭阻塞」(Head-of-line blocking)與短鏈接和長鏈接無關,而是由於HTTP規定報文必須是「一發一收」,這就造成了一個先進先出的「串行」隊列。若是隊首的請求由於處理的太慢耽誤了時間,那麼後面的請求也不得不跟着一塊兒等待。
由於「請求-應答」模型不能變,因此「隊頭阻塞」問題在HTTP/1.1裏沒法解決,只能緩解。緩解的方法就是「併發鏈接」,也就是同時對一個域名發起多個長鏈接,用數據來解決質量的問題。可是,HTTP協議建議客戶端使用併發,但不能「濫用」併發,因此對併發數有限制(6~8)。
域名分片(domain sharing):因爲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機制,至關因而服務器給每一個客戶端都貼上一張小紙條,上面寫了一些只有服務器才能理解的數據,須要的時候客戶端把這些信息發給服務器,服務器看到Cookie,就可以認出對方是誰了。
Cookie 就是服務器委託瀏覽器存儲在客戶端裏的一些數據,而這些數據一般都會記錄用戶的關鍵識別信息。因此,就須要在「key=value」外再用一些手段來保護,防止外泄或竊取,這些手段就是 Cookie 的屬性。
首先,咱們應該設置Cookie的生存週期,也就是它的有效期,可使用Expires和Max-Age兩個屬性來設置。
其次,咱們須要設置Cookie的做用域,讓瀏覽器僅發送給特定的服務器和URI。「Domain」和「Path」指定了 Cookie 所屬的域名和路徑,瀏覽器在發送 Cookie 前會從 URI 中提取出 host 和 path 部分,對比 Cookie 的屬性。若是不知足條件,就不會在請求頭裏發送 Cookie。
最後考慮的就是Cookie的安全性了,儘可能不要讓服務器外的人看到。
Q:分塊傳輸數據的時候,若是數據裏含有回車換行(\r\n)是否會影響分塊的處理呢?
Q:若是對一個被 gzip 的文件執行範圍請求,好比「Range: bytes=10-19」,那麼這個範圍是應用於原文件仍是壓縮後的文件呢?
Q:在開發基於 HTTP 協議的客戶端時應該如何選擇使用的鏈接模式呢?短鏈接仍是長鏈接?
Q:應當如何下降長鏈接對服務器的負面影響呢?
Q:301 和 302 很是類似,試着用本身的理解再描述一下二者的異同點。
Q:你能結合本身的實際狀況,再列出幾個應當使用重定向的場景嗎?
Q:若是 Cookie 的 Max-Age 屬性設置爲 0,會有什麼效果呢?
Q:Cookie 的好處已經很清楚了,你以爲它有什麼缺點呢?