半小時(shí)搞懂 HTTP、HTTPS和HTTP2
另外,建議在學(xué)習(xí) HTTP 知識(shí)的時(shí)候,利用 Chrome 開發(fā)者工具來做實(shí)踐,這可以幫助你理解得更深刻。

(此圖在網(wǎng)上找來的,侵刪)
HTTP 概述
HTTP 超文本傳輸協(xié)議是位于 TCP/IP 體系結(jié)構(gòu)中的應(yīng)用層協(xié)議,它是萬維網(wǎng)數(shù)據(jù)通信的基礎(chǔ)。
當(dāng)我們?cè)L問一個(gè)網(wǎng)站時(shí),需要通過統(tǒng)一資源定位符(uniform resource locator,URL)來定位服務(wù)器并獲取資源。
<協(xié)議>://<域名>:<端口>/<路徑>
復(fù)制代碼
一個(gè) URL 的一般形式通常如上所示(http://test.com/index.html ),現(xiàn)在最常用的協(xié)議就是 HTTP,HTTP 的默認(rèn)端口是 80,通常可以省略。

HTTP/1.1
HTTP/1.1 是目前使用最廣泛的版本,一般沒有特別標(biāo)明版本都是指 HTTP/1.1。
HTTP 連接建立過程
我們來看一下在瀏覽器輸入 URL 后獲取 HTML 頁面的過程。
先通過域名系統(tǒng)(Domain Name System,DNS)查詢將域名轉(zhuǎn)換為 IP 地址。即將 test.com轉(zhuǎn)換為221.239.100.30這一過程。通過三次握手(稍后會(huì)講)建立 TCP 連接。 發(fā)起 HTTP 請(qǐng)求。 目標(biāo)服務(wù)器接收到 HTTP 請(qǐng)求并處理。 目標(biāo)服務(wù)器往瀏覽器發(fā)回 HTTP 響應(yīng)。 瀏覽器解析并渲染頁面。
下圖中的 RTT 為往返時(shí)延(Round-Trip Time:往返時(shí)延。表示從發(fā)送端發(fā)送數(shù)據(jù)開始,到發(fā)送端收到來自接收端的確認(rèn),總共經(jīng)歷的時(shí)延)。

HTTP 連接拆除過程
所有 HTTP 客戶端(瀏覽器)、服務(wù)器都可在任意時(shí)刻關(guān)閉 TCP 連接。通常會(huì)在一條報(bào)文結(jié)束時(shí)關(guān)閉連接,但出錯(cuò)的時(shí)候,也可能在首部行的中間或其他任意位置關(guān)閉連接。
TCP 三次握手和四次揮手
由于 HTTP 是基于 TCP 的,所以打算在這補(bǔ)充一下 TCP 連接建立和拆除的過程。
首先,我們需要了解一些 TCP 報(bào)文段的字段和標(biāo)志位:
32 比特的序號(hào)字段和確認(rèn)號(hào)字段,TCP 字節(jié)流每一個(gè)字節(jié)都按順序編號(hào)。確認(rèn)號(hào)是接收方期望從對(duì)方收到的下一字節(jié)的序號(hào)。 ACK 標(biāo)志位,用于指示確認(rèn)字段中的值是有效的 ACK=1 有效,ACK=0 無效。 SYN 標(biāo)志位,用于連接建立,SYN 為 1 時(shí),表明這是一個(gè)請(qǐng)求建立連接報(bào)文。 FIN 標(biāo)志位,用于連接拆除,F(xiàn)IN 為 1 時(shí),表明發(fā)送方數(shù)據(jù)已發(fā)送完畢,并要求釋放連接。

TCP 三次握手建立連接
TCP 標(biāo)準(zhǔn)規(guī)定,ACK 報(bào)文段可以攜帶數(shù)據(jù),但不攜帶數(shù)據(jù)就不用消耗序號(hào)。
客戶端發(fā)送一個(gè)不包含應(yīng)用層數(shù)據(jù)的 TCP 報(bào)文段,首部的 SYN 置為 1,隨機(jī)選擇一個(gè)初始序號(hào)(一般為 0)放在 TCP 報(bào)文段的序號(hào)字段中。(SYN 為 1 的時(shí)候,不能攜帶數(shù)據(jù),但要消耗掉一個(gè)序號(hào)) TCP 報(bào)文段到達(dá)服務(wù)器主機(jī)后,服務(wù)器提取報(bào)文段,并為該 TCP 連接分配緩存和變量。然后向客戶端發(fā)送允許連接的 ACK 報(bào)文段(不包含應(yīng)用層數(shù)據(jù))。這個(gè)報(bào)文段的首部包含 4 個(gè)信息:ACK 置 為 1,SYN 置為 1;確認(rèn)號(hào)字段置為客戶端的序號(hào) + 1;隨機(jī)選擇自己的初始序號(hào)(一般為 0)。 收到服務(wù)器的 TCP 響應(yīng)報(bào)文段后,客戶端也要為該 TCP 連接分配緩存和變量,并向服務(wù)器發(fā)送一個(gè) ACK 報(bào)文段。這個(gè)報(bào)文段將服務(wù)器端的序號(hào) + 1 放置在確認(rèn)號(hào)字段中,用來對(duì)服務(wù)器允許連接的報(bào)文段進(jìn)行響應(yīng),因?yàn)檫B接已經(jīng)建立,所以 SYN 置為 0。最后一個(gè)階段,報(bào)文段可以攜帶客戶到服務(wù)器的數(shù)據(jù)。并且以后的每一個(gè)報(bào)文段,SYN 都置為 0。
下圖是一個(gè)具體的示例:

(此截圖是我使用 Wireshark 抓包工具截取的 TCP 報(bào)文段截圖)。
TCP 四次揮手拆除連接
FIN 報(bào)文段即使不攜帶數(shù)據(jù),也要消耗序號(hào)。
客戶端發(fā)送一個(gè) FIN 置為 1 的報(bào)文段。 服務(wù)器回送一個(gè)確認(rèn)報(bào)文段。 服務(wù)器發(fā)送 FIN 置為 1 的報(bào)文段。 客戶端回送一個(gè)確認(rèn)報(bào)文段。
TCP 為什么是四次揮手,而不是三次?
當(dāng) A 給 B 發(fā)送 FIN 報(bào)文時(shí),代表 A 不再發(fā)送報(bào)文,但仍可以接收?qǐng)?bào)文。 B 可能還有數(shù)據(jù)需要發(fā)送,因此先發(fā)送 ACK 報(bào)文,告知 A “我知道你想斷開連接的請(qǐng)求了”。這樣 A 便不會(huì)因?yàn)闆]有收到應(yīng)答而繼續(xù)發(fā)送斷開連接的請(qǐng)求(即 FIN 報(bào)文)。 B 在處理完數(shù)據(jù)后,就向 A 發(fā)送一個(gè) FIN 報(bào)文,然后進(jìn)入 LAST_ACK 階段(超時(shí)等待)。 A 向 B 發(fā)送 ACK 報(bào)文,雙方都斷開連接。
參考資料:
知乎網(wǎng)友 - 魔方的回答
HTTP 報(bào)文格式
HTTP 報(bào)文由請(qǐng)求行、首部、實(shí)體主體組成,它們之間由 CRLF(回車換行符) 分隔開。
注意:實(shí)體包括首部 (也稱為實(shí)體首部) 和實(shí)體主體,sp 即是空格 space。

請(qǐng)求行和首部是由 ASCII 文本組成的,實(shí)體主體是可選的,可以為空也可以是任意二進(jìn)制數(shù)據(jù)。
請(qǐng)求報(bào)文和響應(yīng)報(bào)文的格式基本相同。
請(qǐng)求報(bào)文格式:
??
復(fù)制代碼
響應(yīng)報(bào)文格式:
??
復(fù)制代碼
一個(gè)請(qǐng)求或響應(yīng)報(bào)文由以下字段組成:
請(qǐng)求方法,客戶端希望服務(wù)器對(duì)資源執(zhí)行的動(dòng)作。 請(qǐng)求 URL,命名了所請(qǐng)求的資源。 協(xié)議版本,報(bào)文所使用的 HTTP 版本。 狀態(tài)碼,這三位數(shù)字描述了請(qǐng)求過程中所發(fā)生的情況。 原因短語,數(shù)字狀態(tài)碼的可讀版本(例如上面的響應(yīng)示例跟在 200 后面的 OK,一般按規(guī)范寫最好)。 首部,可以有零或多個(gè)首部。 實(shí)體的主體部分,可以為空也可以包含任意二進(jìn)制數(shù)據(jù)。
一個(gè) HTTP 請(qǐng)求示例:
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
復(fù)制代碼
一個(gè) HTTP 響應(yīng)示例:
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
復(fù)制代碼
方法
| 方法 | 描述 |
|---|---|
| GET | 從服務(wù)器獲取一份文檔 |
| HEAD | 只從服務(wù)器獲取文檔的頭部 |
| POST | 向服務(wù)器發(fā)送需要處理的數(shù)據(jù) |
| PUT | 將請(qǐng)求的數(shù)據(jù)部分存儲(chǔ)在服務(wù)器上 |
| TRACE | 對(duì)可能經(jīng)過代理服務(wù)器傳送到服務(wù)器上去的報(bào)文進(jìn)行追蹤 |
| OPTIONS | 決定可以在服務(wù)器上執(zhí)行哪些方法 |
| DELETE | 從服務(wù)器上刪除一份文檔 |
GET 和 HEAD
其中 GET 和 HEAD 被稱為安全方法,因?yàn)樗鼈兪莾绲鹊模ㄈ绻粋€(gè)請(qǐng)求不管執(zhí)行多少次,其結(jié)果都是一樣的,這個(gè)請(qǐng)求就是冪等的),類似于 POST 就不是冪等的。
HEAD 方法和 GET 方法很類似,但服務(wù)器在響應(yīng)中只返回首部。這就允許客戶端在未獲取實(shí)際資源的情況下,對(duì)資源的首部進(jìn)行檢查。使用 HEAD,可以:
在不獲取資源的情況下了解資源的情況。 通過查看響應(yīng)狀態(tài)碼,看看某個(gè)對(duì)象是否存在。 通過查看首部,了解測(cè)試資源是否被修改了。
服務(wù)器開發(fā)者必須確保返回的首部與 GET 請(qǐng)求所返回的首部完全相同。遵循 HTTP/1.1 規(guī)范,就必須實(shí)現(xiàn) HEAD 方法。
PUT
與 GET 方法從服務(wù)器讀取文檔相反,PUT 方法會(huì)向服務(wù)器寫入文檔。PUT 方法的語義就是讓服務(wù)器用請(qǐng)求的主體部分來創(chuàng)建一個(gè)由所請(qǐng)求的 URL 命名的新文檔。如果那個(gè)文檔已存在,就覆蓋它。
POST
POST 方法通常用來向服務(wù)器發(fā)送表單數(shù)據(jù)。
TRACE
客戶端發(fā)起一個(gè)請(qǐng)求時(shí),這個(gè)請(qǐng)求可能要穿過路由器、防火墻、代理、網(wǎng)關(guān)等。每個(gè)中間節(jié)點(diǎn)都可能會(huì)修改原始的 HTTP 請(qǐng)求,TRACE 方法允許客戶端在最終發(fā)起請(qǐng)求時(shí),看看它變成了什么樣子。
TRACE 請(qǐng)求會(huì)在目的服務(wù)器端發(fā)起一個(gè) “環(huán)回” 診斷。行程最后一站的服務(wù)器會(huì)彈回一條 TRACE 響應(yīng),并在響應(yīng)主體中攜帶它收到的原始請(qǐng)求報(bào)文。這樣客戶端就可以查看在所有中間 HTTP 應(yīng)用程序組成的請(qǐng)求 / 響應(yīng)鏈上,原始報(bào)文是否被毀壞或修改過。

TRACE 方法主要用于診斷,用于驗(yàn)證請(qǐng)求是否如愿穿過了請(qǐng)求 / 響應(yīng)鏈。它也是一種工具,用來查看代理和其他應(yīng)用程序?qū)τ脩粽?qǐng)求所產(chǎn)生的效果。TRACE 請(qǐng)求中不能帶有實(shí)體的主體部分。TRACE 響應(yīng)的實(shí)體主體部分包含了響應(yīng)服務(wù)器收到的請(qǐng)求的精確副本。
OPTIONS
OPTIONS 方法請(qǐng)求 Web 服務(wù)器告知其支持的各種功能。
DELETE
DELETE 方法就是讓服務(wù)器刪除請(qǐng)求 URL 所指定的資源。
狀態(tài)碼
| 整體范圍 | 已定義范圍 | 分類 |
|---|---|---|
| 100~199 | 100~101 | 信息提示 |
| 200~299 | 200~206 | 成功 |
| 300~399 | 300~305 | 重定向 |
| 400~499 | 400~415 | 客戶端錯(cuò)誤 |
| 500~599 | 500~505 | 服務(wù)器錯(cuò)誤 |
300~399 重定向狀態(tài)碼
重定向狀態(tài)碼要么告訴客戶端使用替代位置來訪問他們感興趣的資源,要么提供一個(gè)替代的響應(yīng)而不是資源的內(nèi)容。如果資源已被移動(dòng),可以發(fā)送一個(gè)重定向狀態(tài)碼和一個(gè)可選的 Location 首部來告知客戶端資源已被移走,以及現(xiàn)在在哪里可以找到它。這樣,瀏覽器可以在不打擾使用者的情況下,透明地轉(zhuǎn)入新的位置。
400~499 客戶端錯(cuò)誤狀態(tài)碼
有時(shí)客戶端會(huì)發(fā)送一些服務(wù)器無法處理的東西,例如格式錯(cuò)誤的請(qǐng)求報(bào)文、一個(gè)不存在的 URL。
500~599 服務(wù)器錯(cuò)誤狀態(tài)碼
有時(shí)客戶端發(fā)送了一條有效請(qǐng)求,服務(wù)器自身卻出錯(cuò)了。
首部
首部和方法共同配合工作,決定了客戶端和服務(wù)器能做什么事情。
首部分類:
通用首部,可以出現(xiàn)在請(qǐng)求或響應(yīng)報(bào)文中。 請(qǐng)求首部,提供更多有關(guān)請(qǐng)求的信息。 響應(yīng)首部,提供更多有關(guān)響應(yīng)的信息。 實(shí)體首部,描述主體的長(zhǎng)度和內(nèi)容,或者資源自身。 擴(kuò)展首部,規(guī)范中沒有定義的新首部。
通用首部
有些首部提供了與報(bào)文相關(guān)的最基本信息,它們被稱為通用首部。以下是一些常見的通用首部:

請(qǐng)求首部
請(qǐng)求首部是只在請(qǐng)求報(bào)文中有意義的首部,用于說明請(qǐng)求的詳情。以下是一些常見的請(qǐng)求首部:

響應(yīng)首部
響應(yīng)首部讓服務(wù)器為客戶端提供了一些額外的信息。
實(shí)體首部
實(shí)體首部提供了有關(guān)實(shí)體及其內(nèi)容的大量信息,從有關(guān)對(duì)象類型的信息,到能夠?qū)Y源使用的各種有效的請(qǐng)求方法。
例如內(nèi)容首部,提供了與實(shí)體內(nèi)容有關(guān)的特定信息,說明了其類型、尺寸以及處理它所需的其他有用信息。另外,通用的緩存首部說明了如何或什么時(shí)候進(jìn)行緩存。實(shí)體的緩存首部提供了與被緩存實(shí)體有關(guān)的信息。

性能優(yōu)化
1. 減少 HTTP 請(qǐng)求
每發(fā)起一個(gè) HTTP 請(qǐng)求,都得經(jīng)歷三次握手建立 TCP 連接,如果連接只用來交換少量數(shù)據(jù),這個(gè)過程就會(huì)嚴(yán)重降低 HTTP 性能。所以我們可以將多個(gè)小文件合成一個(gè)大文件,從而減少 HTTP 請(qǐng)求次數(shù)。
其實(shí)由于持久連接(重用 TCP 連接,以消除連接及關(guān)閉時(shí)延;HTTP/1.1 默認(rèn)開啟持久連接)的存在,每個(gè)新請(qǐng)求不一定都需要建立一個(gè)新的 TCP 連接。但是,瀏覽器處理完一個(gè) HTTP 請(qǐng)求才能發(fā)起下一個(gè),所以在 TCP 連接數(shù)沒達(dá)到瀏覽器規(guī)定的上限時(shí),還是會(huì)建立新的 TCP 連接。從這點(diǎn)來看,減少 HTTP 請(qǐng)求仍然是有必要的。
2. 靜態(tài)資源使用 CDN
內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)是一組分布在多個(gè)不同地理位置的 Web 服務(wù)器。我們都知道,當(dāng)服務(wù)器離用戶越遠(yuǎn)時(shí),延遲越高。CDN 就是為了解決這一問題,在多個(gè)位置部署服務(wù)器,讓用戶離服務(wù)器更近,從而縮短請(qǐng)求時(shí)間。
3. 善用緩存
為了避免用戶每次訪問網(wǎng)站都得請(qǐng)求文件,我們可以通過添加 Expires 頭來控制這一行為。Expires 設(shè)置了一個(gè)時(shí)間,只要在這個(gè)時(shí)間之前,瀏覽器都不會(huì)請(qǐng)求文件,而是直接使用緩存。
不過這樣會(huì)產(chǎn)生一個(gè)問題,當(dāng)文件更新了怎么辦?怎么通知瀏覽器重新請(qǐng)求文件?
可以通過更新頁面中引用的資源鏈接地址,讓瀏覽器主動(dòng)放棄緩存,加載新資源。
具體做法是把資源地址 URL 的修改與文件內(nèi)容關(guān)聯(lián)起來,也就是說,只有文件內(nèi)容變化,才會(huì)導(dǎo)致相應(yīng) URL 的變更,從而實(shí)現(xiàn)文件級(jí)別的精確緩存控制。什么東西與文件內(nèi)容相關(guān)呢?我們會(huì)很自然的聯(lián)想到利用數(shù)據(jù)摘要要算法對(duì)文件求摘要信息,摘要信息與文件內(nèi)容一一對(duì)應(yīng),就有了一種可以精確到單個(gè)文件粒度的緩存控制依據(jù)了。
參考資料:
張?jiān)讫?-- 大公司里怎樣開發(fā)和部署前端代碼?
4. 壓縮文件
壓縮文件可以減少文件下載時(shí)間,讓用戶體驗(yàn)性更好。
gzip 是目前最流行和最有效的壓縮方法??梢酝ㄟ^向 HTTP 請(qǐng)求頭中的 Accept-Encoding 頭添加 gzip 標(biāo)識(shí)來開啟這一功能。當(dāng)然,服務(wù)器也得支持這一功能。
舉個(gè)例子,我用 Vue 開發(fā)的項(xiàng)目構(gòu)建后生成的 app.js 文件大小為 1.4MB,使用 gzip 壓縮后只有 573KB,體積減少了將近 60%。
5. 通過 max-age 和 no-cache 實(shí)現(xiàn)文件精確緩存
通用消息頭部 Cache-Control 其中有兩個(gè)選項(xiàng):
max-age: 設(shè)置緩存存儲(chǔ)的最大周期,超過這個(gè)時(shí)間緩存被認(rèn)為過期 (單位秒)。在這個(gè)時(shí)間前,瀏覽器讀取文件不會(huì)發(fā)出新請(qǐng)求,而是直接使用緩存。no-cache: 指定 no-cache 表示客戶端可以緩存資源,每次使用緩存資源前都必須重新驗(yàn)證其有效性。
我們可以將那些長(zhǎng)期不變的靜態(tài)資源設(shè)置一個(gè)非常長(zhǎng)的緩存時(shí)間,例如設(shè)置成緩存一年。
然后將 index.html 文件設(shè)置成 no-cache。這樣每次訪問網(wǎng)站時(shí),瀏覽器都會(huì)詢問 index.html 是否有更新,如果沒有,就使用舊的 index.html 文件。如果有更新,就讀取新的 index.html 文件。當(dāng)加載新的 index.html 時(shí),也會(huì)去加載里面新的 URL 資源。
例如 index.html 原來引用了 a.js 和 b.js,現(xiàn)在更新了變成 a.js 和 c.js。那就只會(huì)加載 c.js 文件。
具體請(qǐng)看 webpack + express 實(shí)現(xiàn)文件精確緩存。
HTTPS
HTTPS 是最流行的 HTTP 安全形式,由網(wǎng)景公司首創(chuàng),所有主要的瀏覽器和服務(wù)器都支持此協(xié)議。使用 HTTPS 時(shí),所有的 HTTP 請(qǐng)求和響應(yīng)數(shù)據(jù)在發(fā)送之前,都要進(jìn)行加密。加密可以使用 SSL 或 TLS。

SSL/TLS 協(xié)議作用在 HTTP 協(xié)議之下,對(duì)于上層應(yīng)用來說,原來的發(fā)送 / 接收數(shù)據(jù)流程不變,這就很好地兼容了老的 HTTP 協(xié)議。由于 SSL/TLS 差別不大,下面統(tǒng)一使用 SSL。
要想了解 HTTPS 為何安全,還得繼續(xù)了解一下這些概念:加密算法、摘要算法、數(shù)字簽名和數(shù)字證書。
加密算法
對(duì)稱密鑰密碼體制
對(duì)稱密鑰密碼體制,即加密密鑰和解密密鑰是使用相同的密碼體制。對(duì)稱密鑰加密技術(shù)的缺點(diǎn)之一就是發(fā)送者和接收者在對(duì)話之前,一定要有一個(gè)共享的密鑰,所以不太安全。
公鑰密碼體制
公鑰密碼體制使用不同的加密密鑰與解密密鑰。公鑰密碼體制產(chǎn)生的主要原因有兩個(gè):一是對(duì)稱密鑰密碼體制的密鑰分配問題,二是對(duì)數(shù)字簽名的需求。
在公鑰密碼體制中,加密密鑰是公開的,解密密鑰是需要保密的,加密算法和解密算法也是公開的。
公鑰密碼體制的加密和解密有如下特點(diǎn):
密鑰對(duì)產(chǎn)生器產(chǎn)生出接收者 B 的一對(duì)密鑰,即加密密鑰 PK 和解密密鑰 SK。 發(fā)送者 A 用 B 的公鑰 PK 作為加密密鑰來加密信息,B 接收后用解密密鑰 SK 解密。

使用對(duì)稱密鑰時(shí),由于雙方使用同樣的密鑰,因此在通信信道上可以進(jìn)行一對(duì)一的雙向保密通信,雙方都可以用同一個(gè)密鑰加密解密。
使用公開密鑰時(shí),在通信信道上可以是多對(duì)一的單向保密信道。即可以有多人持有 B 的公鑰,但只有 B 才能解密。
摘要算法
摘要算法的主要特征是加密過程不需要密鑰,并且經(jīng)過加密的數(shù)據(jù)無法被解密,目前可以被解密逆向的只有 CRC32 算法,只有輸入相同的明文數(shù)據(jù)經(jīng)過相同的消息摘要算法才能得到相同的密文。
數(shù)字簽名
用加密系統(tǒng)對(duì)報(bào)文進(jìn)行簽名,以說明是誰編寫的報(bào)文,同時(shí)證明報(bào)文未被篡改過,這種技術(shù)稱為數(shù)字簽名。
數(shù)字簽名是附加在報(bào)文上的特殊加密校驗(yàn)碼。使用數(shù)字簽名的好處有:
簽名可以證明是作者編寫了這條報(bào)文。只有作者才會(huì)有最機(jī)密的私有密鑰,因此,只有作者才能計(jì)算出這些校驗(yàn)和。 簽名可以防止報(bào)文被篡改,如果有人在報(bào)文傳輸過程中對(duì)其進(jìn)行了修改,校驗(yàn)和就不再匹配了。
數(shù)字簽名通常是用非對(duì)稱公開密鑰技術(shù)產(chǎn)生的。

看上圖,任何人都能用 A 的公鑰 PK 對(duì)密文進(jìn)行 E 運(yùn)算后得到 A 發(fā)送的明文??梢娺@種通信并非為了保密,而是為了進(jìn)行簽名和核實(shí)簽名,即確認(rèn)此信息是 A 發(fā)送的(使用 A 的密鑰進(jìn)行加密的報(bào)文,只有使用 A 的公鑰才能正確解密)。但上述過程僅對(duì)報(bào)文進(jìn)行了簽名,對(duì)報(bào)文 X 本身卻未保密,所以要采用下圖的方法,同時(shí)實(shí)現(xiàn)秘密通信和數(shù)字簽名。

數(shù)字證書
假如你想訪問一個(gè)網(wǎng)站,怎么確保對(duì)方給你的公鑰是你想訪問的網(wǎng)站的公鑰,而不是被中間人篡改過的?
數(shù)字證書的出現(xiàn)就是為了解決這個(gè)問題,它是由數(shù)字證書認(rèn)證機(jī)構(gòu)頒發(fā)的,用來證明公鑰擁有者的身份。換句話說,數(shù)字證書的作用就相當(dāng)于人的身份證,身份證證明了張三就是張三,而不是別人。
數(shù)字證書一般包含以下內(nèi)容:
對(duì)象的名稱(人、服務(wù)器、組織等); 過期時(shí)間; 證書發(fā)布者(由誰為證書擔(dān)保); 來自證書發(fā)布者的數(shù)字簽名; 對(duì)象的公鑰; 對(duì)象和所用簽名算法的描述性信息。
任何人都可以創(chuàng)建一個(gè)數(shù)字證書,但由誰來擔(dān)保才是重點(diǎn)。
數(shù)字證書的數(shù)字簽名計(jì)算過程:
用摘要算法對(duì)數(shù)字證書的內(nèi)容計(jì)算出摘要; 用數(shù)字證書的私鑰對(duì)摘要進(jìn)行加密得到數(shù)字簽名。

當(dāng)瀏覽器收到證書時(shí),會(huì)對(duì)簽名頒發(fā)機(jī)構(gòu)進(jìn)行驗(yàn)證,如果頒發(fā)機(jī)構(gòu)是個(gè)很有權(quán)威的公共簽名機(jī)構(gòu),瀏覽器可能就知道其公開密鑰了(瀏覽器會(huì)預(yù)裝很多簽名頒發(fā)機(jī)構(gòu)的證書)。如果對(duì)簽名頒發(fā)機(jī)構(gòu)一無所知,瀏覽器通常會(huì)向用戶顯示一個(gè)對(duì)話框,看看他是否相信這個(gè)簽名發(fā)布者。
因?yàn)閿?shù)字證書的公鑰是公開的,任何人都可以用公鑰解密出數(shù)字證書的數(shù)字簽名的摘要,然后再用同樣的摘要算法對(duì)證書內(nèi)容進(jìn)行摘要計(jì)算,將得出的摘要和解密后的摘要作對(duì)比,如果內(nèi)容一致則說明這個(gè)證書沒有被篡改過,可以信任。
這個(gè)過程是建立在被大家所認(rèn)可的證書機(jī)構(gòu)之上得到的公鑰,所以這是一種安全的方式。

HTTPS 連接建立過程
HTTPS 連接建立過程和 HTTP 差不多,區(qū)別在于 HTTP(默認(rèn)端口 80) 請(qǐng)求只要在 TCP 連接建立后就可以發(fā)起,而 HTTPS(默認(rèn)端口 443) 在 TCP 連接建立后,還需要經(jīng)歷 SSL 協(xié)議握手,成功后才能發(fā)起請(qǐng)求。


我知道肯定會(huì)有人不滿足于簡(jiǎn)化版的 SSL 握手過程,所以我找了一篇文章 SSL/TLS 握手過程詳解,這篇文章非常詳細(xì)的講解了 SSL 握手的每一步驟。建議有興趣的同學(xué)看一看。
HTTP/2
HTTP/2 是 HTTP/1.x 的擴(kuò)展,而非替代。所以 HTTP 的語義不變,提供的功能不變,HTTP 方法、狀態(tài)碼、URL 和首部字段等這些核心概念也不變。
之所以要遞增一個(gè)大版本到 2.0,主要是因?yàn)樗淖兞丝蛻舳伺c服務(wù)器之間交換數(shù)據(jù)的方式。HTTP 2.0 增加了新的二進(jìn)制分幀數(shù)據(jù)層,而這一層并不兼容之前的 HTTP 1.x 服務(wù)器及客戶端——是謂 2.0。
HTTP/2 連接建立過程
現(xiàn)在的主流瀏覽器 HTTP/2 的實(shí)現(xiàn)都是基于 SSL/TLS 的,也就是說使用 HTTP/2 的網(wǎng)站都是 HTTPS 協(xié)議的,所以本文只討論基于 SSL/TLS 的 HTTP/2 連接建立過程。
基于 SSL/TLS 的 HTTP/2 連接建立過程和 HTTPS 差不多。在 SSL/TLS 握手協(xié)商過程中,客戶端在 ClientHello 消息中設(shè)置 ALPN(應(yīng)用層協(xié)議協(xié)商)擴(kuò)展來表明期望使用 HTTP/2 協(xié)議,服務(wù)器用同樣的方式回復(fù)。通過這種方式,HTTP/2 在 SSL/TLS 握手協(xié)商過程中就建立起來了。
HTTP/1.1 的問題
1. 隊(duì)頭阻塞
在 HTTP 請(qǐng)求應(yīng)答過程中,如果出現(xiàn)了某種情況,導(dǎo)致響應(yīng)一直未能完成,那后面所有的請(qǐng)求就會(huì)一直阻塞著,這種情況叫隊(duì)頭阻塞。
2. 低效的 TCP 利用
由于 TCP 慢啟動(dòng)機(jī)制,導(dǎo)致每個(gè) TCP 連接在一開始的時(shí)候傳輸速率都不高,在處理多個(gè)請(qǐng)求后,才會(huì)慢慢達(dá)到 “合適” 的速率。對(duì)于請(qǐng)求數(shù)據(jù)量很小的 HTTP 請(qǐng)求來說,這種情況就是種災(zāi)難。
3. 臃腫的消息首部
HTTP/1.1 的首部無法壓縮,再加上 cookie 的存在,經(jīng)常會(huì)出現(xiàn)首部大小比請(qǐng)求數(shù)據(jù)大小還大的情況。
4. 受限的優(yōu)先級(jí)設(shè)置
HTTP/1.1 無法為重要的資源指定優(yōu)先級(jí),每個(gè) HTTP 請(qǐng)求都是一視同仁。
在繼續(xù)討論 HTTP/2 的新功能之前,先把 HTTP/1.1 的問題列出來是有意義的。因?yàn)?HTTP/2 的某些新功能就是為了解決上述某些問題而產(chǎn)生的。
二進(jìn)制分幀層
HTTP/2 是基于幀的協(xié)議。采用分幀是為了將重要信息封裝起來,讓協(xié)議的解析方可以輕松閱讀、解析并還原信息。
而 HTTP/1.1 是以文本分隔的。解析 HTTP/1.1 不需要什么高科技,但往往速度慢且容易出錯(cuò)。你需要不斷地讀入字節(jié),直到遇到分隔符 CRLF 為止,同時(shí)還要考慮不守規(guī)矩的客戶端,它只會(huì)發(fā)送 LF。
解析 HTTP/1.1 的請(qǐng)求或響應(yīng)還會(huì)遇到以下問題:
一次只能處理一個(gè)請(qǐng)求或響應(yīng),完成之前不能停止解析。 無法預(yù)判解析需要多少內(nèi)存。
HTTP/2 有了幀,處理協(xié)議的程序就能預(yù)先知道會(huì)收到什么,并且 HTTP/2 有表示幀長(zhǎng)度的字段。

幀結(jié)構(gòu)
?+-----------------------------------------------+
?|?????????????????Length?(24)???????????????????|
?+---------------+---------------+---------------+
?|???Type?(8)????|???Flags?(8)???|
?+-+-------------+---------------+-------------------------------+
?|R|?????????????????Stream?Identifier?(31)??????????????????????|
?+=+=============================================================+
?|???????????????????Frame?Payload?(0...)??????????????????????...
?+---------------------------------------------------------------+
復(fù)制代碼
| 名稱 | 長(zhǎng)度 | 描述 |
|---|---|---|
| Length | 3 字節(jié) | 表示幀負(fù)載的長(zhǎng)度,取值范圍為 (2 的 14 次方)至 (2 的 24 次方 - 1)。(2 的 14 次方) 16384 字節(jié)是默認(rèn)的最大幀大小,如果需要更大的幀,必須在 SETTINGS 幀中設(shè)置 |
| Type | 1 字節(jié) | 當(dāng)前幀類型(見下表) |
| Flags | 1 字節(jié) | 具體幀類型的標(biāo)識(shí) |
| R | 1 位 | 保留位,不要設(shè)置,否則可能會(huì)帶來嚴(yán)重的后果 |
| Stream Identifier | 31 位 | 每個(gè)流的唯一 ID |
| Frame Payload | 長(zhǎng)度可變 | 真實(shí)的幀內(nèi)容,長(zhǎng)度是在 Length 字段中設(shè)置的 |
由于 HTTP/2 是分幀的,請(qǐng)求和響應(yīng)都可以多路復(fù)用,有助于解決類似類似隊(duì)頭阻塞的問題。
幀類型
| 名稱 | ID | 描述 |
|---|---|---|
| DATA | 0x0 | 傳輸流的核心內(nèi)容 |
| HEADERS | 0x1 | 包含 HTTP 首部,和可選的優(yōu)先級(jí)參數(shù) |
| PRIORITY | 0x2 | 指示或更改流的優(yōu)先級(jí)和依賴 |
| RST_STREAM | 0x3 | 允許一端停止流(通常由于錯(cuò)誤導(dǎo)致的) |
| SETTINGS | 0x4 | 協(xié)商連接級(jí)參數(shù) |
| PUSH_PROMISE | 0x5 | 提示客戶端,服務(wù)器要推送些東西 |
| PING | 0x6 | 測(cè)試連接可用性和往返時(shí)延(RTT) |
| GOAWAY | 0x7 | 告訴另一端,當(dāng)前的端已結(jié)束 |
| WINDOW_UPDATE | 0x8 | 協(xié)商一端將要接收多少字節(jié)(用于流量控制) |
| CONTINUATION | 0x9 | 用以擴(kuò)展 HEADERS 模塊 |
多路復(fù)用
在 HTTP/1.1 中,如果客戶端想發(fā)送多個(gè)并行的請(qǐng)求,那么必須使用多個(gè) TCP 連接。
而 HTTP/2 的二進(jìn)制分幀層突破了這一限制,所有的請(qǐng)求和響應(yīng)都在同一個(gè) TCP 連接上發(fā)送:客戶端和服務(wù)器把 HTTP 消息分解成多個(gè)幀,然后亂序發(fā)送,最后在另一端再根據(jù)流 ID 重新組合起來。
這個(gè)機(jī)制為 HTTP 帶來了巨大的性能提升,因?yàn)椋?/p>
可以并行交錯(cuò)地發(fā)送請(qǐng)求,請(qǐng)求之間互不影響; 可以并行交錯(cuò)地發(fā)送響應(yīng),響應(yīng)之間互不干擾; 只使用一個(gè)連接即可并行發(fā)送多個(gè)請(qǐng)求和響應(yīng); 消除不必要的延遲,從而減少頁面加載的時(shí)間; 不必再為繞過 HTTP 1.x 限制而多做很多工作;

流
HTTP/2 規(guī)范對(duì)流的定義是:HTTP/2 連接上獨(dú)立的、雙向的幀序列交換。如果客戶端想要發(fā)出請(qǐng)求,它會(huì)開啟一個(gè)新流,然后服務(wù)器在這個(gè)流上回復(fù)。由于有分幀,所以多個(gè)請(qǐng)求和響應(yīng)可以交錯(cuò),而不會(huì)互相阻塞。流 ID 用來標(biāo)識(shí)幀所屬的流。
客戶端到服務(wù)器的 HTTP/2 連接建立后,通過發(fā)送 HEADERS 幀來啟動(dòng)新的流。如果首部需要跨多個(gè)幀,可能還會(huì)發(fā)送 CONTINUATION 幀。該 HEADERS 幀可能來自請(qǐng)求或響應(yīng)。后續(xù)流啟動(dòng)的時(shí)候,會(huì)發(fā)送一個(gè)帶有遞增流 ID 的新 HEADERS 幀。
消息
HTTP 消息泛指 HTTP 請(qǐng)求或響應(yīng),消息由一或多個(gè)幀組成,這些幀可以亂序發(fā)送,然后再根據(jù)每個(gè)幀首部的流 ID 重新組裝。
一個(gè)消息至少由 HEADERS 幀(它初始化流)組成,并且可以另外包含 CONTINUATION 和 DATA 幀,以及其他的 HEADERS 幀。

HTTP/1.1 的請(qǐng)求和響應(yīng)部分都分成消息首部和消息體兩部分;HTTP/2 的請(qǐng)求和響應(yīng)分成 HEADERS 幀和 DATA 幀。
優(yōu)先級(jí)
把 HTTP 消息分解為很多獨(dú)立的幀之后,就可以通過優(yōu)化這些幀的交錯(cuò)和傳輸順序,進(jìn)一步提升性能。
通過 HEADERS 幀和 PRIORITY 幀,客戶端可以明確地和服務(wù)器溝通它需要什么,以及它需要這些資源的順序。具體來講,服務(wù)器可以根據(jù)流的優(yōu)先級(jí),控制資源分配(CPU、內(nèi)存、帶寬),而在響應(yīng)數(shù)據(jù)準(zhǔn)備好之后,優(yōu)先將最高優(yōu)先級(jí)的幀發(fā)送給客戶端。
流量控制
在同一個(gè) TCP 連接上傳輸多個(gè)數(shù)據(jù)流,就意味著要共享帶寬。標(biāo)定數(shù)據(jù)流的優(yōu)先級(jí)有助于按序交付,但只有優(yōu)先級(jí)還不足以確定多個(gè)數(shù)據(jù)流或多個(gè)連接間的資源分配。
為解決這個(gè)問題,HTTP/2 為數(shù)據(jù)流和連接的流量控制提供了一個(gè)簡(jiǎn)單的機(jī)制:
流量控制基于每一跳進(jìn)行,而非端到端的控制; 流量控制基于 WINDOW_UPDATE 幀進(jìn)行,即接收方廣播自己準(zhǔn)備接收某個(gè)數(shù)據(jù)流的多少字節(jié),以及對(duì)整個(gè)連接要接收多少字節(jié); 流量控制窗口大小通過 WINDOW_UPDATE 幀更新,這個(gè)字段指定了流 ID 和窗口大小遞增值; 流量控制有方向性,即接收方可能根據(jù)自己的情況為每個(gè)流乃至整個(gè)連接設(shè)置任意窗口大?。?/section> 流量控制可以由接收方禁用,包括針對(duì)個(gè)別的流和針對(duì)整個(gè)連接。
HTTP/2 連接建立之后,客戶端與服務(wù)器交換 SETTINGS 幀,目的是設(shè)置雙向的流量控制窗口大小。除此之外,任何一端都可以選擇禁用個(gè)別流或整個(gè)連接的流量控制。
服務(wù)器推送
HTTP/2 新增的一個(gè)強(qiáng)大的新功能,就是服務(wù)器可以對(duì)一個(gè)客戶端請(qǐng)求發(fā)送多個(gè)響應(yīng)。換句話說,除了對(duì)最初請(qǐng)求的響應(yīng)外,服務(wù)器還可以額外向客戶端推送資源,而無需客戶端明確地請(qǐng)求。

為什么需要這樣一個(gè)機(jī)制呢?通常的 Web 應(yīng)用都由幾十個(gè)資源組成,客戶端需要分析服務(wù)器提供的文檔才能逐個(gè)找到它們。那為什么不讓服務(wù)器提前就把這些資源推送給客戶端,從而減少額外的時(shí)間延遲呢?服務(wù)器已經(jīng)知道客戶端下一步要請(qǐng)求什么資源了,這時(shí)候服務(wù)器推送即可派上用場(chǎng)。
另外,客戶端也可以拒絕服務(wù)器的推送。
首部壓縮
HTTP/1.1 存在的一個(gè)問題就是臃腫的首部,HTTP/2 對(duì)這一問題進(jìn)行了改進(jìn),可以對(duì)首部進(jìn)行壓縮。在一個(gè) Web 頁面中,一般都會(huì)包含大量的請(qǐng)求,而其中有很多請(qǐng)求的首部往往有很多重復(fù)的部分。
例如有如下兩個(gè)請(qǐng)求:
:authority:?unpkg.zhimg.com
:method:?GET
:path:?/[email protected]/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
復(fù)制代碼
: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
復(fù)制代碼
從上面兩個(gè)請(qǐng)求可以看出來,有很多數(shù)據(jù)都是重復(fù)的。如果可以把相同的首部存儲(chǔ)起來,僅發(fā)送它們之間不同的部分,就可以節(jié)省不少的流量,加快請(qǐng)求的時(shí)間。
HTTP/2 在客戶端和服務(wù)器端使用 “首部表” 來跟蹤和存儲(chǔ)之前發(fā)送的鍵-值對(duì),對(duì)于相同的數(shù)據(jù),不再通過每次請(qǐng)求和響應(yīng)發(fā)送。
下面再來看一個(gè)簡(jiǎn)化的例子,假設(shè)客戶端按順序發(fā)送如下請(qǐng)求首部:
Header1:foo
Header2:bar
Header3:bat
復(fù)制代碼
當(dāng)客戶端發(fā)送請(qǐng)求時(shí),它會(huì)根據(jù)首部值創(chuàng)建一張表:
| 索引 | 首部名稱 | 值 |
|---|---|---|
| 62 | Header1 | foo |
| 63 | Header2 | bar |
| 64 | Header3 | bat |
如果服務(wù)器收到了請(qǐng)求,它會(huì)照樣創(chuàng)建一張表。當(dāng)客戶端發(fā)送下一個(gè)請(qǐng)求的時(shí)候,如果首部相同,它可以直接發(fā)送這樣的首部塊:
62?63?64
復(fù)制代碼
服務(wù)器會(huì)查找先前建立的表格,并把這些數(shù)字還原成索引對(duì)應(yīng)的完整首部。
性能優(yōu)化
使用 HTTP/2 代替 HTTP/1.1,本身就是一種巨大的性能提升。這小節(jié)要聊的是在 HTTP/1.1 中的某些優(yōu)化手段,在 HTTP/2 中是不必要的,可以取消的。
取消合并資源
在 HTTP/1.1 中要把多個(gè)小資源合并成一個(gè)大資源,從而減少請(qǐng)求。而在 HTTP/2 就不需要了,因?yàn)?HTTP/2 所有的請(qǐng)求都可以在一個(gè) TCP 連接發(fā)送。
取消域名拆分
取消域名拆分的理由同上,再多的 HTTP 請(qǐng)求都可以在一個(gè) TCP 連接上發(fā)送,所以不需要采取多個(gè)域名來突破瀏覽器 TCP 連接數(shù)限制這一規(guī)則了。
參考資料
HTTP 權(quán)威指南 HTTP/2 基礎(chǔ)教程 SSL/TLS 握手過程詳解 互聯(lián)網(wǎng)安全之?dāng)?shù)字簽名、數(shù)字證書與 PKI 系統(tǒng) 計(jì)算機(jī)網(wǎng)絡(luò)(第 7 版) Web 性能權(quán)威指南
