HTTP/2之旅 (翻譯)

Journey to HTTP/2
HTTP/2html

距離我上一次經過博客寫做以來, 通過了很長的一段安靜的時間. 由於一直沒有足夠的時間投入其中. 直到如今有了一些空閒的時間, 我想利用他們寫一些HTTP相關的文章.git

HTTP是一種協議, 每個web開發者都應該知道他是如何推動整個網絡的 並應該清楚的知道他是如何幫助你開發更好的應用.github

什麼是HTTP

首先, 什麼是HTTP? HTTP是一種基於TCP/IP的應用層的傳輸協議, 規定了客戶端和服務器端如何進行通訊的. 定義了在物聯網中是請求和傳送的內容. 對於應用層的協議, 我理解的只是一層抽象的協議, 讓主機(客戶端和服務器)之間的交流標準化, 而且依賴於TCP/IP來完成客戶端之間的請求和響應.TCP默認使用80端口, 也可使用其餘的端口. HTTPS使用過的是443端口.web

HTTP/0.9 一個班機(開始的協議)(1991)

第一個HTTP的版本是HTTP/0.9在1991年以前推出. 那是一種很是簡單的協議, 含有一個簡單的被稱爲GET的方法. 若是一個客戶端經過訪問服務器上的一些網頁, 他會發出一個下面這種的簡單請求.瀏覽器

GET /index.html

服務器返回的內容以下面展現的緩存

(response body)
(connection closed)

這就是服務器得到的請求, 在響應中返回一個HTML, 只要內容開始傳輸, 那響應就會關閉. 他是安全

  • 無頭響應
  • GET只是一個請求方法
  • 響應一個HTML服務器

    正如你看到的, 協議真的沒什麼, 除了做爲將來發展的一個踏板.cookie

HTTP/1.0-1996

在1996年, 下一個HTTP版本, 即HTTP/1.0版本被開發, 大大超過了上一個版本.網絡

不一樣於HTTP/0.9只能定義HTML響應, HTTP/1.0可以定義其餘響應格式, 即圖片, 視頻文件, 普通文本和其餘任何的內容類型. 他增長了更多的方法(即, HEADPOST), 請求和響應的格式沒有改變, HTTP頭部能夠在請求和響應都增長, 定義額外的狀態碼, 引入字符集的支持, 多部分類型, 做者, 緩存, 內容格式化而且支持更多

下面是一個簡單的HTTP/1.0的請求和響應看起來大概如此:

GET / HTTP/1.0
Host: kamranahmed.info
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS)
Accept: */*

正如你看到的, 經過這個請求, 客戶端也能夠發送他的我的信息, 支持的響應類型內容. 在HTTP/0.9中客戶端並無辦法發送這些信息, 由於沒有頭部.

對於上面的請求可能有以下的示例響應

HTTP / 1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

(response body)
(connection closed)

每個響應的開始都是HTTP/1.0(HTTP後面跟着的是版本號), 而後是狀態碼200, 後面跟着的事緣由短語(若是你須要的話, 也能夠是對於狀態碼的描述)

在這個新的版本中, 請求和響應頭, 依舊都是使用ASCII進行編碼, 可是響應主體可使用任何類型, 即, 圖片, 視頻, 普通文本和其餘的任何內容類型. 因此, 如今服務器能夠發送任何類型的響應給客戶端; 在HTTP引入後不久, "超文本"一詞, 在HTTP中變得並不適用. HMTP或者超媒體傳輸協議也許更加適用於場景, 可是, 我想, 咱們仍是堅持使用生命這個名字.

HTTP/1.0一個主要的缺點是在每個連接中使用不一樣的請求方式. 結果就是, 不管何時, 客戶端都是爲了從服務器得到一些東西, 他會打開一個新的TCP連接, 稍後一個簡單的請求會徹底使用這個連接. 而後這個連接就關閉了. 不管下一個請求是什麼, 都會打開一個新的連接. 爲何壞呢? 很好, 讓咱們假設, 你瀏覽的網站有10張圖片, 5個樣式文件, 和5個JavaScript文件, 當網頁打開的時候, 須要一共進行20次請求. 由於請求一旦被知足, 服務器就會關閉連接. 將會有一系列獨立的20個連接, 每一個連接都在本身獨立的連接上提供服務. 大量的連接致使嚴重的性能損失, 由於創建一個新的TCP形成明顯的性能損失, 由於三次握手的創建啓動很是慢.

三次握手 (Three-way Handshake)

三次握手的簡單創建過程: 全部的TCP連接都是經過三次握手開始的, 也就是客戶端和服務器在發送應用數據以前, 發送一系列的數據包.

  • SYN - 客戶端挑選一個隨機數, 咱們稱之爲x, 而後發送給服務器端.
  • SYN ACK - 服務器接收到請求以後, 發送一個ACK包返回給客戶端, 也是一個隨機的數字, 咱們把服務器選出的數字稱之爲y, 而且和x+1, 這裏的x是經過客戶端發送給服務器的.
  • ACK - 客戶端將從服務器收到的數字y增長, 返回一個ACK的數據包, 包含一個數字y+1.

    一次完整的三次握手的過程就完成了, 客戶端和服務器端的數據就能夠開始傳輸了. 須要注意的是, 客戶端一旦發送完最後一個ACK數據包, 就當即開始發送應用數據, 可是服務器端須要等到最後一個ACK包接受完成纔會去響應請求.

    圖片

須要注意, 這張圖片有一個嚴重的問題, 最後一次經過客戶端發送的數據包ACK, 此次握手應該只包括y+1, 也就是, 應該使用ACK: Y+1替代ACK: x+1, y+1

然而, HTTP / 1.0的一些方案嘗試經過增長一個請求頭Connection: keep-alive去解決這個問題. 那意味着告訴服務器: "你好, 服務器, 不要關閉這個連接, 我還須要它", 但由於沒有普遍使用, 因此這個問題依舊存在.

除了無鏈接, HTTP也是一個無狀態的協議, 也就是服務器不會存儲有關客戶端的信息, 因此每個請求都必須含有服務器可以完成請求的獨立信息, 和任何老的請求沒有關係. 致使了, 大量分開的請求在客戶端打卡的時候, 須要發送一些多餘數據, 增長了網絡帶寬的使用.

HTTP / 1.1 - 1999

僅僅3年, 就在1999年發佈了下一個版本, HTTP / 1.1, 對上個版本進行了大幅改進. 對於HTTP/1.0的主要改進包括:

  • 新的HTTP方法增長了PUT, PATCH, 'OPTIONS', 'DELETE'
  • 主機名錶示, 在HTTP/1.0中請求頭中的Host並非必須的, 但在HTTP/1.1就是必須的了.
  • 上面提到的持久連接: 在HTTP/1.0中, 每一個連接是惟一一個請求, 只要請求完成了, 就關閉了, 致使了嚴重的性能浪費和一些潛在的問題. HTTP/1.1引入了持久連接, 也就是連接默認是不關閉的, 會一直保持打開, 容許多個連續的請求. 能夠利用請求頭上的Connection: close關閉連接. 客戶端一般在最後一次請求中發送這個請求頭來關閉鏈接狀態.
  • 開始支持管道流Pipelining, 就是客戶端能夠發送多個請求到服務端, 不須要等待服務器在同一個鏈接上的響應, 而且服務器端在街道請求以後, 會遵循同樣的順序返回響應. 可是客戶端如何知道這是第一個響應下載完成的點. 和下一個響應什麼時候開始. 爲了解決這個問題, 必須在頭部使用Content -Length, 那可讓客戶端區分出相應結束的地方, 而且能夠開始等待下一個響應.
  • 應該注意的是, 爲了從持續鏈接和管道流中獲利, 響應中的Content-Length必須是可用的, 由於這可讓客戶端知道傳輸完成, 並能夠繼續開始下一次請求(普通連續的請求方式)或者開始等待下一次響應(當管道流可使用的時候)
  • 當這種方式依舊有個問題: 若數據是動態的, 服務器沒有辦法提早知道內容大小. 這種請求, 你的確不能使用持續鏈接. 爲了解決這個問題, HTTP/1.1動態引入了動態編碼. 在這種狀況下, 服務器並不能經過分開編碼省略內容長度. 然而, 若是這些方法都不能使用, 連接在最後一次請求後必須關閉.
  • 當服務器並不能在傳輸開始的時候計算出Content-Length, 會對內容進行分塊傳輸, 那意味着一塊一塊的發送數據, 而且對發送的每個快添加一個Content-Length, 當全部數據塊發送完成的時候, 也就是此次傳輸完成了, 就會發送一個空的數據塊, 就是一個Content-Lenght是0的數據塊, 標誌這客戶端的此次傳輸完成了. 爲了標誌出客戶端的傳輸, 服務器端應該在請求頭上添加一個Transfer-Encoding: chunked.
  • 不像HTTP/1.0中只有一個基礎的認證, HTTP/1.1A還包括了摘要和代理認證.
  • 緩存
  • 字節範圍
  • 字符集合
  • 語言談判(Language negotiation? 這特麼是什麼啊?)
  • 客戶端cookies
  • 支持加強壓縮
  • 新的狀態碼
  • 等等

    我並不許備在這篇文章裏面, 徹底展開素有HTTP/1.1的功能. 你能夠經過本身去了解更多. 我推薦你閱讀Key differences between HTTP/1.0 and HTTP/1.1, 還還有一個不錯的original RFC

    HTTP/1.1在1999年發佈後, 已經存在不少年了. 即便它可以不錯的提升性能, 但網絡世界天天都在變化, 它有些力不從心. 現在加載一個網頁特別消耗資源. 一個簡單的網頁至少打開30個連接. 即便HTTP/1.1引入了持久鏈接, 爲何這麼鏈接呢? 由於在HTTP/1.1中任什麼時候間, 都只能有一個未完成的連接. 在HTTP/1.1嘗試經過管道流水線操做(pipelining)去解決, 但由於**線頭阻塞(head-of-line-blocking)**的緣由沒能解決問題, 指的是, 若是緩慢和繁重的請求可能會阻塞後面的請求, 一但某個管道中的請求被阻塞了, 那就不能不等到下一次請求被知足. (TODO: 這裏沒有很好的理解管道流的概念). 爲了解決在HTTP/1.1`中的這些缺點, 開發者開始實行變通的方法. 例如, 使用精靈圖, 在對CSS中的圖片進行編碼, 惟一一個極大的CSS/JavaScript文件, 主域分割(domain sharding)等

SPDY - 2009

Google帶頭開始嘗試新的協議, 來提升web速度, 提高web的安全性, 下降網頁加載延遲. 在2009年, 他們發佈了SPDY.

SPDY, 是谷歌的商標, 並不是是首字母縮寫

協議提出, 咱們能夠經過提升帶寬的方式提升網絡的性能, 可是有一個點, 過了這個點, 就沒有辦法大量的提高性能了. 但若是你對延遲也這麼作, 就是咱們繼續下降延遲, 延遲是性能提升的常數, 也就是下降延遲, 就能夠提升西性能. 在SPDY以後, 關於性能提高有一個重要理念, 下降延遲, 以此提升網站的性能.

當咱們並不請求其中的不一樣, 延遲就是延遲, 也就是, 數據在服務器和客戶端之間須要的傳遞時間(使用毫秒計算.) 帶寬是指每秒鐘數據傳輸的總量(bit/每秒)

SPDY的功能包括: 多路優化, 壓縮, 優先級劃分, 安全性等. 在這裏並不會深刻講解SPDY, 由於下面的HTTP/2協議大部分都受到了SPDY的啓發.

SPDY並無嘗試取代HTTP; 它是HTTP所在應用數據層之上的傳輸層, 在請求發送到網絡以前對其進行修改. 它開始在實際中投入使用, 大部分的瀏覽器開始使用它.

2015年, Google並不但願出現競爭的兩種協議, 他們決定把它合併到HTTP中, 產生HTTP/2, 再也不使用SPDY.

HTTP/2 - 2015

如今, 你必定確信, 咱們須要另外一個加強版的HTTP協議. HTTP/2是爲了下降內容的延遲傳輸而設計的. 和HTTP/1.1主要區別或者功能, 包括:

  • 使用二進制替代文本
  • 多路複用(Multiplexing) - 多個異步HTTP請求使用同一個連接.
  • 使用HPACK壓縮頭部
  • 服務端推送 - 對同一個請求的多個響應
  • 請求優化
  • 安全性

    圖片

名詞解釋

這裏參考: HTTP/2

  • Message: 邏輯上的request, response.
  • Frame: 數據傳輸中的最小單位. 每一個Frame都屬於一個特定的stream或者整個連接.
    • Length: Frame的長度, 默認最大16kb, 若是要更大須要設置max frame size
    • Type: Frame的類型, 有DATA, HEADRES, PRIORITY等
    • Flag 和 R: 保留位
    • Stream identifier: 標識所屬於的stream, 若是爲0, 表示這個frame屬於整條連接.
    • Frame Payload: 不一樣的type, 有不一樣的格式.
  • Stream: 一個雙向流, 一條連接能夠有多個stream.
    • HTTP/2依靠streams實現了多路複用, 提升了連接的利用率.
    • 一條鏈接能夠包含多個streams, 多個streams發送的數據互不影響
    • Stream能夠被client和server單方面使用, 也能夠共享使用
    • Stream會肯定好發送frame的順序, 另外一端按照接收到的順序處理
    • Stream會有惟一的標識.
      • 若是是客戶端建立的stream, ID是奇數.若是是server建立的, ID就是偶數. ID 0x00和0x01都有特定用途.
      • Stream不可能被重複利用, 若是一條連接的ID分配完了, client會新建一條鏈接. 而server則會給clent發送一個GOAWAY frame強制client新建一條連接.
      • 爲了更大的一條鏈接上面的stream併發, 能夠考慮調大SETTING_MAX_CONCURRENT_STREAMS

1. 二進制協議

HTTP/2嘗試經過二進制協議的方法解決,如今HTTP/1.1的延遲問題. 做爲一個二進制協議, 他更容易被解析, 但不容易被人眼所辨識. HTTP/2主要使用Frames和Streams進行構建.

介紹下: Frames和Streams

HTTP中的信息, 如今可以被壓縮成爲一個或多個frames. HEADRSframe爲了元數據(meta data), DATAframe爲了有效荷載(payload), 還有存在其餘集中類型的frames(HEADRS, DATA, RST_STREAM, SETTINGS, PRIORITY等), 你能夠查看the HTTP/2 specs

每個HTTP/2的請求和響應都會生成一個惟一的streamID並分配給frames. Frames只是二進制的數據. 一個frames的連接被稱爲Stream. 每個frame都有stream id. 用來標記他所屬於的stream, 每個frame都有相同的頭部. 此外, 除了Stream ID 是惟一的之外, 值得一提的是, 客戶端發定義的任何一個請求中的streamID都使用奇數, 服務器的每個響應中的streamID都是用偶數.

除了HEADERSDATA兩種類型的frame, 其餘類型的frame中, 我想提下, RST_STREAM, 這是一種特殊的類型, 用來中斷stream, 即, 客戶端發送了這種frame就是告訴服務器, 我不再須要這種stream了. 在HTTP/1.1中, 惟一一種可讓服務器端中止發送數據的方法, 就是響應的時候告訴客戶端關閉這條鏈接. 致使了延遲增長, 由於一個新的連接須要很是屢次的請求進行打開. 在HTTP/2中, 客戶端可使用RST_STREAM, 去中止接受一個特殊的Stream, 這個連接會一直保持着打開, 另外一個stream會繼續使用.

2. 多路複用(Multiplexing)

由於HTTP/2如今是一個二進制的協議, 正如前面所說的, 他使用frames和stream進行請求和響應, 一個TCP連接一旦被打開, 全部的stream均可以使用相同的連接進行異步的發送, 不須要再增長任何連接. 相反, 服務器也能夠進行相同的異步響應方法, 即, 響應沒有順序, 客戶度使用streamID來進行特殊數據包的區分. 這樣就能夠解決一個頭部阻塞問題(head-of-line-blocking)的問題. 客戶端不須要話費時間一直等待請求, 其餘請求仍然被正常處理.

3. HPACK頭部壓縮(HPACK Header Compression)

這是RFC中單獨的一部分, 這是RFC針對優化發送的報文頭部. 當同一個客戶端不斷訪問着服務器的時候, 會帶着不少多餘的數據. 咱們一遍又一遍的發送着報文頭部, 有時候, 會有cookies增長報文頭部的大小, 致使的帶寬的使用增長了時間延遲. 爲了解決這個問題, HTTP/2使用了頭部壓縮.

圖片

與請求響應不一樣的是, 頭部信息沒法經過gzip或者compress等格式壓縮, 這裏的頭部壓縮使用了一種徹底不一樣的機制. 文字值使用Huffman編碼機, 頭部信息表經過客戶端和服務端, 而且在客戶端和服務端都省略了請求隊列中重複的頭部信息. 好比: 用戶信息等. 使用二者都在維護的頭部表進行引用.

當咱們討論標題的時候, 補充一點, 頭部仍然和HTTP/1.1保持相同, 除了添加的一些僞標題, 即::method, :scheme, :host:path.

4. 服務器推送

服務器推送是另外一個在服務器端強大的功能, 都知道當客戶端請求一個肯定的資源的時候, 服務器能用把這個資源推送給客戶端, 甚至不須要客戶端推送. 舉個例子: 當瀏覽器加載一個web頁面, 他會格式化整個頁面, 找出須要從服務器段獲取的內容, 後隨之發送請求給服務器獲取內容.

服務器端推送, 容許當服務器知道客戶端須要的數據時, 經過推送的數據, 來減小往返次數. 他是如何完成的, 服務器發送一個特殊的數據幀, 命名爲PUSH_PROMISE通知客戶端, "嗨, 我將會把整個資源發送給你, 不須要再詢問我了". 這個PUSH_PROMISE數據幀與致使推送發生的流相關, 他其中包括了流ID, 即, 整個數據流就是服務器端將要推送的數據.

5. 請求優化

當stream數據流打開的時候, 客戶端向HEADERS數據幀中注入一個優化信息, 來對stream進行優化. 在任什麼時候候, 客戶度都可以發送一個PRIORITY數據幀來改變steam的優化.

若是不含有優化信息, 服務器異步響應請求. 也就是沒有順序. 若是給stream分配一個優化, 其中至少含有優化信息, 服務器可以決定, 針對整個請求, 須要執行返回多少資源.

6. 安全性

是否應該在HTTP/2中強制使用TLS, 引發了普遍討論. 最後決定不會強制使用. 然而, 大部分的廠商表示, 他們只會在TLS層面上支持HTTP\2. 因此, 即便HTTP/2規範中不須要加密, 可是已經成爲了一種默認的選項. HTTP\2經過TLS實現中的確有一些要求. 必須使用1.2或者更高版本的TLS, 必須含有必定級別的最小祕鑰, 須要含有臨時祕鑰.
HTTP/2在兼容性方面, 已經漸漸超過SPDY. 在許多方面提供了性能優點, 不用多久, 咱們就能夠開始用了.

HTTP/2的詳細細節感興趣的人, 能夠訪問link to specsdemonstrating the performance benefits of HTTP/2.. 歡迎在評論中提出疑問,但願指出在閱讀過程當中遇到的錯誤點. 下次見.

相關文章
相關標籤/搜索