6 分鐘了解 HTTP 發(fā)展史
HTTP/0.9HTTP/0.9 是于 1991 年提出的,主要用于學(xué)術(shù)交流,需求很簡單——用來在網(wǎng)絡(luò)之間傳遞 HTML 超文本的內(nèi)容,所以被稱為超文本傳輸協(xié)議。整體來看,它的實(shí)現(xiàn)也很簡單,采用了基于請求響應(yīng)的模式,從客戶端發(fā)出請求,服務(wù)器返回?cái)?shù)據(jù)。
完整請求流程
- 因?yàn)?HTTP 都是基于 TCP 協(xié)議的,所以客戶端先要根據(jù) IP 地址、端口和服務(wù)器建立 TCP 連接,而建立連接的過程就是 TCP 協(xié)議三次握手的過程。
- 建立好連接之后,會發(fā)送一個(gè) GET 請求行的信息,如GET /index.html 用來獲取 index.html。
- 服務(wù)器接收請求信息之后,讀取對應(yīng)的 HTML 文件,并將數(shù)據(jù)以 ASCII 字符流返回給客戶端。
- HTML 文檔傳輸完成后,斷開連接。
HTTP/0.9 請求過程
特點(diǎn)
- 第一個(gè)是只有一個(gè)請求行,并沒有 HTTP 請求頭和請求體,因?yàn)橹恍枰粋€(gè)請求行就可以完整表達(dá)客戶端的需求了。
- 第二個(gè)是服務(wù)器也沒有返回頭信息,這是因?yàn)榉?wù)器端并不需要告訴客戶端太多信息,只需要返回?cái)?shù)據(jù)就可以了。
- 第三個(gè)是返回的文件內(nèi)容是以 ASCII 字符流來傳輸?shù)模驗(yàn)槎际?HTML 格式的文件,所以使用 ASCII 字節(jié)碼來傳輸是最合適的。
HTTP/1.0
HTTP/0.9 存在許多的問題,比如如下的這些:
- 只支持 HTML 類型文件,無法傳輸 JS、CSS、字體、圖片和視頻等類型的文件;
- 文件傳輸格式局限于 ASCII,無法輸出其他類型編碼的文件;
- 只有請求行,傳輸給服務(wù)器的信息太少;
- 只響應(yīng)請求數(shù)據(jù),不能傳輸額外的數(shù)據(jù)給瀏覽器。
所以它已經(jīng)不能滿足當(dāng)時(shí)的需求了,于是乎 HTTP/1.0 來了,它帶來了這些:
- 新增了請求頭和請求體,能傳輸更多的信息給服務(wù)器,比如如下請求頭字段:Accept 文件類型,Accept-Encoding 壓縮格式,Accept-Charset 字符編碼格式,Accept-Language 國際化語音:
- Accept: text/html
- Accept-Encoding: gzip, deflate, br
- Accept-Charset: ISO-8859-1,utf-8
- Accept-Language: zh-CN,zh
- 請求頭新增 User-Agent 字段,用于服務(wù)器統(tǒng)計(jì)客戶端信息:
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
- 新增了響應(yīng)頭,能夠告訴瀏覽器更多的信息,比如 Content-Encoding 表示服務(wù)器返回文件的壓縮類型,Content-Type 告訴瀏覽器服務(wù)器返回的是什么類型的文件以及使用了什么編碼格式:
- Content-Encoding: gzip
- Content-Type: text/html; charset=utf-8
- 新增響應(yīng)行狀態(tài)碼,用于告知瀏覽器當(dāng)前請求的狀態(tài),比如 200 表示請求成功:
- HTTP/1.1 200 OK
- 新增緩存機(jī)制,用來緩存已經(jīng)下載過的資源,減輕了服務(wù)端壓力。
在構(gòu)建請求流程上來看,HTTP/1.0 區(qū)別于 HTTP/0.9 最大的區(qū)別就是在請求和響應(yīng)的時(shí)候新增了不少字段用于在瀏覽器和服務(wù)器之間通信。
HTTP/1.1
HTTP/1.0 雖說已經(jīng)能夠傳輸不同類型的文件了,但是它還是有缺點(diǎn)的,比如每發(fā)出一次 HTTP 請求都需要經(jīng)歷如下階段:
- 建立 TCP 連接;
- HTTP 請求;
- HTTP 響應(yīng);
- 斷開 TCP 連接。
HTTP/1.0 發(fā)送多個(gè)同域名請求:
可以發(fā)現(xiàn)每次請求都需要重新建立 TCP 連接和斷開連接的操作,這無疑增加了網(wǎng)絡(luò)開銷,同時(shí)也延遲了頁面顯示。
HTTP/1.1 在請求頭中增加了 Connection 字段:用于提供 TCP 的持久連接**:
- Connection: keep-alive
它默認(rèn)是開啟持久連接的,即對于同一個(gè)域名,瀏覽器默認(rèn)支持 6 個(gè) TCP 持久連接。當(dāng)啟用持久連接后,多個(gè)同域名下的請求發(fā)送會是如下情況:
HTTP/1.1 中新增 Host 字段,用于支持虛擬主機(jī)
- Host: bubuzou.com
- “虛擬主機(jī):一臺物理機(jī)器上綁定多個(gè)虛擬主機(jī),每個(gè)虛擬主機(jī)有單獨(dú)的域名,這些域名都公用一個(gè) IP 地址。
HTTP/1.1 通過引入 Chunk transfer 機(jī)制來支持動態(tài)內(nèi)容:服務(wù)器會將數(shù)據(jù)分割成若干個(gè)任意大小的數(shù)據(jù)塊,每個(gè)數(shù)據(jù)塊發(fā)送時(shí)會附上上個(gè)數(shù)據(jù)塊的長度,最后使用一個(gè)零長度的塊作為發(fā)送數(shù)據(jù)完成的標(biāo)志。
HTTP/1.1 還引入了客戶端 Cookie 機(jī)制和安全機(jī)制
HTTP/2
我們知道 HTTP/1.1 為網(wǎng)絡(luò)效率做了大量的優(yōu)化,最核心的有如下三種方式:
- 增加了持久連接;
- 瀏覽器為每個(gè)域名最多同時(shí)維護(hù) 6 個(gè) TCP 持久連接;
- 使用 CDN 的實(shí)現(xiàn)域名分片機(jī)制。
HTTP/1.1 中依然存在的問題
雖然 HTTP/1.1 采取了很多優(yōu)化資源加載速度的策略,也取得了一定的效果,但是 HTTP/1.1對帶寬的利用率卻并不理想,這也是 HTTP/1.1 的一個(gè)核心問題。
帶寬是指每秒最大能發(fā)送或者接收的字節(jié)數(shù)。我們把每秒能發(fā)送的最大字節(jié)數(shù)稱為上行帶寬,每秒能夠接收的最大字節(jié)數(shù)稱為下行帶寬。
之所以說 HTTP/1.1 對帶寬的利用率不理想,是因?yàn)?HTTP/1.1 很難將帶寬用滿。比如我們常說的 100M 帶寬,實(shí)際的下載速度能達(dá)到 12.5M/S,而采用 HTTP/1.1 時(shí),也許在加載頁面資源時(shí)最大只能使用到 2.5M/S,很難將 12.5M 全部用滿。
之所以會出現(xiàn)這個(gè)問題,主要是 3 個(gè)問題導(dǎo)致的:
第一個(gè)原因,TCP 的慢啟動
一旦一個(gè) TCP 連接建立之后,就進(jìn)入了發(fā)送數(shù)據(jù)狀態(tài),剛開始 TCP 協(xié)議會采用一個(gè)非常慢的速度去發(fā)送數(shù)據(jù),然后慢慢加快發(fā)送數(shù)據(jù)的速度,直到發(fā)送數(shù)據(jù)的速度達(dá)到一個(gè)理想狀態(tài),我們把這個(gè)過程稱為慢啟動。這個(gè)過程可以想象是一輛車的啟動過程,開始的時(shí)候慢,當(dāng)速度起來后加速就更快了。
而之所以說慢啟動會帶來性能問題,是因?yàn)轫撁嬷谐S玫囊恍╆P(guān)鍵資源文件本來就不大,如 HTML 文件、CSS 文件和 JavaScript 文件,通常這些文件在 TCP 連接建立好之后就要發(fā)起請求的,但這個(gè)過程是慢啟動,所以耗費(fèi)的時(shí)間比正常的時(shí)間要多很多,這樣就推遲了寶貴的首次渲染頁面的時(shí)長了。
第二個(gè)原因,同時(shí)開啟了多條 TCP 連接,那么這些連接會競爭固定的帶寬
你可以想象一下,系統(tǒng)同時(shí)建立了多條 TCP 連接,當(dāng)帶寬充足時(shí),每條連接發(fā)送或者接收速度會慢慢向上增加;而一旦帶寬不足時(shí),這些 TCP 連接又會減慢發(fā)送或者接收的速度。
這樣就會出現(xiàn)一個(gè)問題,因?yàn)橛械?TCP 連接下載的是一些關(guān)鍵資源,如 CSS 文件、JavaScript 文件等,而有的 TCP 連接下載的是圖片、視頻等普通的資源文件,但是多條 TCP 連接之間又不能協(xié)商讓哪些關(guān)鍵資源優(yōu)先下載,這樣就有可能影響那些關(guān)鍵資源的下載速度了。
第三個(gè)原因,HTTP/1.1 隊(duì)頭阻塞的問題
我們知道在 HTTP/1.1 中使用持久連接時(shí),雖然能公用一個(gè) TCP 管道,但是在一個(gè)管道中同一時(shí)刻只能處理一個(gè)請求,在當(dāng)前的請求沒有結(jié)束之前,其他的請求只能處于阻塞狀態(tài)。這意味著我們不能隨意在一個(gè)管道中發(fā)送請求和接收內(nèi)容。
這是一個(gè)很嚴(yán)重的問題,因?yàn)樽枞埱蟮囊蛩赜泻芏啵⑶叶际且恍┎淮_定性的因素,假如有的請求被阻塞了 5 秒,那么后續(xù)排隊(duì)的請求都要延遲等待 5 秒,在這個(gè)等待的過程中,帶寬、CPU 都被白白浪費(fèi)了。
HTTP/2 的多路復(fù)用
為了解決 HTTP/1.1 中存在的問題,在 HTTP/2 中采用最具顛覆性的方案:多路復(fù)用機(jī)制。

HTTP/2 多路復(fù)用是什么
HTTP/2 的多路復(fù)用機(jī)制用簡單的話來說就是瀏覽器針對同一域名的資源,只建立一個(gè) TCP 連接通道,所有的針對這個(gè)域名的請求全部都在這個(gè)通道中完成;
除此之外,數(shù)據(jù)的傳輸不再使用文本格式,而是會將它們分割為更小的流和幀,并對他們采用二進(jìn)制格式的編碼。在一個(gè) TCP 連接通道中,支持任意數(shù)量的雙向數(shù)據(jù)流,這些數(shù)據(jù)流是并行、亂序的且它們之間互不干擾。而數(shù)據(jù)流中傳輸?shù)臄?shù)據(jù)是二進(jìn)制幀,它是 HTTP/2 中數(shù)據(jù)傳輸?shù)淖钚挝唬粋€(gè)流中的幀是按照順序傳輸?shù)模沂遣⑿械模詿o需按順序等待。
解決了什么問題
因?yàn)橹皇褂靡粋€(gè) TCP 連接,所以減少了由于 TCP 慢啟動而消耗的時(shí)間,另外也由于只有單條 TCP 連接,所以不存在不同的 TCP 爭奪網(wǎng)絡(luò)帶寬的問題。
客戶端發(fā)送的請求經(jīng)過二進(jìn)制分幀層后,不再是一個(gè)個(gè)完整的 HTTP 請求報(bào)文,而是一堆亂序的幀(即不同流的幀是亂的,但是同一條流的幀數(shù)順序傳輸?shù)?,所以就不會按順序傳輸,也就不存在等待,從而解決了 HTTP 對頭阻塞問題。
是如何實(shí)現(xiàn)的
- 首先,瀏覽器準(zhǔn)備好請求數(shù)據(jù),包括了請求行、請求頭等信息,如果是 POST 方法,那么還要有請求體。
- 這些數(shù)據(jù)經(jīng)過二進(jìn)制分幀層處理之后,會被轉(zhuǎn)換為一個(gè)個(gè)帶有請求 ID 編號的幀,通過協(xié)議棧將這些幀發(fā)送給服務(wù)器。請求頭的信息存在 header 幀中,而請求體數(shù)據(jù)存在 data 幀中。
- 服務(wù)器接收到所有幀之后,會將所有相同 ID 的幀合并為一條完整的請求信息。
- 然后服務(wù)器處理該條請求,并將處理的響應(yīng)行、響應(yīng)頭和響應(yīng)體分別發(fā)送至二進(jìn)制分幀層。
- 同樣,二進(jìn)制分幀層會將這些響應(yīng)數(shù)據(jù)轉(zhuǎn)換為一個(gè)個(gè)帶有請求 ID 編號的幀,經(jīng)過協(xié)議棧發(fā)送給瀏覽器。
- 瀏覽器接收到響應(yīng)幀之后,會根據(jù) ID 編號將幀的數(shù)據(jù)提交給對應(yīng)的請求。
HTTP/2 其他特性
1. 可以設(shè)置請求的優(yōu)先級
在瀏覽器中,某些數(shù)據(jù)是非常重要的,比如關(guān)鍵 CSS 或者 JS,這些重要的數(shù)據(jù)如果比較晚才推送到瀏覽器,那么對用戶來說肯定是一個(gè)不好的體驗(yàn)。
所以 HTTP/2 中可以支持設(shè)置請求的優(yōu)先級,這樣服務(wù)器收到高優(yōu)先級的請求后,會優(yōu)先處理。
2. 服務(wù)器推送
在 HTTP/2 中服務(wù)器解析到一個(gè) HTML 頁面后,服務(wù)器知道瀏覽器需要這個(gè)頁面上引用到的資源,比如 CSS 和 JS,那么服務(wù)器就會主動的把這些資源一并推送給瀏覽器,減少客戶端的等待時(shí)間。
3. 頭部壓縮
HTTP/2 使用 HPACK 壓縮算法對請求頭和響應(yīng)頭進(jìn)行壓縮,雖然單個(gè)請求壓縮之后效果不是很明顯,但是如果一個(gè)頁面有 100 個(gè)請求,那每個(gè)請求壓縮 20% 之后,那提速效果就很明顯了。
而 HPACK 的壓縮原理其實(shí)就是 2 點(diǎn):
- 它要求客戶端和服務(wù)器兩者都維護(hù)和更新先前看到的報(bào)頭字段的索引列表(即,建立共享的壓縮上下文),然后將該列表用作有效編碼先前傳輸?shù)闹档膮⒖肌T趯?shí)際傳輸?shù)臅r(shí)候用索引代替每一側(cè)的靜態(tài)或動態(tài)表中已經(jīng)存在的字段,從而減小每個(gè)請求的大小。
- 它允許通過靜態(tài)霍夫曼碼對發(fā)送的標(biāo)頭字段進(jìn)行編碼,從而減小了它們各自的傳輸大小。
HTTP/3
HTTP/2 依然是基于 TCP 的,所以還存在以下一些問題。
TCP 的隊(duì)頭阻塞
HTTP/2 中多個(gè)請求是跑在一個(gè) TCP 連接中的,如果某個(gè)數(shù)據(jù)流中出現(xiàn)了丟包的情況,就會阻塞該 TCP 連接中的所有請求。這個(gè)和 HTTP/1.1 中的不同,在 HTTP/1.1 中,由于瀏覽器為每個(gè)域名建立了 6 個(gè) TCP 連接,如果其中一個(gè) TCP 連接發(fā)生了隊(duì)頭阻塞,那么其他的 5 個(gè)連接依然可以繼續(xù)傳輸數(shù)據(jù)。
TCP 建立連接的延時(shí)
在傳輸數(shù)據(jù)之前,需要進(jìn)行 TCP 的 3 次握手,需要花費(fèi) 1.5 個(gè) RTT;如果是 HTTPS,那還需要進(jìn)行 TLS 連接,又需要 1 ~ 2 個(gè) RTT。
- “網(wǎng)絡(luò)延遲又叫 RTT(Round Trip Time),是從瀏覽器發(fā)送一個(gè)數(shù)據(jù)包到服務(wù)器,再從服務(wù)器返回?cái)?shù)據(jù)包到瀏覽器的整個(gè)往返時(shí)間。
總之,在傳輸數(shù)據(jù)之前需要花掉 3 ~ 4 個(gè) RTT。如果客戶端和服務(wù)器距離近的話,那 1 個(gè) RTT 大概是 10ms,但如果遠(yuǎn)的話,可能是 100ms,所以傳輸數(shù)據(jù)之前需要花掉 300ms 左右,這個(gè)時(shí)候就能感覺到慢了。
TCP 協(xié)議僵化
我們知道 TCP 協(xié)議存在隊(duì)頭阻塞和建立連接延遲的問題,但是又沒辦法改進(jìn) TCP 協(xié)議,理由有如下 2 個(gè):
中間設(shè)備僵化。中間設(shè)備比如路由器、交換機(jī)、防火墻和 NAT 等,這些設(shè)備依賴的軟件使用了大量的 TCP 特性,一旦功能被設(shè)置后就很少進(jìn)行更新了。如果在客戶端進(jìn)行升級 TCP 協(xié)議,那么當(dāng)新協(xié)議的數(shù)據(jù)包經(jīng)過這些設(shè)備的時(shí)候,可能會不理解包的內(nèi)容,造成數(shù)據(jù)丟失。
操作系統(tǒng)也是導(dǎo)致 TCP 協(xié)議僵化的另外一個(gè)原因。
QUIC 協(xié)議

