半小時搞懂 HTTP、HTTPS和HTTP2

本文將盡可能用通俗易懂的方式來向讀者講述 HTTP 的知識。javascript

另外,建議在學習 HTTP 知識的時候,利用 Chrome 開發者工具來作實踐,這能夠幫助你理解得更深入。html

在這裏插入圖片描述

(此圖在網上找來的,侵刪)前端

HTTP 概述

HTTP 超文本傳輸​​協議是位於 TCP/IP 體系結構中的應用層協議,它是萬維網數據通訊的基礎。java

當咱們訪問一個網站時,須要經過統一資源定位符(uniform resource locator,URL)來定位服務器並獲取資源。node

<協議>://<域名>:<端口>/<路徑>

一個 URL 的通常形式一般如上所示(http://test.com/index.html ),如今最經常使用的協議就是 HTTP,HTTP 的默認端口是 80,一般能夠省略。webpack

在這裏插入圖片描述

HTTP/1.1

HTTP/1.1 是目前使用最普遍的版本,通常沒有特別標明版本都是指 HTTP/1.1。git

HTTP 鏈接創建過程

咱們來看一下在瀏覽器輸入 URL 後獲取 HTML 頁面的過程。github

  1. 先經過域名系統(Domain Name System,DNS)查詢將域名轉換爲 IP 地址。即將 test.com 轉換爲 221.239.100.30 這一過程。
  2. 經過三次握手(稍後會講)創建 TCP 鏈接。
  3. 發起 HTTP 請求。
  4. 目標服務器接收到 HTTP 請求並處理。
  5. 目標服務器往瀏覽器發回 HTTP 響應。
  6. 瀏覽器解析並渲染頁面。

下圖中的 RTT 爲往返時延(Round-Trip Time: 往返時延。表示從發送端發送數據開始,到發送端收到來自接收端的確認,總共經歷的時延)。web

在這裏插入圖片描述

HTTP 鏈接拆除過程

全部 HTTP 客戶端(瀏覽器)、服務器均可在任意時刻關閉 TCP 鏈接。一般會在一條報文結束時關閉鏈接,但出錯的時候,也可能在首部行的中間或其餘任意位置關閉鏈接。算法

TCP 三次握手和四次揮手

因爲 HTTP 是基於 TCP 的,因此打算在這補充一下 TCP 鏈接創建和拆除的過程。

首先,咱們須要瞭解一些 TCP 報文段的字段和標誌位:

  1. 32 比特的序號字段和確認號字段,TCP 字節流每個字節都按順序編號。確認號是接收方指望從對方收到的下一字節的序號。
  2. ACK 標誌位,用於指示確認字段中的值是有效的 ACK=1 有效,ACK=0 無效。
  3. SYN 標誌位,用於鏈接創建,SYN 爲 1 時,代表這是一個請求創建鏈接報文。
  4. FIN 標誌位,用於鏈接拆除,FIN 爲 1 時,代表發送方數據已發送完畢,並要求釋放鏈接。

在這裏插入圖片描述

TCP 三次握手創建鏈接

TCP 標準規定,ACK 報文段能夠攜帶數據,但不攜帶數據就不用消耗序號。

  1. 客戶端發送一個不包含應用層數據的 TCP 報文段,首部的 SYN 置爲 1,隨機選擇一個初始序號(通常爲 0)放在 TCP 報文段的序號字段中。(SYN 爲 1 的時候,不能攜帶數據,但要消耗掉一個序號)
  2. TCP 報文段到達服務器主機後,服務器提取報文段,併爲該 TCP 鏈接分配緩存和變量。而後向客戶端發送容許鏈接的 ACK 報文段(不包含應用層數據)。這個報文段的首部包含 4 個信息:ACK 置 爲 1,SYN 置爲 1;確認號字段置爲客戶端的序號 + 1;隨機選擇本身的初始序號(通常爲 0)。
  3. 收到服務器的 TCP 響應報文段後,客戶端也要爲該 TCP 鏈接分配緩存和變量,並向服務器發送一個 ACK 報文段。這個報文段將服務器端的序號 + 1 放置在確認號字段中,用來對服務器容許鏈接的報文段進行響應,由於鏈接已經創建,因此 SYN 置爲 0。最後一個階段,報文段能夠攜帶客戶到服務器的數據。而且之後的每個報文段,SYN 都置爲 0。

下圖是一個具體的示例:

在這裏插入圖片描述

(此截圖是我使用 Wireshark 抓包工具截取的 TCP 報文段截圖)。

TCP 四次揮手拆除鏈接

FIN 報文段即便不攜帶數據,也要消耗序號。

  1. 客戶端發送一個 FIN 置爲 1 的報文段。
  2. 服務器回送一個確認報文段。
  3. 服務器發送 FIN 置爲 1 的報文段。
  4. 客戶端回送一個確認報文段。

TCP 爲何是四次揮手,而不是三次?

  1. 當 A 給 B 發送 FIN 報文時,表明 A 再也不發送報文,但仍能夠接收報文。
  2. B 可能還有數據須要發送,所以先發送 ACK 報文,告知 A 「我知道你想斷開鏈接的請求了」。這樣 A 便不會由於沒有收到應答而繼續發送斷開鏈接的請求(即 FIN 報文)。
  3. B 在處理完數據後,就向 A 發送一個 FIN 報文,而後進入 LAST_ACK 階段(超時等待)。
  4. A 向 B 發送 ACK 報文,雙方都斷開鏈接。

參考資料:

HTTP 報文格式

HTTP 報文由請求行、首部、實體主體組成,它們之間由 CRLF(回車換行符) 分隔開。

注意:實體包括首部(也稱爲實體首部)和實體主體,sp 便是空格 space

在這裏插入圖片描述

請求行和首部是由 ASCII 文本組成的,實體主體是可選的,能夠爲空也能夠是任意二進制數據。

請求報文和響應報文的格式基本相同。

請求報文格式

<method> <request-URL> <version>
<headers>
<entity-body>

響應報文格式

<version> <status> <reason-phrase>
<headers>
<entity-body>

一個請求或響應報文由如下字段組成

  1. 請求方法,客戶端但願服務器對資源執行的動做。
  2. 請求 URL,命名了所請求的資源。
  3. 協議版本,報文所使用的 HTTP 版本。
  4. 狀態碼,這三位數字描述了請求過程當中所發生的狀況。
  5. 緣由短語,數字狀態碼的可讀版本(例如上面的響應示例跟在 200 後面的 OK,通常按規範寫最好)。
  6. 首部,能夠有零或多個首部。
  7. 實體的主體部分,能夠爲空也能夠包含任意二進制數據。

一個 HTTP 請求示例

GET /2.app.js HTTP/1.1
Host: 118.190.217.8:3389
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Accept: */*
Referer: http://118.190.217.8:3389/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

一個 HTTP 響應示例

HTTP/1.1 200 OK
X-Powered-By: Express
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Sat, 07 Mar 2020 03:52:30 GMT
ETag: W/"253e-170b31f7de7"
Content-Type: application/javascript; charset=UTF-8
Vary: Accept-Encoding
Content-Encoding: gzip
Date: Fri, 15 May 2020 05:38:05 GMT
Connection: keep-alive
Transfer-Encoding: chunked

方法

方法 描述
GET 從服務器獲取一份文檔
HEAD 只從服務器獲取文檔的頭部
POST 向服務器發送須要處理的數據
PUT 將請求的數據部分存儲在服務器上
TRACE 對可能通過代理服務器傳送到服務器上去的報文進行追蹤
OPTIONS 決定能夠在服務器上執行哪些方法
DELETE 從服務器上刪除一份文檔
GET 和 HEAD

其中 GET 和 HEAD 被稱爲安全方法,由於它們是冪等的(若是一個請求無論執行多少次,其結果都是同樣的,這個請求就是冪等的),相似於 POST 就不是冪等的。

HEAD 方法和 GET 方法很相似,但服務器在響應中只返回首部。這就容許客戶端在未獲取實際資源的狀況下,對資源的首部進行檢查。使用 HEAD,能夠:

  1. 在不獲取資源的狀況下了解資源的狀況。
  2. 經過查看響應狀態碼,看看某個對象是否存在。
  3. 經過查看首部,瞭解測試資源是否被修改了。

服務器開發者必須確保返回的首部與 GET 請求所返回的首部徹底相同。遵循 HTTP/1.1 規範,就必須實現 HEAD 方法。

PUT

與 GET 方法從服務器讀取文檔相反,PUT 方法會向服務器寫入文檔。PUT 方法的語義就是讓服務器用請求的主體部分來建立一個由所請求的 URL 命名的新文檔。 若是那個文檔已存在,就覆蓋它。

POST

POST 方法一般用來向服務器發送表單數據。

TRACE

客戶端發起一個請求時,這個請求可能要穿過路由器、防火牆、代理、網關等。每一箇中間節點均可能會修改原始的 HTTP 請求,TRACE 方法容許客戶端在最終發起請求時,看看它變成了什麼樣子。

TRACE 請求會在目的服務器端發起一個「環回」診斷。行程最後一站的服務器會彈回一條 TRACE 響應,並在響應主體中攜帶它收到的原始請求報文。 這樣客戶端就能夠查看在全部中間 HTTP 應用程序組成的請求/響應鏈上,原始報文是否被毀壞或修改過。

在這裏插入圖片描述

TRACE 方法主要用於診斷,用於驗證請求是否如願穿過了請求/響應鏈。它也是一種工具,用來查看代理和其餘應用程序對用戶請求所產生的效果。 TRACE 請求中不能帶有實體的主體部分。TRACE 響應的實體主體部分包含了響應服務器收到的請求的精確副本。

OPTIONS

OPTIONS 方法請求 Web 服務器告知其支持的各類功能。

DELETE

DELETE 方法就是讓服務器刪除請求 URL 所指定的資源。

狀態碼

總體範圍 已定義範圍 分類
100~199 100~101 信息提示
200~299 200~206 成功
300~399 300~305 重定向
400~499 400~415 客戶端錯誤
500~599 500~505 服務器錯誤
300~399 重定向狀態碼

重定向狀態碼要麼告訴客戶端使用替代位置來訪問他們感興趣的資源,要麼提供一個替代的響應而不是資源的內容。 若是資源已被移動,能夠發送一個重定向狀態碼和一個可選的 Location 首部來告知客戶端資源已被移走,以及如今在哪裏能夠找到它。這樣,瀏覽器能夠在不打擾使用者的狀況下,透明地轉入新的位置。

400~499 客戶端錯誤狀態碼

有時客戶端會發送一些服務器沒法處理的東西,例如格式錯誤的請求報文、一個不存在的 URL。

500~599 服務器錯誤狀態碼

有時客戶端發送了一條有效請求,服務器自身卻出錯了。

首部

首部和方法共同配合工做,決定了客戶端和服務器能作什麼事情。

首部分類

  1. 通用首部,能夠出如今請求或響應報文中。
  2. 請求首部,提供更多有關請求的信息。
  3. 響應首部,提供更多有關響應的信息。
  4. 實體首部,描述主體的長度和內容,或者資源自身。
  5. 擴展首部,規範中沒有定義的新首部。
通用首部

有些首部提供了與報文相關的最基本信息,它們被稱爲通用首部。如下是一些常見的通用首部:

在這裏插入圖片描述

請求首部

請求首部是隻在請求報文中有意義的首部,用於說明請求的詳情。如下是一些常見的請求首部:

在這裏插入圖片描述

響應首部

響應首部讓服務器爲客戶端提供了一些額外的信息。

實體首部

實體首部提供了有關實體及其內容的大量信息,從有關對象類型的信息,到可以對資源使用的各類有效的請求方法。

例如內容首部,提供了與實體內容有關的特定信息,說明了其類型、尺寸以及處理它所需的其餘有用信息。
另外,通用的緩存首部說明了如何或何時進行緩存。實體的緩存首部提供了與被緩存實體有關的信息。
在這裏插入圖片描述

性能優化

1. 減小 HTTP 請求

每發起一個 HTTP 請求,都得經歷三次握手創建 TCP 鏈接,若是鏈接只用來交換少許數據,這個過程就會嚴重下降 HTTP 性能。因此咱們能夠將多個小文件合成一個大文件,從而減小 HTTP 請求次數。

其實因爲持久鏈接(重用 TCP 鏈接,以消除鏈接及關閉時延;HTTP/1.1 默認開啓持久鏈接)的存在,每一個新請求不必定都須要創建一個新的 TCP 鏈接。可是,瀏覽器處理完一個 HTTP 請求才能發起下一個,因此在 TCP 鏈接數沒達到瀏覽器規定的上限時,仍是會創建新的 TCP 鏈接。從這點來看,減小 HTTP 請求仍然是有必要的。

2. 靜態資源使用 CDN

內容分發網絡(CDN)是一組分佈在多個不一樣地理位置的 Web 服務器。咱們都知道,當服務器離用戶越遠時,延遲越高。CDN 就是爲了解決這一問題,在多個位置部署服務器,讓用戶離服務器更近,從而縮短請求時間。

3. 善用緩存

爲了不用戶每次訪問網站都得請求文件,咱們能夠經過添加 Expires 頭來控制這一行爲。Expires 設置了一個時間,只要在這個時間以前,瀏覽器都不會請求文件,而是直接使用緩存。

不過這樣會產生一個問題,當文件更新了怎麼辦?怎麼通知瀏覽器從新請求文件?

能夠經過更新頁面中引用的資源連接地址,讓瀏覽器主動放棄緩存,加載新資源。

具體作法是把資源地址 URL 的修改與文件內容關聯起來,也就是說,只有文件內容變化,纔會致使相應 URL 的變動,從而實現文件級別的精確緩存控制。什麼東西與文件內容相關呢?咱們會很天然的聯想到利用數據摘要要算法對文件求摘要信息,摘要信息與文件內容一一對應,就有了一種能夠精確到單個文件粒度的緩存控制依據了。

參考資料:

4. 壓縮文件

壓縮文件能夠減小文件下載時間,讓用戶體驗性更好。

gzip 是目前最流行和最有效的壓縮方法。能夠經過向 HTTP 請求頭中的 Accept-Encoding 頭添加 gzip 標識來開啓這一功能。固然,服務器也得支持這一功能。

舉個例子,我用 Vue 開發的項目構建後生成的 app.js 文件大小爲 1.4MB,使用 gzip 壓縮後只有 573KB,體積減小了將近 60%。

5. 經過 max-age 和 no-cache 實現文件精確緩存

通用消息頭部 Cache-Control 其中有兩個選項:

  1. max-age: 設置緩存存儲的最大週期,超過這個時間緩存被認爲過時(單位秒)。在這個時間前,瀏覽器讀取文件不會發出新請求,而是直接使用緩存。
  2. no-cache: 指定 no-cache 表示客戶端能夠緩存資源,每次使用緩存資源前都必須從新驗證其有效性。

咱們能夠將那些長期不變的靜態資源設置一個很是長的緩存時間,例如設置成緩存一年。

而後將 index.html 文件設置成 no-cache。這樣每次訪問網站時,瀏覽器都會詢問 index.html 是否有更新,若是沒有,就使用舊的 index.html 文件。若是有更新,就讀取新的 index.html 文件。當加載新的 index.html 時,也會去加載裏面新的 URL 資源。

例如 index.html 原來引用了 a.jsb.js,如今更新了變成 a.jsc.js。那就只會加載 c.js 文件。

具體請看 webpack + express 實現文件精確緩存

HTTPS

HTTPS 是最流行的 HTTP 安全形式,由網景公司獨創,全部主要的瀏覽器和服務器都支持此協議。 使用 HTTPS 時,全部的 HTTP 請求和響應數據在發送以前,都要進行加密。加密可使用 SSL 或 TLS。
在這裏插入圖片描述
SSL/TLS 協議做用在 HTTP 協議之下,對於上層應用來講,原來的發送/接收數據流程不變,這就很好地兼容了老的 HTTP 協議。因爲 SSL/TLS 差異不大,下面統一使用 SSL。

要想了解 HTTPS 爲什麼安全,還得繼續瞭解一下這些概念:加密算法摘要算法數字簽名數字證書

加密算法

對稱密鑰密碼體制

對稱密鑰密碼體制,即加密密鑰和解密密鑰是使用相同的密碼體制。對稱密鑰加密技術的缺點之一就是發送者和接收者在對話以前,必定要有一個共享的密鑰,因此不太安全。

公鑰密碼體制

公鑰密碼體制使用不一樣的加密密鑰與解密密鑰。公鑰密碼體制產生的主要緣由有兩個:一是對稱密鑰密碼體制的密鑰分配問題,二是對數字簽名的需求。

在公鑰密碼體制中,加密密鑰是公開的,解密密鑰是須要保密的,加密算法和解密算法也是公開的。

公鑰密碼體制的加密和解密有以下特色:

  1. 密鑰對產生器產生出接收者 B 的一對密鑰,即加密密鑰 PK 和解密密鑰 SK。
  2. 發送者 A 用 B 的公鑰 PK 做爲加密密鑰來加密信息,B 接收後用解密密鑰 SK 解密。

![\[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uWP5p7So-1589532545350)(../imgs/is3.png)\]](https://img-blog.csdnimg.cn/2...

使用對稱密鑰時,因爲雙方使用一樣的密鑰,所以在通訊信道上能夠進行一對一的雙向保密通訊,雙方均可以用同一個密鑰加密解密。

使用公開密鑰時,在通訊信道上能夠是多對一的單向保密信道。便可以有多人持有 B 的公鑰,但只有 B 才能解密。

摘要算法

摘要算法的主要特徵是加密過程不須要密鑰,而且通過加密的數據沒法被解密,目前能夠被解密逆向的只有CRC32算法,只有輸入相同的明文數據通過相同的消息摘要算法才能獲得相同的密文。

數字簽名

用加密系統對報文進行簽名,以說明是誰編寫的報文,同時證實報文未被篡改過,這種技術稱爲數字簽名

數字簽名是附加在報文上的特殊加密校驗碼。使用數字簽名的好處有:

  1. 簽名能夠證實是做者編寫了這條報文。只有做者纔會有最機密的私有密鑰,所以,只有做者才能計算出這些校驗和。
  2. 簽名能夠防止報文被篡改,若是有人在報文傳輸過程當中對其進行了修改,校驗和就再也不匹配了。

數字簽名一般是用非對稱公開密鑰技術產生的。

在這裏插入圖片描述

看上圖,任何人都能用 A 的公鑰 PK 對密文進行 E 運算後獲得 A 發送的明文。可見這種通訊並不是爲了保密,而是爲了進行簽名和核實簽名,即確認此信息是 A 發送的(使用 A 的密鑰進行加密的報文,只有使用 A 的公鑰才能正確解密)。 但上述過程僅對報文進行了簽名,對報文 X 自己卻未保密,因此要採用下圖的方法,同時實現祕密通訊和數字簽名。

在這裏插入圖片描述

數字證書

假如你想訪問一個網站,怎麼確保對方給你的公鑰是你想訪問的網站的公鑰,而不是被中間人篡改過的?

數字證書的出現就是爲了解決這個問題,它是由數字證書認證機構頒發的,用來證實公鑰擁有者的身份。換句話說,數字證書的做用就至關於人的身份證,身份證證實了張三就是張三,而不是別人。

數字證書通常包含如下內容

  1. 對象的名稱(人、服務器、組織等);
  2. 過時時間;
  3. 證書發佈者(由誰爲證書擔保);
  4. 來自證書發佈者的數字簽名;
  5. 對象的公鑰;
  6. 對象和所用簽名算法的描述性信息。

任何人均可以建立一個數字證書,但由誰來擔保纔是重點。

數字證書的數字簽名計算過程

  1. 用摘要算法對數字證書的內容計算出摘要;
  2. 用數字證書的私鑰對摘要進行加密獲得數字簽名。

在這裏插入圖片描述

當瀏覽器收到證書時,會對簽名頒發機構進行驗證,若是頒發機構是個頗有權威的公共簽名機構,瀏覽器可能就知道其公開密鑰了(瀏覽器會預裝不少簽名頒發機構的證書)。若是對簽名頒發機構一無所知,瀏覽器一般會向用戶顯示一個對話框,看看他是否相信這個簽名發佈者。

由於數字證書的公鑰是公開的,任何人均可以用公鑰解密出數字證書的數字簽名的摘要,而後再用一樣的摘要算法對證書內容進行摘要計算,將得出的摘要和解密後的摘要做對比,若是內容一致則說明這個證書沒有被篡改過,能夠信任。

這個過程是創建在被你們所承認的證書機構之上獲得的公鑰,因此這是一種安全的方式。

在這裏插入圖片描述

HTTPS 鏈接創建過程

HTTPS 鏈接創建過程和 HTTP 差很少,區別在於 HTTP(默認端口 80) 請求只要在 TCP 鏈接創建後就能夠發起,而 HTTPS(默認端口 443) 在 TCP 鏈接創建後,還須要經歷 SSL 協議握手,成功後才能發起請求。

在這裏插入圖片描述

在這裏插入圖片描述

我知道確定會有人不知足於簡化版的 SSL 握手過程,因此我找了一篇文章SSL/TLS 握手過程詳解,這篇文章很是詳細的講解了 SSL 握手的每一步驟。建議有興趣的同窗看一看。

HTTP/2

HTTP/2 是 HTTP/1.x 的擴展,而非替代。因此 HTTP 的語義不變,提供的功能不變,HTTP 方法、狀態碼、URL 和首部字段等這些核心概念也不變。

之因此要遞增一個大版本到 2.0,主要是由於它改變了客戶端與服務器之間交換數據的方式。HTTP 2.0 增長了新的二進制分幀數據層,而這一層並不兼容以前的 HTTP 1.x 服務器及客戶端——是謂 2.0。

HTTP/2 鏈接創建過程

如今的主流瀏覽器 HTTP/2 的實現都是基於 SSL/TLS 的,也就是說使用 HTTP/2 的網站都是 HTTPS 協議的,因此本文只討論基於 SSL/TLS 的 HTTP/2 鏈接創建過程。

基於 SSL/TLS 的 HTTP/2 鏈接創建過程和 HTTPS 差很少。在 SSL/TLS 握手協商過程當中,客戶端在 ClientHello 消息中設置 ALPN(應用層協議協商)擴展來代表指望使用 HTTP/2 協議,服務器用一樣的方式回覆。經過這種方式,HTTP/2 在 SSL/TLS 握手協商過程當中就創建起來了。

HTTP/1.1 的問題

1. 隊頭阻塞

在 HTTP 請求應答過程當中,若是出現了某種狀況,致使響應一直未能完成,那後面全部的請求就會一直阻塞着,這種狀況叫隊頭阻塞。

2. 低效的 TCP 利用

因爲 TCP 慢啓動機制,致使每一個 TCP 鏈接在一開始的時候傳輸速率都不高,在處理多個請求後,纔會慢慢達到「合適」的速率。對於請求數據量很小的 HTTP 請求來講,這種狀況就是種災難。

3. 臃腫的消息首部

HTTP/1.1 的首部沒法壓縮,再加上 cookie 的存在,常常會出現首部大小比請求數據大小還大的狀況。

4. 受限的優先級設置

HTTP/1.1 沒法爲重要的資源指定優先級,每一個 HTTP 請求都是一視同仁。

在繼續討論 HTTP/2 的新功能以前,先把 HTTP/1.1 的問題列出來是有意義的。由於 HTTP/2 的某些新功能就是爲了解決上述某些問題而產生的。

二進制分幀層

HTTP/2 是基於幀的協議。採用分幀是爲了將重要信息封裝起來,讓協議的解析方能夠輕鬆閱讀、解析並還原信息。

而 HTTP/1.1 是以文本分隔的。解析 HTTP/1.1 不須要什麼高科技,但每每速度慢且容易出錯。你須要不斷地讀入字節,直到遇到分隔符 CRLF 爲止,同時還要考慮不守規矩的客戶端,它只會發送 LF。

解析 HTTP/1.1 的請求或響應還會遇到如下問題:

  1. 一次只能處理一個請求或響應,完成以前不能中止解析。
  2. 沒法預判解析須要多少內存。

HTTP/2 有了幀,處理協議的程序就能預先知道會收到什麼,而且 HTTP/2 有表示幀長度的字段。

在這裏插入圖片描述

幀結構

+-----------------------------------------------+
 |                 Length (24)                   |
 +---------------+---------------+---------------+
 |   Type (8)    |   Flags (8)   |
 +-+-------------+---------------+-------------------------------+
 |R|                 Stream Identifier (31)                      |
 +=+=============================================================+
 |                   Frame Payload (0...)                      ...
 +---------------------------------------------------------------+
名稱 長度 描述
Length 3 字節 表示幀負載的長度,取值範圍爲 (2 的 14 次方)至 (2 的 24 次方 - 1)。(2 的 14 次方) 16384 字節是默認的最大幀大小,若是須要更大的幀,必須在 SETTINGS 幀中設置
Type 1 字節 當前幀類型(見下表)
Flags 1 字節 具體幀類型的標識
R 1 位 保留位,不要設置,不然可能會帶來嚴重的後果
Stream Identifier 31 位 每一個流的惟一 ID
Frame Payload 長度可變 真實的幀內容,長度是在 Length 字段中設置的

因爲 HTTP/2 是分幀的,請求和響應均可以多路複用,有助於解決相似相似隊頭阻塞的問題。

幀類型

名稱 ID 描述
DATA 0x0 傳輸流的核心內容
HEADERS 0x1 包含 HTTP 首部,和可選的優先級參數
PRIORITY 0x2 指示或更改流的優先級和依賴
RST_STREAM 0x3 容許一端中止流(一般因爲錯誤致使的)
SETTINGS 0x4 協商鏈接級參數
PUSH_PROMISE 0x5 提示客戶端,服務器要推送些東西
PING 0x6 測試鏈接可用性和往返時延(RTT)
GOAWAY 0x7 告訴另外一端,當前的端已結束
WINDOW_UPDATE 0x8 協商一端將要接收多少字節(用於流量控制)
CONTINUATION 0x9 用以擴展 HEADERS 模塊

多路複用

在 HTTP/1.1 中,若是客戶端想發送多個並行的請求,那麼必須使用多個 TCP 鏈接。

而 HTTP/2 的二進制分幀層突破了這一限制,全部的請求和響應都在同一個 TCP 鏈接上發送:客戶端和服務器把 HTTP 消息分解成多個幀,而後亂序發送,最後在另外一端再根據流 ID 從新組合起來。

這個機制爲 HTTP 帶來了巨大的性能提高,由於:

  • 能夠並行交錯地發送請求,請求之間互不影響;
  • 能夠並行交錯地發送響應,響應之間互不干擾;
  • 只使用一個鏈接便可並行發送多個請求和響應;
  • 消除沒必要要的延遲,從而減小頁面加載的時間;
  • 沒必要再爲繞過 HTTP 1.x 限制而多作不少工做;

在這裏插入圖片描述

HTTP/2 規範對流的定義是:HTTP/2 鏈接上獨立的、雙向的幀序列交換。若是客戶端想要發出請求,它會開啓一個新流,而後服務器在這個流上回復。 因爲有分幀,因此多個請求和響應能夠交錯,而不會互相阻塞。流 ID 用來標識幀所屬的流。

客戶端到服務器的 HTTP/2 鏈接創建後,經過發送 HEADERS 幀來啓動新的流。若是首部須要跨多個幀,可能還會發送 CONTINUATION 幀。該 HEADERS 幀可能來自請求或響應。 後續流啓動的時候,會發送一個帶有遞增流 ID 的新 HEADERS 幀。

消息

HTTP 消息泛指 HTTP 請求或響應,消息由一或多個幀組成,這些幀能夠亂序發送,而後再根據每一個幀首部的流 ID 從新組裝。

一個消息至少由 HEADERS 幀(它初始化流)組成,而且能夠另外包含 CONTINUATION 和 DATA 幀,以及其餘的 HEADERS 幀。

在這裏插入圖片描述

HTTP/1.1 的請求和響應部分都分紅消息首部和消息體兩部分;HTTP/2 的請求和響應分紅 HEADERS 幀和 DATA 幀。

優先級

把 HTTP 消息分解爲不少獨立的幀以後,就能夠經過優化這些幀的交錯和傳輸順序,進一步提高性能。

經過 HEADERS 幀和 PRIORITY 幀,客戶端能夠明確地和服務器溝通它須要什麼,以及它須要這些資源的順序。具體來說,服務器能夠根據流的優先級,控制資源分配(CPU、內存、帶寬),而在響應數據準備好以後,優先將最高優先級的幀發送給客戶端。

流量控制

在同一個 TCP 鏈接上傳輸多個數據流,就意味着要共享帶寬。標定數據流的優先級有助於按序交付,但只有優先級還不足以肯定多個數據流或多個鏈接間的資源分配。

爲解決這個問題,HTTP/2 爲數據流和鏈接的流量控制提供了一個簡單的機制:

  • 流量控制基於每一跳進行,而非端到端的控制;
  • 流量控制基於 WINDOW_UPDATE 幀進行,即接收方廣播本身準備接收某個數據流的多少字節,以及對整個鏈接要接收多少字節;
  • 流量控制窗口大小經過 WINDOW_UPDATE 幀更新,這個字段指定了流 ID 和窗口大小遞增值;
  • 流量控制有方向性,即接收方可能根據本身的狀況爲每一個流乃至整個鏈接設置任意窗口大小;
  • 流量控制能夠由接收方禁用,包括針對個別的流和針對整個鏈接。

HTTP/2 鏈接創建以後,客戶端與服務器交換 SETTINGS 幀,目的是設置雙向的流量控制窗口大小。除此以外,任何一端均可以選擇禁用個別流或整個鏈接的流量控制。

服務器推送

HTTP/2 新增的一個強大的新功能,就是服務器能夠對一個客戶端請求發送多個響應。換句話說,除了對最初請求的響應外,服務器還能夠額外向客戶端推送資源,而無需客戶端明確地請求。

在這裏插入圖片描述

爲何須要這樣一個機制呢?一般的 Web 應用都由幾十個資源組成,客戶端須要分析服務器提供的文檔才能逐個找到它們。那爲何不讓服務器提早就把這些資源推送給客戶端,從而減小額外的時間延遲呢?服務器已經知道客戶端下一步要請求什麼資源了,這時候服務器推送便可派上用場。

另外,客戶端也能夠拒絕服務器的推送。

首部壓縮

HTTP/1.1 存在的一個問題就是臃腫的首部,HTTP/2 對這一問題進行了改進,能夠對首部進行壓縮。
在一個 Web 頁面中,通常都會包含大量的請求,而其中有不少請求的首部每每有不少重複的部分。

例若有以下兩個請求:

:authority: unpkg.zhimg.com
:method: GET
:path: /za-js-sdk@2.16.0/dist/zap.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
:authority: zz.bdstatic.com
:method: GET
:path: /linksubmit/push.js
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36

從上面兩個請求能夠看出來,有不少數據都是重複的。若是能夠把相同的首部存儲起來,僅發送它們之間不一樣的部分,就能夠節省很多的流量,加快請求的時間。

HTTP/2 在客戶端和服務器端使用「首部表」來跟蹤和存儲以前發送的鍵-值對,對於相同的數據,再也不經過每次請求和響應發送。

下面再來看一個簡化的例子,假設客戶端按順序發送以下請求首部:

Header1:foo
Header2:bar
Header3:bat

當客戶端發送請求時,它會根據首部值建立一張表:

索引 首部名稱
62 Header1 foo
63 Header2 bar
64 Header3 bat

若是服務器收到了請求,它會照樣建立一張表。
當客戶端發送下一個請求的時候,若是首部相同,它能夠直接發送這樣的首部塊:

62 63 64

服務器會查找先前創建的表格,並把這些數字還原成索引對應的完整首部。

性能優化

使用 HTTP/2 代替 HTTP/1.1,自己就是一種巨大的性能提高。
這小節要聊的是在 HTTP/1.1 中的某些優化手段,在 HTTP/2 中是沒必要要的,能夠取消的。

取消合併資源

在 HTTP/1.1 中要把多個小資源合併成一個大資源,從而減小請求。而在 HTTP/2 就不須要了,由於 HTTP/2 全部的請求均可以在一個 TCP 鏈接發送。

取消域名拆分

取消域名拆分的理由同上,再多的 HTTP 請求均可以在一個 TCP 鏈接上發送,因此不須要採起多個域名來突破瀏覽器 TCP 鏈接數限制這一規則了。

參考資料

更多文章,敬請關注

本文同步分享在 博客「譚光志」(SegmentFault)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索