HTTP 概述
HTTP 超文本傳輸??協(xié)議是位于 TCP/IP 體系結(jié)構(gòu)中的應(yīng)用層協(xié)議毛萌,它是萬維網(wǎng)的數(shù)據(jù)通信的基礎(chǔ)。
當(dāng)我們訪問一個(gè)網(wǎng)站時(shí)褪测,需要通過統(tǒng)一資源定位符 URL 來定位服務(wù)器并獲取資源猴誊。
<協(xié)議>://<域名>:<端口>/<路徑>
一個(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 頁面的過程。
- 先通過 DNS查詢將域名轉(zhuǎn)換為 IP 地址墨状。即將
test.com
轉(zhuǎn)換為221.239.100.30
這一過程卫漫。 - 通過三次握手(詳情見文末)建立 TCP 連接。
- 發(fā)起 HTTP 請求肾砂。
- 目標(biāo)服務(wù)器接收到 HTTP 請求并處理列赎。
- 目標(biāo)服務(wù)器往瀏覽器發(fā)回 HTTP 響應(yīng)。
- 瀏覽器解析并渲染頁面镐确。
下圖中的 RTT 為往返時(shí)延包吝。
HTTP 連接拆除過程
所有 HTTP 客戶端(瀏覽器)、服務(wù)器都可在任意時(shí)刻關(guān)閉 TCP 連接源葫。通常會在一條報(bào)文結(jié)束時(shí)關(guān)閉連接诗越,但出錯(cuò)的時(shí)候,也可能在首部行的中間或其他任意位置關(guān)閉連接臼氨。
由于 HTTP 是基于 TCP 的掺喻,所以在經(jīng)歷 TCP 四次揮手(詳情見文末)過程后,連接就正常關(guān)閉了储矩。
HTTP 報(bào)文格式
HTTP 報(bào)文由請求行感耙、首部、實(shí)體主體組成持隧,它們之間由 CRLF(回車換行符) 分隔開即硼。
注意:實(shí)體包括首部(也稱為實(shí)體首部)和實(shí)體主體,sp 即是空格 space屡拨。
請求行和首部是由 ASCII 文本組成的只酥,實(shí)體主體是可選的,可以為空也可以是任意二進(jìn)制數(shù)據(jù)呀狼。
請求報(bào)文和響應(yīng)報(bào)文的格式基本相同裂允。
請求報(bào)文格式:
<method> <request-URL> <version>
<headers>
<entity-body>
響應(yīng)報(bào)文格式:
<version> <status> <reason-phrase>
<headers>
<entity-body>
一個(gè)請求或響應(yīng)報(bào)文由以下字段組成:
1.請求方法,客戶端希望服務(wù)器對資源執(zhí)行的動作哥艇。
2.請求 URL绝编,命名了所請求的資源。
3.協(xié)議版本貌踏,報(bào)文所使用的 HTTP 版本十饥。
4.狀態(tài)碼,這三位數(shù)字描述了請求過程中所發(fā)生的情況祖乳。
5.原因短語逗堵,數(shù)字狀態(tài)碼的可讀版本(例如上面的響應(yīng)示例跟在 200 后面的 OK,一般按規(guī)范寫最好)眷昆。
6.首部蜒秤,可以有零或多個(gè)首部汁咏。
7.實(shí)體的主體部分,可以為空也可以包含任意二進(jìn)制數(shù)據(jù)作媚。
一個(gè) 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
一個(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
方法
GET 和 HEAD
其中 GET 和 HEAD 被稱為安全方法梆暖,因?yàn)樗鼈兪莾绲鹊模ㄈ绻粋€(gè)請求不管執(zhí)行多少次,其結(jié)果都是一樣的掂骏,這個(gè)請求就是冪等的),類似于 POST 就不是冪等的厚掷。
HEAD 方法和 GET 方法很類似弟灼,但服務(wù)器在響應(yīng)中只返回首部。這就允許客戶端在未獲取實(shí)際資源的情況下冒黑,對資源的首部進(jìn)行檢查田绑。使用 HEAD,可以:
1.在不獲取資源的情況下了解資源的情況抡爹。
2.通過查看響應(yīng)狀態(tài)碼掩驱,看看某個(gè)對象是否存在。
3.通過查看首部冬竟,了解測試資源是否被修改了欧穴。
4.服務(wù)器開發(fā)者必須確保返回的首部與 GET 請求所返回的首部完全相同。遵循 HTTP/1.1 規(guī)范泵殴,就必須實(shí)現(xiàn) HEAD 方法涮帘。
PUT
與 GET 方法從服務(wù)器讀取文檔相反,PUT 方法會向服務(wù)器寫入文檔笑诅。PUT 方法的語義就是讓服務(wù)器用請求的主體部分來創(chuàng)建一個(gè)由所請求的 URL 命名的新文檔调缨。 如果那個(gè)文檔已存在,就覆蓋它吆你。因?yàn)?PUT 允許用戶對內(nèi)容進(jìn)行修改弦叶,所以服務(wù)器要求在執(zhí)行 PUT 之前,要用密碼登錄妇多。
POST
POST 方法通常用來向服務(wù)器發(fā)送表單數(shù)據(jù)伤哺。
TRACE
客戶端發(fā)起一個(gè)請求時(shí),這個(gè)請求可能要穿過路由器砌梆、防火墻默责、代理、網(wǎng)關(guān)等咸包。每個(gè)中間節(jié)點(diǎn)都可能會修改原始的 HTTP 請求桃序,TRACE 方法允許客戶端在最終發(fā)起請求時(shí),看看它變成了什么樣子烂瘫。
TRACE 請求會在目的服務(wù)器端發(fā)起一個(gè)“環(huán)回”診斷媒熊。行程最后一站的服務(wù)器會彈回一條 TRACE 響應(yīng)奇适,并在響應(yīng)主體中攜帶它收到的原始請求報(bào)文。 這樣客戶端就可以查看在所有中間 HTTP 應(yīng)用程序組成的請求/響應(yīng)鏈上芦鳍,原始報(bào)文是否被毀壞或修改過嚷往。
TRACE 方法主要用于診斷,用于驗(yàn)證請求是否如愿穿過了請求/響應(yīng)鏈柠衅。它也是一種工具皮仁,用來查看代理和其他應(yīng)用程序?qū)τ脩粽埱笏a(chǎn)生的效果。 TRACE 請求中不能帶有實(shí)體的主體部分菲宴。TRACE 響應(yīng)的實(shí)體主體部分包含了響應(yīng)服務(wù)器收到的請求的精確副本贷祈。
OPTIONS
OPTIONS 方法請求 Web 服務(wù)器告知其支持的各種功能。
DELETE
DELETE 方法就是讓服務(wù)器刪除請求 URL 所指定的資源喝峦。
狀態(tài)碼
300~399 重定向狀態(tài)碼
重定向狀態(tài)碼要么告訴客戶端使用替代位置來訪問他們感興趣的資源势誊,要么提供一個(gè)替代的響應(yīng)而不是資源的內(nèi)容。 如果資源已被移動谣蠢,可以發(fā)送一個(gè)重定向狀態(tài)碼和一個(gè)可選的 Location 首部來告知客戶端資源已被移走粟耻,以及現(xiàn)在在哪里可以找到它。這樣眉踱,瀏覽器可以在不打擾使用者的情況下挤忙,透明地轉(zhuǎn)入新的位置。400~499 客戶端錯(cuò)誤狀態(tài)碼
有時(shí)客戶端會發(fā)送一些服務(wù)器無法處理的東西勋锤,例如格式錯(cuò)誤的請求報(bào)文饭玲、一個(gè)不存在的 URL。
500~599 服務(wù)器錯(cuò)誤狀態(tài)碼
有時(shí)客戶端發(fā)送了一條有效請求叁执,服務(wù)器自身卻出錯(cuò)了茄厘。
首部
首部和方法共同配合工作,決定了客戶端和服務(wù)器能做什么事情谈宛。
首部分類:
1.通用首部次哈,可以出現(xiàn)在請求或響應(yīng)報(bào)文中。
2.請求首部吆录,提供更多有關(guān)請求的信息窑滞。
3.響應(yīng)首部,提供更多有關(guān)響應(yīng)的信息恢筝。
4.實(shí)體首部哀卫,描述主體的長度和內(nèi)容,或者資源自身撬槽。
5.擴(kuò)展首部此改,規(guī)范中沒有定義的新首部。
通用首部
有些首部提供了與報(bào)文相關(guān)的最基本信息侄柔,它們被稱為通用首部共啃。以下是一些常見的通用首部:
請求首部
請求首部是只在請求報(bào)文中有意義的首部占调,用于說明請求的詳情。以下是一些常見的請求首部:
響應(yīng)首部
響應(yīng)首部讓服務(wù)器為客戶端提供了一些額外的信息移剪。
實(shí)體首部
實(shí)體首部提供了有關(guān)實(shí)體及其內(nèi)容的大量信息究珊,從有關(guān)對象類型的信息,到能夠?qū)Y源使用的各種有效的請求方法纵苛。 例如內(nèi)容首部剿涮,提供了與實(shí)體內(nèi)容有關(guān)的特定信息,說明了其類型攻人、尺寸以及處理它所需的其他有用信息幔虏。 另外,通用的緩存首部說明了如何或什么時(shí)候進(jìn)行緩存贝椿。實(shí)體的緩存首部提供了與被緩存實(shí)體有關(guān)的信息。
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 請求應(yīng)答過程中,如果出現(xiàn)了某種情況赖阻,導(dǎo)致響應(yīng)一直未能完成蝶押,那后面所有的請求就會一直阻塞著,這種情況叫隊(duì)頭阻塞政供。
2. 低效的 TCP 利用
由于 TCP 慢啟動機(jī)制播聪,導(dǎo)致每個(gè) TCP 連接在一開始的時(shí)候傳輸速率都不高朽基,在處理多個(gè)請求后,才會慢慢達(dá)到“合適”的速率离陶。對于請求數(shù)據(jù)量很小的 HTTP 請求來說稼虎,這種情況就是種災(zāi)難。
*** 3. 臃腫的消息首部***
HTTP/1.1 的首部無法壓縮招刨,再加上 cookie 的存在霎俩,經(jīng)常會出現(xiàn)首部大小比請求數(shù)據(jù)大小還大的情況。
*** 4. 受限的優(yōu)先級設(shè)置***
HTTP/1.1 無法為重要的資源指定優(yōu)先級沉眶,每個(gè) HTTP 請求都是一視同仁打却。
在繼續(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ī)矩的客戶端,它只會發(fā)送 LF盲再。
解析 HTTP/1.1 的請求或響應(yīng)還會遇到以下問題:
- 一次只能處理一個(gè)請求或響應(yīng)西设,完成之前不能停止解析。
- 無法預(yù)判解析需要多少內(nèi)存答朋。
HTTP/2 有了幀济榨,處理協(xié)議的程序就能預(yù)先知道會收到什么,并且 HTTP/2 有表示幀長度的字段绿映。
幀結(jié)構(gòu)
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
由于 HTTP/2 是分幀的擒滑,請求和響應(yīng)都可以多路復(fù)用,有助于解決類似類似隊(duì)頭阻塞的問題叉弦。
幀類型
多路復(fù)用
在 HTTP/1.1 中丐一,如果客戶端想發(fā)送多個(gè)并行的請求,那么必須使用多個(gè) TCP 連接淹冰。
而 HTTP/2 的二進(jìn)制分幀層突破了這一限制库车,所有的請求和響應(yīng)都在同一個(gè) TCP 連接上發(fā)送:客戶端和服務(wù)器把 HTTP 消息分解成多個(gè)幀,然后亂序發(fā)送樱拴,最后在另一端再根據(jù)流 ID 重新組合起來柠衍。
這個(gè)機(jī)制為 HTTP 帶來了巨大的性能提升洋满,因?yàn)椋?/p>
- 可以并行交錯(cuò)地發(fā)送請求,請求之間互不影響珍坊;
- 可以并行交錯(cuò)地發(fā)送響應(yīng)牺勾,響應(yīng)之間互不干擾;
- 只使用一個(gè)連接即可并行發(fā)送多個(gè)請求和響應(yīng)阵漏;
- 消除不必要的延遲驻民,從而減少頁面加載的時(shí)間;
-
不必再為繞過 HTTP 1.x 限制而多做很多工作履怯;
多路復(fù)用
流
HTTP/2 規(guī)范對流的定義是:HTTP/2 連接上獨(dú)立的回还、雙向的幀序列交換。如果客戶端想要發(fā)出請求叹洲,它會開啟一個(gè)新流柠硕,然后服務(wù)器在這個(gè)流上回復(fù)。 由于有分幀运提,所以多個(gè)請求和響應(yīng)可以交錯(cuò)仅叫,而不會互相阻塞。流 ID 用來標(biāo)識幀所屬的流糙捺。
客戶端到服務(wù)器的 HTTP/2 連接建立后,通過發(fā)送 HEADERS 幀來啟動新的流笙隙。如果首部需要跨多個(gè)幀洪灯,可能還會發(fā)送 CONTINUATION 幀。該 HEADERS 幀可能來自請求或響應(yīng)竟痰。 后續(xù)流啟動的時(shí)候签钩,會發(fā)送一個(gè)帶有遞增流 ID 的新 HEADERS 幀。
消息
HTTP 消息泛指 HTTP 請求或響應(yīng)坏快,消息由一或多個(gè)幀組成铅檩,這些幀可以亂序發(fā)送,然后再根據(jù)每個(gè)幀首部的流 ID 重新組裝莽鸿。
一個(gè)消息至少由 HEADERS 幀(它初始化流)組成昧旨,并且可以另外包含 CONTINUATION 和 DATA 幀,以及其他的 HEADERS 幀祥得。
HTTP/1.1 的請求和響應(yīng)部分都分成消息首部和消息體兩部分级及;HTTP/2 的請求和響應(yīng)分成 HEADERS 幀和 DATA 幀乒疏。
優(yōu)先級
把 HTTP 消息分解為很多獨(dú)立的幀之后,就可以通過優(yōu)化這些幀的交錯(cuò)和傳輸順序饮焦,進(jìn)一步提升性能怕吴。
通過 HEADERS 幀和 PRIORITY 幀窍侧,客戶端可以明確地和服務(wù)器溝通它需要什么,以及它需要這些資源的順序转绷。具體來講伟件,服務(wù)器可以根據(jù)流的優(yōu)先級,控制資源分配(CPU暇咆、內(nèi)存锋爪、帶寬),而在響應(yīng)數(shù)據(jù)準(zhǔn)備好之后爸业,優(yōu)先將最高優(yōu)先級的幀發(fā)送給客戶端其骄。
流量控制
在同一個(gè) TCP 連接上傳輸多個(gè)數(shù)據(jù)流,就意味著要共享帶寬扯旷。標(biāo)定數(shù)據(jù)流的優(yōu)先級有助于按序交付拯爽,但只有優(yōu)先級還不足以確定多個(gè)數(shù)據(jù)流或多個(gè)連接間的資源分配。
為解決這個(gè)問題钧忽,HTTP/2 為數(shù)據(jù)流和連接的流量控制提供了一個(gè)簡單的機(jī)制:
- 流量控制基于每一跳進(jìn)行毯炮,而非端到端的控制;
- 流量控制基于 WINDOW_UPDATE 幀進(jìn)行耸黑,即接收方廣播自己準(zhǔn)備接收某個(gè)數(shù)據(jù)流的多少字節(jié)桃煎,以及對整個(gè)連接要接收多少字節(jié);
- 流量控制窗口大小通過 WINDOW_UPDATE 幀更新大刊,這個(gè)字段指定了流 ID 和窗口大小遞增值为迈;
- 流量控制有方向性,即接收方可能根據(jù)自己的情況為每個(gè)流乃至整個(gè)連接設(shè)置任意窗口大腥本葫辐;
- 流量控制可以由接收方禁用,包括針對個(gè)別的流和針對整個(gè)連接伴郁。
HTTP/2 連接建立之后耿战,客戶端與服務(wù)器交換 SETTINGS 幀,目的是設(shè)置雙向的流量控制窗口大小焊傅。除此之外剂陡,任何一端都可以選擇禁用個(gè)別流或整個(gè)連接的流量控制。
服務(wù)器推送
HTTP/2 新增的一個(gè)強(qiáng)大的新功能狐胎,就是服務(wù)器可以對一個(gè)客戶端請求發(fā)送多個(gè)響應(yīng)鹏倘。換句話說,除了對最初請求的響應(yīng)外顽爹,服務(wù)器還可以額外向客戶端推送資源纤泵,而無需客戶端明確地請求。
為什么需要這樣一個(gè)機(jī)制呢?通常的 Web 應(yīng)用都由幾十個(gè)資源組成捏题,客戶端需要分析服務(wù)器提供的文檔才能逐個(gè)找到它們玻褪。那為什么不讓服務(wù)器提前就把這些資源推送給客戶端,從而減少額外的時(shí)間延遲呢公荧?服務(wù)器已經(jīng)知道客戶端下一步要請求什么資源了带射,這時(shí)候服務(wù)器推送即可派上用場。
另外循狰,客戶端也可以拒絕服務(wù)器的推送窟社。
首部壓縮
HTTP/1.1 存在的一個(gè)問題就是臃腫的首部,HTTP/2 對這一問題進(jìn)行了改進(jìn)绪钥,可以對首部進(jìn)行壓縮灿里。 在一個(gè) Web 頁面中,一般都會包含大量的請求程腹,而其中有很多請求的首部往往有很多重復(fù)的部分匣吊。
例如有如下兩個(gè)請求:
: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
從上面兩個(gè)請求可以看出來,有很多數(shù)據(jù)都是重復(fù)的寸潦。如果可以把相同的首部存儲起來色鸳,僅發(fā)送它們之間不同的部分,就可以節(jié)省不少的流量见转,加快請求的時(shí)間命雀。
HTTP/2 在客戶端和服務(wù)器端使用“首部表”來跟蹤和存儲之前發(fā)送的鍵-值對,對于相同的數(shù)據(jù)斩箫,不再通過每次請求和響應(yīng)發(fā)送吏砂。
下面再來看一個(gè)簡化的例子,假設(shè)客戶端按順序發(fā)送如下請求首部:
Header1:foo
Header2:bar
Header3:bat
當(dāng)客戶端發(fā)送請求時(shí)校焦,它會根據(jù)首部值創(chuàng)建一張表:
如果服務(wù)器收到了請求,它會照樣創(chuàng)建一張表统倒。 當(dāng)客戶端發(fā)送下一個(gè)請求的時(shí)候寨典,如果首部相同,它可以直接發(fā)送這樣的首部塊:
62 63 64
服務(wù)器會查找先前建立的表格房匆,并把這些數(shù)字還原成索引對應(yīng)的完整首部耸成。
性能優(yōu)化
使用 HTTP/2 代替 HTTP/1.1,本身就是一種巨大的性能提升浴鸿。 這小節(jié)要聊的是在 HTTP/1.1 中的某些優(yōu)化手段井氢,在 HTTP/2 中是不必要的,可以取消的岳链。
取消合并資源
在 HTTP/1.1 中要把多個(gè)小資源合并成一個(gè)大資源花竞,從而減少請求。而在 HTTP/2 就不需要了掸哑,因?yàn)?HTTP/2 所有的請求都可以在一個(gè) TCP 連接發(fā)送约急。
取消域名拆分
取消域名拆分的理由同上零远,再多的 HTTP 請求都可以在一個(gè) TCP 連接上發(fā)送,所以不需要采取多個(gè)域名來突破瀏覽器 TCP 連接數(shù)限制這一規(guī)則了厌蔽。