HTTP/3 是基于 UDP 實(shí)現(xiàn)的,實(shí)現(xiàn)了類似于 TCP 的多路數(shù)據(jù)流、傳輸可靠性等功能,我們把這套功能稱為 QUIC 協(xié)議。
- 實(shí)現(xiàn)了類似 TCP 的流量控制、傳輸可靠性的功能。雖然 UDP 不提供可靠性的傳輸,但 QUIC 在 UDP 的基礎(chǔ)之上增加了一層來保證數(shù)據(jù)可靠性傳輸。它提供了數(shù)據(jù)包重傳、擁塞控制以及其他一些 TCP 中存在的特性。
- 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相較于早期版本 TLS1.3 有更多的優(yōu)點(diǎn),其中最重要的一點(diǎn)是減少了握手所花費(fèi)的 RTT 個(gè)數(shù)。
- 實(shí)現(xiàn)了 HTTP/2 中的多路復(fù)用功能。和 TCP 不同,QUIC 實(shí)現(xiàn)了在同一物理連接上可以有多個(gè)獨(dú)立的邏輯數(shù)據(jù)流(如下圖)。實(shí)現(xiàn)了數(shù)據(jù)流的單獨(dú)傳輸,就解決了 TCP 中隊(duì)頭阻塞的問題。
- 實(shí)現(xiàn)了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以實(shí)現(xiàn)使用 0-RTT 或者 1-RTT 來建立連接,這意味著 QUIC 可以用最快的速度來發(fā)送和接收數(shù)據(jù),這樣可以大大提升首次打開頁面的速度。
HTTP/3 的挑戰(zhàn)
- 第一,從目前的情況來看,服務(wù)器和瀏覽器端都沒有對 HTTP/3 提供比較完整的支持。Chrome 雖然在數(shù)年前就開始支持 Google 版本的 QUIC,但是這個(gè)版本的 QUIC 和官方的 QUIC 存在著非常大的差異。
- 第二,部署 HTTP/3 也存在著非常大的問題。因?yàn)橄到y(tǒng)內(nèi)核對 UDP 的優(yōu)化遠(yuǎn)遠(yuǎn)沒有達(dá)到 TCP 的優(yōu)化程度,這也是阻礙 QUIC 的一個(gè)重要原因。
- 第三,中間設(shè)備僵化的問題。這些設(shè)備對 UDP 的優(yōu)化程度遠(yuǎn)遠(yuǎn)低于 TCP,據(jù)統(tǒng)計(jì)使用 QUIC 協(xié)議時(shí),大約有 3%~7% 的丟包率。