背景介紹就到此為止了,歷史的腳步已經(jīng)將我們推到了今天?,F(xiàn)在讓我們深入看看該協(xié)議的規(guī)范,看看那些細(xì)節(jié)和概念。
http2是一個(gè)二進(jìn)制協(xié)議。
仔細(xì)想想,如果你是一個(gè)曾經(jīng)跟互聯(lián)網(wǎng)協(xié)議打過(guò)交道,那你很可能會(huì)本能反對(duì)二進(jìn)制協(xié)議,你甚至準(zhǔn)備好了一大堆理由來(lái)證明基于文本/ascii的協(xié)議是多么的有用,正如你曾無(wú)數(shù)次地通過(guò)telnet等應(yīng)用手工地輸入HTTP來(lái)發(fā)起請(qǐng)求。
基于二進(jìn)制的http2可以使成幀的使用變得更為便捷。在HTTP1.1和其他基于文本的協(xié)議中,對(duì)幀的起始和結(jié)束識(shí)別起來(lái)相當(dāng)復(fù)雜。而通過(guò)移除掉可選的空白符以及其他冗余后,再來(lái)實(shí)現(xiàn)這些會(huì)變得更容易。
而另一方面,這項(xiàng)決議同樣使得我們可以更加便捷的從幀結(jié)構(gòu)中分離出那部分協(xié)議本身的內(nèi)容。而在HTTP1中,各個(gè)部分相互交織,猶如一團(tuán)亂麻。
事實(shí)上,由于協(xié)議提供了壓縮這一特性,而其經(jīng)常運(yùn)行在TLS之上的事實(shí)又再次降低了基于純文本實(shí)現(xiàn)的價(jià)值,反正也沒(méi)辦法直接從數(shù)據(jù)流上看到文本。因此通常情況下,我們必須習(xí)慣使用類似Wireshark這樣的工具對(duì)http2的協(xié)議層一探究竟。
我們可以使用curl這樣的工具來(lái)調(diào)試協(xié)議,而如果要進(jìn)一步地分析網(wǎng)絡(luò)數(shù)據(jù)流則需要諸如Wireshark這樣的http2解析器。
http2會(huì)發(fā)送有著不同類型的二進(jìn)制幀,但他們都有如下的公共字段:Type, Length, Flags, Stream Identifier和frame payload
規(guī)范中一共定義了10種不同的幀,其中最基礎(chǔ)的兩種分別對(duì)應(yīng)于HTTP 1.1的DATA和HEADERS。之后我會(huì)更詳細(xì)的介紹它們其中的一部分。
上一節(jié)提到的Stream Identifier將http2連接上傳輸?shù)拿總€(gè)幀都關(guān)聯(lián)到一個(gè)“流”。流是一個(gè)獨(dú)立的,雙向的幀序列可以通過(guò)一個(gè)http2的連接在服務(wù)端與客戶端之間不斷的交換數(shù)據(jù)。
每個(gè)單獨(dú)的http2連接都可以包含多個(gè)并發(fā)的流,這些流中交錯(cuò)的包含著來(lái)自兩端的幀。流既可以被客戶端/服務(wù)器端單方面的建立和使用,也可以被雙方共享,或者被任意一邊關(guān)閉。在流里面,每一幀發(fā)送的順序非常關(guān)鍵。接收方會(huì)按照收到幀的順序來(lái)進(jìn)行處理。
流的多路復(fù)用意味著在同一連接中來(lái)自各個(gè)流的數(shù)據(jù)包會(huì)被混合在一起。就好像兩個(gè)(或者更多)獨(dú)立的“數(shù)據(jù)列車”被拼湊到了一輛列車上,但它們最終會(huì)在終點(diǎn)站被分開(kāi)。下圖就是兩列“數(shù)據(jù)火車”的示例
它們就是這樣通過(guò)多路復(fù)用的方式被組裝到了同一列火車上。
每個(gè)流都包含一個(gè)優(yōu)先級(jí)(也就是“權(quán)重”),它被用來(lái)告訴對(duì)端哪個(gè)流更重要。當(dāng)資源有限的時(shí)候,服務(wù)器會(huì)根據(jù)優(yōu)先級(jí)來(lái)選擇應(yīng)該先發(fā)送哪些流。
借助于PRIORITY幀,客戶端同樣可以告知服務(wù)器當(dāng)前的流依賴于其他哪個(gè)流。該功能讓客戶端能建立一個(gè)優(yōu)先級(jí)“樹(shù)”,所有“子流”會(huì)依賴于“父流”的傳輸完成情況。
優(yōu)先級(jí)和依賴關(guān)系可以在傳輸過(guò)程中被動(dòng)態(tài)的改變。這樣當(dāng)用戶滾動(dòng)一個(gè)全是圖片的頁(yè)面的時(shí)候,瀏覽器就能夠指定哪個(gè)圖片擁有更高的優(yōu)先級(jí)。或者是在你切換標(biāo)簽頁(yè)的時(shí)候,瀏覽器可以提升新切換到頁(yè)面所包含流的優(yōu)先級(jí)。
HTTP是一種無(wú)狀態(tài)的協(xié)議。簡(jiǎn)而言之,這意味著每個(gè)請(qǐng)求必須要攜帶服務(wù)器需要的所有細(xì)節(jié),而不是讓服務(wù)器保存住之前請(qǐng)求的元數(shù)據(jù)。因?yàn)閔ttp2并沒(méi)有改變這個(gè)范式,所以它也以同樣原理工作。
這也保證了HTTP可重復(fù)性。當(dāng)一個(gè)客戶端從同一服務(wù)器請(qǐng)求了大量資源(例如頁(yè)面的圖片)的時(shí)候,所有這些請(qǐng)求看起來(lái)幾乎都是一致的,而這些大量一致的東西則正好值得被壓縮。
當(dāng)每個(gè)頁(yè)面資源的個(gè)數(shù)上升的時(shí)候,cookies和請(qǐng)求的大小都會(huì)增加。cookies需要被包含在所有請(qǐng)求中,且他們?cè)诙鄠€(gè)請(qǐng)求中經(jīng)常是一模一樣的。
HTTP 1.1請(qǐng)求的大小正變得越來(lái)越大,有時(shí)甚至?xí)笥赥CP窗口的初始大小,這會(huì)嚴(yán)重拖累發(fā)送請(qǐng)求的速度。因?yàn)樗鼈冃枰却龓е鳤CK的響應(yīng)回來(lái)以后,才能繼續(xù)被發(fā)送。這也是另一個(gè)需要壓縮的理由。
HTTPS和SPDY的壓縮機(jī)制被發(fā)現(xiàn)有受BREACH和CRIME攻擊的隱患。通過(guò)向流中注入一些已知的文本來(lái)觀察輸出的變化,攻擊者可以從加密的載荷中推導(dǎo)出原始發(fā)送的數(shù)據(jù)。
為協(xié)議的動(dòng)態(tài)內(nèi)容進(jìn)行壓縮并使其免于被攻擊,需要仔細(xì)且全面的考慮,而這也正是HTTPbis小組嘗試去做的。
HPACK,HTTP/2頭部壓縮,顧名思義它是一個(gè)專為http2頭部設(shè)計(jì)的壓縮格式。確切的講,它甚至被制定寫(xiě)入在另外一個(gè)單獨(dú)的草案里。新的格式同時(shí)引入了一些其他對(duì)策讓破解壓縮變得困難,例如采用幀的可選填充和用一個(gè)bit作為標(biāo)記,來(lái)讓中間人不壓縮指定的頭部。
用Roberto Peon(HPACK的設(shè)計(jì)者之一)的話說(shuō)
“HPACK旨在提供一個(gè)一致性的實(shí)現(xiàn)使信息量的損失盡可能少,使編解碼快速而方便,使接收方能控制壓縮文本的大小,允許代理重新建立索引(如,通過(guò)代理在前后端共享狀態(tài)),以及對(duì)哈夫曼編碼串的更快速比較”
+
HTTP 1.1的有一個(gè)缺點(diǎn)是:當(dāng)一個(gè)含有確切值的Content-Length的HTTP消息被送出之后,你就很難中斷它了。當(dāng)然,通常你可以斷開(kāi)整個(gè)TCP鏈接(但也不總是可以這樣),但這樣導(dǎo)致的代價(jià)就是需要通過(guò)三次握手來(lái)重新建立一個(gè)新的TCP連接。
一個(gè)更好的方案是只終止當(dāng)前傳輸?shù)南⒉⒅匦掳l(fā)送一個(gè)新的。在http2里面,我們可以通過(guò)發(fā)送RST_STREAM幀來(lái)實(shí)現(xiàn)這種需求,從而避免浪費(fèi)帶寬和中斷已有的連接。
這個(gè)功能通常被稱作“緩存推送”。主要的思想是:當(dāng)一個(gè)客戶端請(qǐng)求資源X,而服務(wù)器知道它很可能也需要資源Z的情況下,服務(wù)器可以在客戶端發(fā)送請(qǐng)求前,主動(dòng)將資源Z推送給客戶端。這個(gè)功能幫助客戶端將Z放進(jìn)緩存以備將來(lái)之需。
服務(wù)器推送需要客戶端顯式的允許服務(wù)器提供該功能。但即使如此,客戶端依然能自主選擇是否需要中斷該推送的流。如果不需要的話,客戶端可以通過(guò)發(fā)送一個(gè)RST_STREAM幀來(lái)中止。
每個(gè)http2流都擁有自己的公示的流量窗口,它可以限制另一端發(fā)送數(shù)據(jù)。如果你正好知道SSH的工作原理的話,這兩者非常相似。
對(duì)于每個(gè)流來(lái)說(shuō),兩端都必須告訴對(duì)方自己還有足夠的空間來(lái)處理新的數(shù)據(jù),而在該窗口被擴(kuò)大前,另一端只被允許發(fā)送這么多數(shù)據(jù)。
而只有數(shù)據(jù)幀會(huì)受到流量控制。
更多建議: