HTTP發(fā)展簡(jiǎn)史
HTTP/0.9
- 1991年發(fā)布
- 只支持GET命令
- 請(qǐng)求及返回值都是ASCII碼
- 請(qǐng)求以換行符(CRLF)結(jié)束
- 返回值只支持HTML格式
- 服務(wù)器返回值之后立刻關(guān)閉連接
GET /index.html
HTTP/1.0
- 1996年發(fā)布
- 除了GET命令,還增加了POST和HEAD命令
- 服務(wù)器支持返回任意格式
- 請(qǐng)求和返回值除了包含數(shù)據(jù)部分圃阳,還增加頭部信息(HTTP header)
- 返回值增加狀態(tài)碼(status code)
GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
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
<html>
<body>Hello World</body>
</html>
HTTP/1.1
- 1997年發(fā)布
- 支持持久連接(Connection: keep-alive),即TCP連接默認(rèn)不關(guān)閉璧帝,可以被多個(gè)請(qǐng)求復(fù)用
- 支持管道機(jī)制(pipelining)捍岳,即一個(gè)TCP連接內(nèi)可以同時(shí)發(fā)起多個(gè)請(qǐng)求
- 支持分塊傳輸編碼(chunked transfer encoding)
- 支持?jǐn)帱c(diǎn)續(xù)傳(Accept-Ranges)
- 支持更多命令,如PUT, PATCH, OPTIONS, DELETE
- 盡管支持復(fù)用TCP連接睬隶,但仍然會(huì)產(chǎn)生隊(duì)頭阻塞(Head-of-line blocking)問(wèn)題
HTTP/2
- 2015年發(fā)布
- 2009年Google自行研發(fā)的SPDY在Chrome上驗(yàn)證成功后锣夹,被當(dāng)作是HTTP/2的基礎(chǔ)
- 完全采用二進(jìn)制協(xié)議
- 支持多路復(fù)用(multiplexing)
- 支持頭部壓縮(header compression)
- 支持服務(wù)器推送(server push)
二進(jìn)制協(xié)議
HTTP/2之前的協(xié)議都是基于ASCII碼,好處是可讀性好苏潜,容易上手银萍。其缺點(diǎn)是可選的空格以及多變的終止符給識(shí)別幀造成了一些困難。采用二進(jìn)制協(xié)議可以使得幀的識(shí)別更簡(jiǎn)單恤左,并且傳輸信息更高效贴唇。其缺點(diǎn)是不便于調(diào)試,這就需要我們使用相應(yīng)的工具來(lái)理解二進(jìn)制的內(nèi)容赃梧。
HTTP/2完全采用二進(jìn)制協(xié)議滤蝠,頭信息和數(shù)據(jù)體都是二進(jìn)制的豌熄,統(tǒng)稱為幀(frame)授嘀。下圖展示了同一個(gè)請(qǐng)求在HTTP/1.1和HTTP/2的對(duì)應(yīng)關(guān)系,可見(jiàn)請(qǐng)求在HTTP/2中分為了兩部分:頭部幀和數(shù)據(jù)幀锣险。
一個(gè)幀的基本格式如下:
所有幀都由一個(gè)9字節(jié)的header和可變長(zhǎng)的payload組成蹄皱,各字段定義如下:
- Length: 表示payload的長(zhǎng)度,payload的長(zhǎng)度默認(rèn)不能超過(guò)214 (16,384) 芯肤,除非修改SETTINGS_MAX_FRAME_SIZE為更大的值
- Type: 表示幀的類型巷折,比如0x0表示數(shù)據(jù)幀,0x1表示頭部幀崖咨,類型不在規(guī)范里定義的幀將會(huì)被丟棄
- Flags: 對(duì)于不同類型的幀锻拘,這個(gè)字段有不同的含義,比如在數(shù)據(jù)幀里击蹲,0x1表示這個(gè)frame是流的最后一幀(END_STREAM)
- R: 保留位署拟,這一位必須置為0并且需要被忽略
- Stream Identifier: 流ID,客戶端發(fā)起的流ID必須是奇數(shù)的歌豺,服務(wù)端發(fā)起的流ID必須是偶數(shù)的
多路復(fù)用
為了說(shuō)明什么是多路復(fù)用推穷,我們先需要明確下面幾個(gè)概念:
- 流(stream): 已建立的連接內(nèi)的雙向字節(jié)流,可以承載一條或多條消息
- 消息(message): 與邏輯請(qǐng)求或響應(yīng)消息對(duì)應(yīng)的完整的一系列幀
- 幀(frame): HTTP/2 通信的最小單位类咧,每個(gè)幀都包含幀頭馒铃,至少也會(huì)標(biāo)識(shí)出當(dāng)前幀所屬的數(shù)據(jù)流
這些概念的關(guān)系總結(jié)如下:
- 所有通信都在一個(gè) TCP 連接上完成蟹腾,此連接可以承載任意數(shù)量的雙向數(shù)據(jù)流
- 每個(gè)數(shù)據(jù)流都有一個(gè)唯一的標(biāo)識(shí)符和可選的優(yōu)先級(jí)信息,用于承載雙向消息
- 每條消息都是一條邏輯 HTTP 消息(例如請(qǐng)求或響應(yīng))区宇,包含一個(gè)或多個(gè)幀
- 幀是最小的通信單位娃殖,承載著特定類型的數(shù)據(jù),例如 HTTP Header议谷、消息負(fù)載等等珊随。 來(lái)自不同數(shù)據(jù)流的幀可以交錯(cuò)發(fā)送,然后再根據(jù)每個(gè)幀頭的數(shù)據(jù)流標(biāo)識(shí)符重新組裝
在HTTP/1.1中柿隙,如果客戶端為了提高性能想要在一個(gè)TCP連接內(nèi)同時(shí)發(fā)起多個(gè)請(qǐng)求叶洞,每個(gè)請(qǐng)求必須按順序被服務(wù)器依次響應(yīng),如果某一個(gè)請(qǐng)求特別耗時(shí)禀崖,那么后面的請(qǐng)求將會(huì)被一直阻塞衩辟。
而在HTTP/2中,如果在一個(gè)TCP連接內(nèi)同時(shí)發(fā)起多個(gè)請(qǐng)求波附,每個(gè)消息可以被拆成互不依賴的幀并且各幀之間交錯(cuò)發(fā)送艺晴,然后在另一端重新把幀組裝起來(lái)。這個(gè)特性就叫做多路復(fù)用掸屡。
上圖展示了同一個(gè)連接內(nèi)并行的多個(gè)數(shù)據(jù)流封寞。客戶端正在向服務(wù)器傳輸一個(gè)數(shù)據(jù)幀(數(shù)據(jù)流5)仅财,與此同時(shí)狈究,服務(wù)器正向客戶端交錯(cuò)發(fā)送數(shù)據(jù)流1和數(shù)據(jù)流3的一系列幀。因此盏求,一個(gè)連接上同時(shí)有三個(gè)并行數(shù)據(jù)流抖锥。
頭部壓縮
每個(gè)HTTP請(qǐng)求時(shí)都會(huì)承載一組表頭。在HTTP/1.x中表頭是以純文本形式傳輸碎罚,通常需要500~800字節(jié)的開(kāi)銷磅废,如果有cookie的話甚至?xí)_(dá)到上千字節(jié)。為了減少這種開(kāi)銷并且提升性能荆烈,HTTP/2使用了HPACK算法進(jìn)行壓縮拯勉,具體來(lái)說(shuō)包含了如下兩種簡(jiǎn)單并強(qiáng)大的技術(shù):
- 頭部字段使用靜態(tài)Huffman編碼,如果編碼后使得字符反而變長(zhǎng)了憔购,那么不采用Huffman編碼
- 客戶端和服務(wù)器同時(shí)維護(hù)并更新一個(gè)索引表宫峦,如果傳輸?shù)闹翟谒饕砝铮敲词褂盟饕底鳛閭鬏數(shù)闹稻胧肌K饕矸譃殪o態(tài)表和動(dòng)態(tài)表兩部分斗遏,靜態(tài)表在規(guī)范里定義,包含了一些常用的字段鞋邑,動(dòng)態(tài)表初始為空诵次,在連接過(guò)程中動(dòng)態(tài)更新
如上圖所示账蓉,最左邊是原始的請(qǐng)求頭,第一行的:method GET
通過(guò)查找靜態(tài)索引表得到索引值為2逾一,所以HPACK算法將其編碼為2铸本。最后第二行的user-agent Mozilla/5.0 ...
不在靜態(tài)索引表里,但在動(dòng)態(tài)索引表里查到索引值為62遵堵,所以HPACK算法將其編碼為62箱玷。最后一行的兩個(gè)字段均未在索引表里查到,所以分別對(duì)其進(jìn)行Huffman編碼陌宿。
服務(wù)器推送
HTTP/2新增的另一個(gè)強(qiáng)大的功能是允許服務(wù)器除了可以響應(yīng)客戶端請(qǐng)求锡足,還可以向客戶端推送額外的資源。
通常當(dāng)我們請(qǐng)求一個(gè)網(wǎng)頁(yè)時(shí)壳坪,客戶端解析HTML源碼舶得,發(fā)現(xiàn)有js或css等其他靜態(tài)資源,然后再發(fā)起請(qǐng)求下載靜態(tài)資源爽蝴。而實(shí)際上沐批,當(dāng)客戶端請(qǐng)求網(wǎng)頁(yè)后,服務(wù)器完全可以預(yù)判客戶端接下來(lái)要請(qǐng)求相關(guān)的靜態(tài)資源蝎亚,那為什么不讓服務(wù)器提前推送這些資源九孩,從而減少額外的延遲時(shí)間呢?HTTP/2為此提出了服務(wù)器推送機(jī)制发框,服務(wù)器端可以通過(guò)發(fā)起PUSH_PROMISE幀告知客戶端躺彬,客戶端收到服務(wù)器想要推送資源的意圖后,可以決定是否接收推送缤底。
事實(shí)上顾患,如果你在網(wǎng)頁(yè)中內(nèi)聯(lián)CSS或Javascript,那么你已經(jīng)體驗(yàn)過(guò)服務(wù)器推送了个唧。使用HTTP/2,我們不僅可以獲得相同效果设预,還可以獲得更多的性能優(yōu)勢(shì):
- 客戶端可以緩存推送資源
- 推送資源可以被不同頁(yè)面重用
- 推送資源可以與其他資源復(fù)用
- 推送資源可以由服務(wù)器設(shè)定優(yōu)先級(jí)
- 推送資源可以被客戶端拒絕(非強(qiáng)制推送)
服務(wù)器推送功能雖然很強(qiáng)大徙歼,但在實(shí)際使用中還需要考慮一些問(wèn)題。第一個(gè)問(wèn)題是如果客戶端已經(jīng)有緩存了鳖枕,那么推送資源就是一種浪費(fèi)魄梯。一種解決方法是只在用戶第一次訪問(wèn)的時(shí)候推送資源。第二個(gè)問(wèn)題是目前我們一般把靜態(tài)資源放在CDN上宾符,目前大部分CDN還不支持服務(wù)器推送酿秸,那么CDN和服務(wù)器推送到底哪個(gè)效果更好,這個(gè)可能還需要一些測(cè)試數(shù)據(jù)來(lái)做評(píng)判魏烫。
參考資料
- Hypertext Transfer Protocol version 2 - RFC7540
- HPACK - Header Compression for HTTP/2 - RFC7541
- An Excerpt from High Performance Browser Networking: HTTP/2
- Homepage for HTTP/2
- Introduction to HTTP/2
- HTTP 協(xié)議入門
- HTTP/2 服務(wù)器推送(Server Push)教程