題圖:by @Olgahtml
Hi,你們好,我是承香墨影!程序員
HTTP 協議在網絡知識中佔據了重要的地位,HTTP 協議最基礎的就是請求和響應的報文,而報文又是由報文頭(Header)和實體組成。大多數 HTTP 協議的使用方式,都是依賴設置不一樣的 HTTP 請求/響應 的 Header 來實現的。瀏覽器
本系列《實用 HTTP》就拋開常規的 Header 講解式的表述方式,從實際問題出發,來分析這些 HTTP 協議的使用方式,究竟是爲了解決什麼問題?同時講解它是如何設計的和它實現原理。緩存
HTTP 協議是一種無狀態的「鬆散協議」,它不會記錄不一樣請求的狀態,而且由於它自己包含了兩端(客戶端和服務端),根據請求和響應來區分,它大部分的內容都只是一個建議,其實雙邊是能夠不遵照此建議的。網絡
「這裏寫了建議零售價 2 元...」post
「哦,不接受建議!」性能
前兩篇文章中,咱們分別聊了 HTTP 的緩存機制 和 HTTP 內容實體編碼壓縮機制 ,在說到實體編碼壓縮的時候,還提到了一個傳輸編碼,讓咱們優化傳輸的方式。實體編碼和傳輸編碼兩者是相輔相成的,通常咱們會配合使用。學習
本文就來聊聊 HTTP 的傳輸編碼機制。優化
傳輸編碼在 HTTP 的報文頭中,使用 Transfer-Encoding
首部進行標記,它就是指明當前使用的傳輸編碼。編碼
Transfer-Encoding
會改變報文的格式和傳輸的方式,使用它不但不會減小內容傳輸的大小,甚至還有可能會使傳輸變大,看似是一個不環保的作法,可是實際上是爲了解決一些特殊問題。
簡單來講,傳輸編碼必須配合持久鏈接去使用,爲了在一個持久鏈接中,將數據分塊傳輸,並標記傳輸結束而設計的,後面會詳細講解。
在早年間的設計裏,和內容編碼使用 Accept-Encoding
來標記客戶端接收的壓縮編碼類型同樣,傳輸編碼還須要配合 TE
這個請求報文頭來使用,用於指定支持的傳輸編碼。可是在最新的 HTTP/1.1 協議規範中,只定義了一種傳輸編碼:分塊編碼(chunked),因此並不須要再依賴 TE
這個頭部。
這些細節,後面都會講到。既然傳輸編碼和持久鏈接是息息相關的,那咱們就先來了解一下什麼是持久鏈接。
持久鏈接通俗來說,就是長鏈接,英文叫 Persistent Connection,其實按字面意思理解就行了。
在早期的 HTTP 協議中,傳輸數據的順序大體分爲發起請求、創建鏈接、傳輸數據、關閉鏈接等步驟,而持久鏈接,就是去掉關閉鏈接這個步驟,讓客戶端和服務端能夠繼續經過這次鏈接傳輸內容。
這其實也是爲了提升傳輸效率,咱們知道 HTTP 協議是創建在 TCP 協議之上的,天然有 TCP 同樣的三次握手、慢啓動等特性,這樣每一次鏈接其實都是一次寶貴的資源。爲了儘量的提升 HTTP 的性能,使用持久鏈接就顯得很重要了。爲此在 HTTP 協議中,就引入了相關的機制。
在早期的 HTTP/1.0 協議中並無持久鏈接,持久鏈接的概念是在後期才引入的,當時是經過 Connection:Keep-Alive
這個頭部來標記實現,用於通知客戶端或服務端相對的另外一端,在發送完數據以後,不要斷開 TCP 鏈接,以後還須要再次使用。
而在 HTTP/1.1 協議中,發現持久鏈接的重要性了,它規定全部的鏈接必須都是持久的,除非顯式的在報文頭裏,經過 Connection:close
這個首部,指定在傳輸結束以後會關閉此鏈接。
實際上在 HTTP/1.1 中Connect
這個頭部已經沒有 Keep-Alive
這個取值了,因爲歷史緣由,不少客戶端和服務端,依然保留了這個報文頭。
長鏈接帶來了另一個問題,如何斷定當前數據發送完成。
在早期不支持持久鏈接的時候,實際上是能夠依靠鏈接斷開來斷定當前傳輸已經結束,大部分瀏覽器也是這麼幹的,但這並非規範的操做。應該使用 Content-Length
這個頭部,來指定當前傳輸的實體內容長度。
下面舉個例子,在保持持久鏈接的狀況下,依賴 Content-Length
來肯定數據發送完畢。
Content-Length
在這裏起到了一個響應實體已經發送結束的判斷依據。這樣的狀況下,咱們就要求 Content-Length
必須和內容實體的長度一致,若是不一致,就會出現各類問題。
如上圖所示,若是 Content-Length
小於內容實體的長度,則會截斷,反之則沒法斷定當前響應已經結束,會將請求持續掛起形成 Padding 狀態。
理想狀況下,咱們在響應一個請求的時候,就須要知道它的內容實體的大小。可是在實際應用中,有些時候內容實體的長度並無那麼容易得到。例如內容實體來自網絡文件、或者是動態生成的。這個時候若是依然想要提早獲取到內容實體的長度,只能開一個足夠大的 Buffer,等內容所有緩存好了再計算。
但這並非一個好的方案,所有緩存到 Buffer 裏,第一會消耗更多的內存,第二也會更耗時,讓客戶端等待太久。
此時就須要一個新的機制,不依賴 Content-Length
的值,來斷定當前內容實體是否傳輸完成,此時就須要 Transfer-Encoding
這個頭部來斷定。
前面也提到,Transfer-Encoding
在最新的 HTTP/1.1 協議裏,就只有 chunked
這個參數,標識當前爲分塊編碼傳輸。
分塊編碼傳輸既然只有一個可選的參數,咱們就只須要指定它爲 Transfer-Encoding:chunked
,後續咱們就能夠將內容實體包裝一個個塊進行傳輸。
分塊傳輸的規則:
1. 每一個分塊包含一個 16 進制的數據長度值和真實數據。
2. 數據長度值獨佔一行,和真實數據經過 CRLF(\r\n) 分割。
3. 數據長度值,不計算真實數據末尾的 CRLF,只計算當前傳輸塊的數據長度。
4. 最後經過一個數據長度值爲 0 的分塊,來標記當前內容實體傳輸結束。
在這個例子中,首先在響應頭部裏標記了 Transfer-Encoding: chunked
,後續先傳遞了第一個分塊 「0123456780」,長度爲 b(11 的十六進制),以後分別傳輸了 「Hello CxmyDev」 和 「123」,最後以一個長度爲 0 的分塊標記當前響應結束。
當咱們使用 chunked 進行分塊編碼傳輸的時候,傳輸結束以後,還有機會在分塊報文的末尾,再追加一段數據,此數據稱爲拖掛(Trailer)。
拖掛的數據,能夠是服務端在末尾須要傳遞的數據,客戶端實際上是能夠忽略並丟棄拖掛的內容的,這就須要雙方協商好傳輸的內容了。
在拖掛中能夠包含附帶的首部字段,除了 Transfer-Encoding、Trailer 以及 Content-Length 首部以外,其餘 HTTP 首部均可以做爲拖掛發送。
通常咱們會使用拖掛來傳遞一些在響應報文開始的時候,沒法肯定的某些值,例如:Content-MD5 首部就是一個常見的在拖掛中追加發送的首部。和長度同樣,對於須要分塊編碼傳輸的內容實體,在開始響應的時候,咱們也很難算出它的 MD5 值。
注意這裏在頭部增長了 Trailder
,用以指定末尾還會傳遞一個 Content-MD5 的拖掛首部,若是有多個拖掛的數據,可使用逗號進行分割。
內容編碼和傳輸編碼通常都是配合使用的。咱們會先使用內容編碼,將內容實體進行壓縮,而後再經過傳輸編碼分塊發送出去。客戶端接收到分塊的數據,再將數據進行從新整合,還原成最初的數據。
咱們對傳輸編碼應該有必定的瞭解了。這裏簡單總結一下:
1. 傳輸編碼使用 Transfer-Encoding 首部進行標記,在最新的 HTTP/1.1 協議裏,它只有 chunked 這一個取值,表示分塊編碼。
2. 傳輸編碼主要是爲了解決持久鏈接裏將數據分塊傳輸以後,斷定內容實體傳輸結束。
3. 分塊的格式:數據長度(16進制)+ 分塊數據。
4. 若是還有額外的數據,能夠在結束以後,使用 Trailer
進行拖掛傳輸額外的數據。
5. 傳輸編碼一般會配合內容編碼一塊兒使用。
此外,傳輸編碼應該是全部 HTTP/1.1 的標準實現,應該都有支持,若是收到沒法理解的通過傳輸編碼的報文,應該直接返回 501 Unimplemented 這個狀態碼來回復便可。
參考鏈接:
公衆號後臺回覆成長『成長』,將會獲得我準備的學習資料,也能回覆『加羣』,一塊兒學習進步;你還能回覆『提問』,向我發起提問。
推薦閱讀: