HTTP

HTTP 基本概念
HTTP 是什么?
HTTP 是超文本傳輸協(xié)議,也就是HyperText Transfer Protocol。
能否詳細解釋「超文本傳輸協(xié)議」?
HTTP的名字「超文本協(xié)議傳輸」,它可以拆成三個部分:
超文本 傳輸 協(xié)議

1. 「協(xié)議」
在生活中,我們也能隨處可見「協(xié)議」,例如:
剛畢業(yè)時會簽一個「三方協(xié)議」; 找房子時會簽一個「租房協(xié)議」;

生活中的協(xié)議,本質上與計算機中的協(xié)議是相同的,協(xié)議的特點:
「協(xié)」字,代表的意思是必須有兩個以上的參與者。例如三方協(xié)議里的參與者有三個:你、公司、學校三個;租房協(xié)議里的參與者有兩個:你和房東。 「議」字,代表的意思是對參與者的一種行為約定和規(guī)范。例如三方協(xié)議里規(guī)定試用期期限、毀約金等;租房協(xié)議里規(guī)定租期期限、每月租金金額、違約如何處理等。
針對 HTTP 協(xié)議,我們可以這么理解。
HTTP 是一個用在計算機世界里的協(xié)議。它使用計算機能夠理解的語言確立了一種計算機之間交流通信的規(guī)范(兩個以上的參與者),以及相關的各種控制和錯誤處理方式(行為約定和規(guī)范)。
2. 「傳輸」
所謂的「傳輸」,很好理解,就是把一堆東西從 A 點搬到 B 點,或者從 B 點 搬到 A 點。
別輕視了這個簡單的動作,它至少包含兩項重要的信息。
HTTP 協(xié)議是一個雙向協(xié)議。
我們在上網(wǎng)沖浪時,瀏覽器是請求方 A ,百度網(wǎng)站就是應答方 B。雙方約定用 HTTP 協(xié)議來通信,于是瀏覽器把請求數(shù)據(jù)發(fā)送給網(wǎng)站,網(wǎng)站再把一些數(shù)據(jù)返回給瀏覽器,最后由瀏覽器渲染在屏幕,就可以看到圖片、視頻了。

數(shù)據(jù)雖然是在 A 和 B 之間傳輸,但允許中間有中轉或接力。
就好像第一排的同學想傳遞紙條給最后一排的同學,那么傳遞的過程中就需要經(jīng)過好多個同學(中間人),這樣的傳輸方式就從「A < --- > B」,變成了「A <-> N <-> M <-> B」。
而在 HTTP 里,需要中間人遵從 HTTP 協(xié)議,只要不打擾基本的數(shù)據(jù)傳輸,就可以添加任意額外的東西。
針對傳輸,我們可以進一步理解了 HTTP。
HTTP 是一個在計算機世界里專門用來在兩點之間傳輸數(shù)據(jù)的約定和規(guī)范。
3. 「超文本」
HTTP 傳輸?shù)膬?nèi)容是「超文本」。
我們先來理解「文本」,在互聯(lián)網(wǎng)早期的時候只是簡單的字符文字,但現(xiàn)在「文本」的涵義已經(jīng)可以擴展為圖片、視頻、壓縮包等,在 HTTP 眼里這些都算作「文本」。
再來理解「超文本」,它就是超越了普通文本的文本,它是文字、圖片、視頻等的混合體,最關鍵有超鏈接,能從一個超文本跳轉到另外一個超文本。
HTML 就是最常見的超文本了,它本身只是純文字文件,但內(nèi)部用很多標簽定義了圖片、視頻等的鏈接,再經(jīng)過瀏覽器的解釋,呈現(xiàn)給我們的就是一個文字、有畫面的網(wǎng)頁了。
OK,經(jīng)過了對 HTTP 里這三個名詞的詳細解釋,就可以給出比「超文本傳輸協(xié)議」這七個字更準確更有技術含量的答案:
HTTP 是一個在計算機世界里專門在「兩點」之間「傳輸」文字、圖片、音頻、視頻等「超文本」數(shù)據(jù)的「約定和規(guī)范」。
那「HTTP 是用于從互聯(lián)網(wǎng)服務器傳輸超文本到本地瀏覽器的協(xié)議 ,這種說法正確嗎?
這種說法是不正確的。因為也可以是「服務器< -- >服務器」,所以采用兩點之間的描述會更準確。
HTTP 常見的狀態(tài)碼有哪些?

1xx 類狀態(tài)碼屬于提示信息,是協(xié)議處理中的一種中間狀態(tài),實際用到的比較少。
2xx 類狀態(tài)碼表示服務器成功處理了客戶端的請求,也是我們最愿意看到的狀態(tài)。
「200 OK」是最常見的成功狀態(tài)碼,表示一切正常。如果是非
HEAD請求,服務器返回的響應頭都會有 body 數(shù)據(jù)。「204 No Content」也是常見的成功狀態(tài)碼,與 200 OK 基本相同,但響應頭沒有 body 數(shù)據(jù)。
「206 Partial Content」是應用于 HTTP 分塊下載或斷點續(xù)傳,表示響應返回的 body 數(shù)據(jù)并不是資源的全部,而是其中的一部分,也是服務器處理成功的狀態(tài)。
3xx 類狀態(tài)碼表示客戶端請求的資源發(fā)送了變動,需要客戶端用新的 URL 重新發(fā)送請求獲取資源,也就是重定向。
「301 Moved Permanently」表示永久重定向,說明請求的資源已經(jīng)不存在了,需改用新的 URL 再次訪問。
「302 Found」表示臨時重定向,說明請求的資源還在,但暫時需要用另一個 URL 來訪問。
301 和 302 都會在響應頭里使用字段 Location,指明后續(xù)要跳轉的 URL,瀏覽器會自動重定向新的 URL。
「304 Not Modified」不具有跳轉的含義,表示資源未修改,重定向已存在的緩沖文件,也稱緩存重定向,也就是告訴客戶端可以繼續(xù)使用緩存資源,用于緩存控制。
4xx 類狀態(tài)碼表示客戶端發(fā)送的報文有誤,服務器無法處理,也就是錯誤碼的含義。
「400 Bad Request」表示客戶端請求的報文有錯誤,但只是個籠統(tǒng)的錯誤。
「403 Forbidden」表示服務器禁止訪問資源,并不是客戶端的請求出錯。
「404 Not Found」表示請求的資源在服務器上不存在或未找到,所以無法提供給客戶端。
5xx 類狀態(tài)碼表示客戶端請求報文正確,但是服務器處理時內(nèi)部發(fā)生了錯誤,屬于服務器端的錯誤碼。
「500 Internal Server Error」與 400 類型,是個籠統(tǒng)通用的錯誤碼,服務器發(fā)生了什么錯誤,我們并不知道。
「501 Not Implemented」表示客戶端請求的功能還不支持,類似“即將開業(yè),敬請期待”的意思。
「502 Bad Gateway」通常是服務器作為網(wǎng)關或代理時返回的錯誤碼,表示服務器自身工作正常,訪問后端服務器發(fā)生了錯誤。
「503 Service Unavailable」表示服務器當前很忙,暫時無法響應服務器,類似“網(wǎng)絡服務正忙,請稍后重試”的意思。
HTTP 常見字段有哪些?
Host 字段
客戶端發(fā)送請求時,用來指定服務器的域名。

Host: www.A.com
有了 Host 字段,就可以將請求發(fā)往「同一臺」服務器上的不同網(wǎng)站。
Content-Length 字段
服務器在返回數(shù)據(jù)時,會有 Content-Length 字段,表明本次回應的數(shù)據(jù)長度。

Content-Length: 1000
如上面則是告訴瀏覽器,本次服務器回應的數(shù)據(jù)長度是 1000 個字節(jié),后面的字節(jié)就屬于下一個回應了。
Connection 字段
Connection 字段最常用于客戶端要求服務器使用 TCP 持久連接,以便其他請求復用。

HTTP/1.1 版本的默認連接都是持久連接,但為了兼容老版本的 HTTP,需要指定 Connection 首部字段的值為 Keep-Alive。
Connection: keep-alive
一個可以復用的 TCP 連接就建立了,直到客戶端或服務器主動關閉連接。但是,這不是標準字段。
Content-Type 字段
Content-Type 字段用于服務器回應時,告訴客戶端,本次數(shù)據(jù)是什么格式。

Content-Type: text/html; charset=utf-8
上面的類型表明,發(fā)送的是網(wǎng)頁,而且編碼是UTF-8。
客戶端請求的時候,可以使用 Accept 字段聲明自己可以接受哪些數(shù)據(jù)格式。
Accept: */*
上面代碼中,客戶端聲明自己可以接受任何格式的數(shù)據(jù)。
Content-Encoding 字段
Content-Encoding 字段說明數(shù)據(jù)的壓縮方法。表示服務器返回的數(shù)據(jù)使用了什么壓縮格式

Content-Encoding: gzip
上面表示服務器返回的數(shù)據(jù)采用了 gzip 方式壓縮,告知客戶端需要用此方式解壓。
客戶端在請求時,用 Accept-Encoding 字段說明自己可以接受哪些壓縮方法。
Accept-Encoding: gzip, deflate
GET 與 POST
GET 和 POST 有什么區(qū)別?
根據(jù) RFC 規(guī)范,GET 的語義是從服務器獲取指定的資源,這個資源可以是靜態(tài)的文本、頁面、圖片視頻等。GET 請求的參數(shù)位置一般是寫在 URL 中,URL 規(guī)定只能支持 ASCII,所以 GET 請求的參數(shù)只允許 ASCII 字符 ,而且瀏覽器會對 URL 的長度有限制(HTTP協(xié)議本身對 URL長度并沒有做任何規(guī)定)。
比如,你打開我的文章,瀏覽器就會發(fā)送 GET 請求給服務器,服務器就會返回文章的所有文字及資源。

根據(jù) RFC 規(guī)范,POST 的語義是根據(jù)請求負荷(報文body)對指定的資源做出處理,具體的處理方式視資源類型而不同。POST 請求攜帶數(shù)據(jù)的位置一般是寫在報文 body 中, body 中的數(shù)據(jù)可以是任意格式的數(shù)據(jù),只要客戶端與服務端協(xié)商好即可,而且瀏覽器不會對 body 大小做限制。
比如,你在我文章底部,敲入了留言后點擊「提交」(暗示你們留言),瀏覽器就會執(zhí)行一次 POST 請求,把你的留言文字放進了報文 body 里,然后拼接好 POST 請求頭,通過 TCP 協(xié)議發(fā)送給服務器。

GET 和 POST 方法都是安全和冪等的嗎?
先說明下安全和冪等的概念:
在 HTTP 協(xié)議里,所謂的「安全」是指請求方法不會「破壞」服務器上的資源。 所謂的「冪等」,意思是多次執(zhí)行相同的操作,結果都是「相同」的。
如果從 RFC 規(guī)范定義的語義來看:
GET 方法就是安全且冪等的,因為它是「只讀」操作,無論操作多少次,服務器上的數(shù)據(jù)都是安全的,且每次的結果都是相同的。所以,可以對 GET 請求的數(shù)據(jù)做緩存,這個緩存可以做到瀏覽器本身上(徹底避免瀏覽器發(fā)請求),也可以做到代理上(如nginx),而且在瀏覽器中 GET 請求可以保存位書簽。 POST 因為是「新增或提交數(shù)據(jù)」的操作,會修改服務器上的資源,所以是不安全的,且多次提交數(shù)據(jù)就會創(chuàng)建多個資源,所以不是冪等的。所以,瀏覽器一般不會緩存 POST 請求,也不能把 POST 請求保存為書簽。
做個簡要的小結。
GET 的語義是請求獲取指定的資源。GET 方法是安全、冪等、可被緩存的。
POST 的語義是根據(jù)請求負荷(報文主體)對指定的資源做出處理,具體的處理方式視資源類型而不同。POST 不安全,不冪等,(大部分實現(xiàn))不可緩存。
注意, 上面是從 RFC 規(guī)范定義的語義來分析的。
但是實際過程中,開發(fā)者不一定會按照 RFC 規(guī)范定義的語義來實現(xiàn) GET 和 POST 方法。比如:
可以用 GET 方法實現(xiàn)新增或刪除數(shù)據(jù)的請求,這樣實現(xiàn)的 GET 方法自然就不是安全和冪等。 可以用 POST 方法實現(xiàn)查詢數(shù)據(jù)的請求,這樣實現(xiàn)的 POST 方法自然就是安全和冪等。
曾經(jīng)有個笑話,有人寫了個博客,刪除博客用的是GET請求,他覺得沒人訪問就連鑒權都沒做。然后Google服務器爬蟲爬了一遍,他所有博文就沒了。。。
如果「安全」放入概念是指信息是否會被泄漏的話,雖然 POST 用 body 傳輸數(shù)據(jù),而 GET 用 URL 傳輸,這樣數(shù)據(jù)會在瀏覽器地址攔容易看到,但是并不能說 GET 不如 POST 安全的。
因為 HTTP 傳輸?shù)膬?nèi)容都是明文的,雖然在瀏覽器地址攔看不到 POST 提交的 body 數(shù)據(jù),但是只要抓個包就都能看到了。
所以,要避免傳輸過程中數(shù)據(jù)被竊取,就要使用 HTTPS 協(xié)議,這樣所有 HTTP 的數(shù)據(jù)都會被加密傳輸。
GET 請求可以帶 body 嗎?
RFC 規(guī)范并沒有規(guī)定 GET 請求不能帶 body 的。理論上,任何請求都可以帶 body 的。只是因為 RFC 規(guī)范定義的 GET 請求是獲取資源,所以根據(jù)這個語義不需要用到 body。
另外,URL 中的查詢參數(shù)也不是 GET 所獨有的,POST 請求的 URL 中也可以有參數(shù)的。
HTTP 緩存技術
HTTP 緩存有哪些實現(xiàn)方式?
對于一些具有重復性的 HTTP 請求,比如每次請求得到的數(shù)據(jù)都一樣的,我們可以把這對「請求-響應」的數(shù)據(jù)都緩存在本地,那么下次就直接讀取本地的數(shù)據(jù),不必在通過網(wǎng)絡獲取服務器的響應了,這樣的話 HTTP/1.1 的性能肯定肉眼可見的提升。
所以,避免發(fā)送 HTTP 請求的方法就是通過緩存技術,HTTP 設計者早在之前就考慮到了這點,因此 HTTP 協(xié)議的頭部有不少是針對緩存的字段。
HTTP 緩存有兩種實現(xiàn)方式,分別是強制緩存和協(xié)商緩存。
什么是強制緩存?
強緩存指的是只要瀏覽器判斷緩存沒有過期,則直接使用瀏覽器的本地緩存,決定是否使用緩存的主動性在于瀏覽器這邊。
如下圖中,返回的是 200 狀態(tài)碼,但在 size 項中標識的是 from disk cache,就是使用了強制緩存。

強緩存是利用下面這兩個 HTTP 響應頭部(Response Header)字段實現(xiàn)的,它們都用來表示資源在客戶端緩存的有效期:
Cache-Control, 是一個相對時間;Expires,是一個絕對時間;
如果 HTTP 響應頭部同時有 Cache-Control 和 Expires 字段的話,Cache-Control的優(yōu)先級高于 Expires 。
Cache-control 選項更多一些,設置更加精細,所以建議使用 Cache-Control 來實現(xiàn)強緩存。具體的實現(xiàn)流程如下:
當瀏覽器第一次請求訪問服務器資源時,服務器會在返回這個資源的同時,在 Response 頭部加上 Cache-Control,Cache-Control 中設置了過期時間大??; 瀏覽器再次請求訪問服務器中的該資源時,會先通過請求資源的時間與 Cache-Control 中設置的過期時間大小,來計算出該資源是否過期,如果沒有,則使用該緩存,否則重新請求服務器; 服務器再次收到請求后,會再次更新 Response 頭部的 Cache-Control。
什么是協(xié)商緩存?
當我們在瀏覽器使用開發(fā)者工具的時候,你可能會看到過某些請求的響應碼是 304,這個是告訴瀏覽器可以使用本地緩存的資源,通常這種通過服務端告知客戶端是否可以使用緩存的方式被稱為協(xié)商緩存。

上圖就是一個協(xié)商緩存的過程,所以協(xié)商緩存就是與服務端協(xié)商之后,通過協(xié)商結果來判斷是否使用本地緩存。
協(xié)商緩存可以基于兩種頭部來實現(xiàn)。
第一種:請求頭部中的 If-Modified-Since 字段與響應頭部中的 Last-Modified 字段實現(xiàn),這兩個字段的意思是:
響應頭部中的 Last-Modified:標示這個響應資源的最后修改時間;請求頭部中的 If-Modified-Since:當資源過期了,發(fā)現(xiàn)響應頭中具有 Last-Modified 聲明,則再次發(fā)起請求的時候帶上 Last-Modified 的時間,服務器收到請求后發(fā)現(xiàn)有 If-Modified-Since 則與被請求資源的最后修改時間進行對比(Last-Modified),如果最后修改時間較新(大),說明資源又被改過,則返回最新資源,HTTP 200 OK;如果最后修改時間較舊(?。?,說明資源無新修改,響應 HTTP 304 走緩存。
第二種:請求頭部中的 If-None-Match 字段與響應頭部中的 ETag 字段,這兩個字段的意思是:
響應頭部中 Etag:唯一標識響應資源;請求頭部中的 If-None-Match:當資源過期時,瀏覽器發(fā)現(xiàn)響應頭里有 Etag,則再次向服務器發(fā)起請求時,會將請求頭If-None-Match 值設置為 Etag 的值。服務器收到請求后進行比對,如果資源沒有變化返回 304,如果資源變化了返回 200。
第一種實現(xiàn)方式是基于時間實現(xiàn)的,第二種實現(xiàn)方式是基于一個唯一標識實現(xiàn)的,相對來說后者可以更加準確地判斷文件內(nèi)容是否被修改,避免由于時間篡改導致的不可靠問題。
如果 HTTP 響應頭部同時有 Etag 和 Last-Modified 字段的時候, Etag 的優(yōu)先級更高,也就是先會判斷 Etag 是否變化了,如果 Etag 沒有變化,然后再看 Last-Modified。
注意,協(xié)商緩存這兩個字段都需要配合強制緩存中 Cache-control 字段來使用,只有在未能命中強制緩存的時候,才能發(fā)起帶有協(xié)商緩存字段的請求。

使用 ETag 字段實現(xiàn)的協(xié)商緩存的過程如下;
當瀏覽器第一次請求訪問服務器資源時,服務器會在返回這個資源的同時,在 Response 頭部加上 ETag 唯一標識,這個唯一標識的值是根據(jù)當前請求的資源生成的; 當瀏覽器再次請求訪問服務器中的該資源時,首先會先檢查強制緩存是否過期,如果沒有過期,則直接使用本地緩存;如果緩存過期了,會在 Request 頭部加上 If-None-Match 字段,該字段的值就是 ETag 唯一標識; 服務器再次收到請求后,會根據(jù)請求中的 If-None-Match 值與當前請求的資源生成的唯一標識進行比較: 如果值相等,則返回 304 Not Modified,不會返回資源; 如果不相等,則返回 200 狀態(tài)碼和返回資源,并在 Response 頭部加上新的 ETag 唯一標識; 如果瀏覽器收到 304 的請求響應狀態(tài)碼,則會從本地緩存中加載資源,否則更新資源。
HTTP 特性
HTTP(1.1) 的優(yōu)點有哪些?
HTTP 最凸出的優(yōu)點是「簡單、靈活和易于擴展、應用廣泛和跨平臺」。
1. 簡單
HTTP 基本的報文格式就是 header + body,頭部信息也是 key-value 簡單文本的形式,易于理解,降低了學習和使用的門檻。
2. 靈活和易于擴展
HTTP協(xié)議里的各類請求方法、URI/URL、狀態(tài)碼、頭字段等每個組成要求都沒有被固定死,都允許開發(fā)人員自定義和擴充。
同時 HTTP 由于是工作在應用層( OSI 第七層),則它下層可以隨意變化。
HTTPS 也就是在 HTTP 與 TCP 層之間增加了 SSL/TLS 安全傳輸層,HTTP/3 甚至把 TCP 層換成了基于 UDP 的 QUIC。
3. 應用廣泛和跨平臺
互聯(lián)網(wǎng)發(fā)展至今,HTTP 的應用范圍非常的廣泛,從臺式機的瀏覽器到手機上的各種 APP,從看新聞、刷貼吧到購物、理財、吃雞,HTTP 的應用遍地開花,同時天然具有跨平臺的優(yōu)越性。
HTTP(1.1) 的缺點有哪些?
HTTP 協(xié)議里有優(yōu)缺點一體的雙刃劍,分別是「無狀態(tài)、明文傳輸」,同時還有一大缺點「不安全」。
1. 無狀態(tài)雙刃劍
無狀態(tài)的好處,因為服務器不會去記憶 HTTP 的狀態(tài),所以不需要額外的資源來記錄狀態(tài)信息,這能減輕服務器的負擔,能夠把更多的 CPU 和內(nèi)存用來對外提供服務。
無狀態(tài)的壞處,既然服務器沒有記憶能力,它在完成有關聯(lián)性的操作時會非常麻煩。
例如登錄->添加購物車->下單->結算->支付,這系列操作都要知道用戶的身份才行。但服務器不知道這些請求是有關聯(lián)的,每次都要問一遍身份信息。
這樣每操作一次,都要驗證信息,這樣的購物體驗還能愉快嗎?別問,問就是酸爽!
對于無狀態(tài)的問題,解法方案有很多種,其中比較簡單的方式用 Cookie 技術。
Cookie 通過在請求和響應報文中寫入 Cookie 信息來控制客戶端的狀態(tài)。
相當于,在客戶端第一次請求后,服務器會下發(fā)一個裝有客戶信息的「小貼紙」,后續(xù)客戶端請求服務器的時候,帶上「小貼紙」,服務器就能認得了了,

2. 明文傳輸雙刃劍
明文意味著在傳輸過程中的信息,是可方便閱讀的,通過瀏覽器的 F12 控制臺或 Wireshark 抓包都可以直接肉眼查看,為我們調(diào)試工作帶了極大的便利性。
但是這正是這樣,HTTP 的所有信息都暴露在了光天化日下,相當于信息裸奔。在傳輸?shù)穆L的過程中,信息的內(nèi)容都毫無隱私可言,很容易就能被竊取,如果里面有你的賬號密碼信息,那你號沒了。

3. 不安全
HTTP 比較嚴重的缺點就是不安全:
通信使用明文(不加密),內(nèi)容可能會被竊聽。比如,賬號信息容易泄漏,那你號沒了。 不驗證通信方的身份,因此有可能遭遇偽裝。比如,訪問假的淘寶、拼多多,那你錢沒了。 無法證明報文的完整性,所以有可能已遭篡改。比如,網(wǎng)頁上植入垃圾廣告,視覺污染,眼沒了。
HTTP 的安全問題,可以用 HTTPS 的方式解決,也就是通過引入 SSL/TLS 層,使得在安全上達到了極致。
HTTP/1.1 的性能如何?
HTTP 協(xié)議是基于 TCP/IP,并且使用了「請求 - 應答」的通信模式,所以性能的關鍵就在這兩點里。
1. 長連接
早期 HTTP/1.0 性能上的一個很大的問題,那就是每發(fā)起一個請求,都要新建一次 TCP 連接(三次握手),而且是串行請求,做了無謂的 TCP 連接建立和斷開,增加了通信開銷。
為了解決上述 TCP 連接問題,HTTP/1.1 提出了長連接的通信方式,也叫持久連接。這種方式的好處在于減少了 TCP 連接的重復建立和斷開所造成的額外開銷,減輕了服務器端的負載。
持久連接的特點是,只要任意一端沒有明確提出斷開連接,則保持 TCP 連接狀態(tài)。

當然,如果某個 HTTP 長連接超過一定時間沒有任何數(shù)據(jù)交互,服務端就會主動斷開這個連接。
2. 管道網(wǎng)絡傳輸
HTTP/1.1 采用了長連接的方式,這使得管道(pipeline)網(wǎng)絡傳輸成為了可能。
即可在同一個 TCP 連接里面,客戶端可以發(fā)起多個請求,只要第一個請求發(fā)出去了,不必等其回來,就可以發(fā)第二個請求出去,可以減少整體的響應時間。
舉例來說,客戶端需要請求兩個資源。以前的做法是,在同一個 TCP 連接里面,先發(fā)送 A 請求,然后等待服務器做出回應,收到后再發(fā)出 B 請求。那么,管道機制則是允許瀏覽器同時發(fā)出 A 請求和 B 請求,如下圖:

但是服務器必須按照接收請求的順序發(fā)送對這些管道化請求的響應。
注意,是按照服務端收到的請求順序響應,并不管哪個請求是先發(fā)送的,假設客戶端先發(fā)送 A 請求,后發(fā)送 B 請求,如果服務端先收到 B 請求,就先響應 B 請求,然后再響應 A 請求,但是假設處理 B 請求的時候,耗時比較長,那么請求 A 的響應就會被阻塞,這稱為「隊頭堵塞」。
所以,HTTP/1.1 管道解決了請求的隊頭阻塞,但是沒有解決響應的隊頭阻塞。
3. 隊頭阻塞
「請求 - 應答」的模式加劇了 HTTP 的性能問題。
因為當順序發(fā)送的請求序列中的一個請求因為某種原因被阻塞時,在后面排隊的所有請求也一同被阻塞了,會招致客戶端一直請求不到數(shù)據(jù),這也就是「隊頭阻塞」,好比上班的路上塞車。

總之 HTTP/1.1 的性能一般般,后續(xù)的 HTTP/2 和 HTTP/3 就是在優(yōu)化 HTTP 的性能。
HTTP 與 HTTPS
HTTP 與 HTTPS 有哪些區(qū)別?
HTTP 是超文本傳輸協(xié)議,信息是明文傳輸,存在安全風險的問題。HTTPS 則解決 HTTP 不安全的缺陷,在 TCP 和 HTTP 網(wǎng)絡層之間加入了 SSL/TLS 安全協(xié)議,使得報文能夠加密傳輸。 HTTP 連接建立相對簡單, TCP 三次握手之后便可進行 HTTP 的報文傳輸。而 HTTPS 在 TCP 三次握手之后,還需進行 SSL/TLS 的握手過程,才可進入加密報文傳輸。 HTTP 的端口號是 80,HTTPS 的端口號是 443。 HTTPS 協(xié)議需要向 CA(證書權威機構)申請數(shù)字證書,來保證服務器的身份是可信的。
HTTPS 解決了 HTTP 的哪些問題?
HTTP 由于是明文傳輸,所以安全上存在以下三個風險:
竊聽風險,比如通信鏈路上可以獲取通信內(nèi)容,用戶號容易沒。 篡改風險,比如強制植入垃圾廣告,視覺污染,用戶眼容易瞎。 冒充風險,比如冒充淘寶網(wǎng)站,用戶錢容易沒。

HTTPS 在 HTTP 與 TCP 層之間加入了 SSL/TLS 協(xié)議,可以很好的解決了上述的風險:
信息加密:交互信息無法被竊取,但你的號會因為「自身忘記」賬號而沒。 校驗機制:無法篡改通信內(nèi)容,篡改了就不能正常顯示,但百度「競價排名」依然可以搜索垃圾廣告。 身份證書:證明淘寶是真的淘寶網(wǎng),但你的錢還是會因為「剁手」而沒。
可見,只要自身不做「惡」,SSL/TLS 協(xié)議是能保證通信是安全的。
HTTPS 是如何解決上面的三個風險的?
混合加密的方式實現(xiàn)信息的機密性,解決了竊聽的風險。 摘要算法的方式來實現(xiàn)完整性,它能夠為數(shù)據(jù)生成獨一無二的「指紋」,指紋用于校驗數(shù)據(jù)的完整性,解決了篡改的風險。 將服務器公鑰放入到數(shù)字證書中,解決了冒充的風險。
1. 混合加密
通過混合加密的方式可以保證信息的機密性,解決了竊聽的風險。

HTTPS 采用的是對稱加密和非對稱加密結合的「混合加密」方式:
在通信建立前采用非對稱加密的方式交換「會話秘鑰」,后續(xù)就不再使用非對稱加密。 在通信過程中全部使用對稱加密的「會話秘鑰」的方式加密明文數(shù)據(jù)。
采用「混合加密」的方式的原因:
對稱加密只使用一個密鑰,運算速度快,密鑰必須保密,無法做到安全的密鑰交換。 非對稱加密使用兩個密鑰:公鑰和私鑰,公鑰可以任意分發(fā)而私鑰保密,解決了密鑰交換問題但速度慢。
2. 摘要算法
摘要算法用來實現(xiàn)完整性,能夠為數(shù)據(jù)生成獨一無二的「指紋」,用于校驗數(shù)據(jù)的完整性,解決了篡改的風險。

客戶端在發(fā)送明文之前會通過摘要算法算出明文的「指紋」,發(fā)送的時候把「指紋 + 明文」一同加密成密文后,發(fā)送給服務器,服務器解密后,用相同的摘要算法算出發(fā)送過來的明文,通過比較客戶端攜帶的「指紋」和當前算出的「指紋」做比較,若「指紋」相同,說明數(shù)據(jù)是完整的。
3. 數(shù)字證書
客戶端先向服務器端索要公鑰,然后用公鑰加密信息,服務器收到密文后,用自己的私鑰解密。
這就存在些問題,如何保證公鑰不被篡改和信任度?
所以這里就需要借助第三方權威機構 CA (數(shù)字證書認證機構),將服務器公鑰放在數(shù)字證書(由數(shù)字證書認證機構頒發(fā))中,只要證書是可信的,公鑰就是可信的。

通過數(shù)字證書的方式保證服務器公鑰的身份,解決冒充的風險。
HTTPS 是如何建立連接的?其間交互了什么?
SSL/TLS 協(xié)議基本流程:
客戶端向服務器索要并驗證服務器的公鑰。 雙方協(xié)商生產(chǎn)「會話秘鑰」。 雙方采用「會話秘鑰」進行加密通信。
前兩步也就是 SSL/TLS 的建立過程,也就是 TLS 握手階段。
SSL/TLS 的「握手階段」涉及四次通信,可見下圖:

SSL/TLS 協(xié)議建立的詳細流程:
1. ClientHello
首先,由客戶端向服務器發(fā)起加密通信請求,也就是 ClientHello 請求。
在這一步,客戶端主要向服務器發(fā)送以下信息:
(1)客戶端支持的 SSL/TLS 協(xié)議版本,如 TLS 1.2 版本。
(2)客戶端生產(chǎn)的隨機數(shù)(Client Random),后面用于生成「會話秘鑰」條件之一。
(3)客戶端支持的密碼套件列表,如 RSA 加密算法。
2. SeverHello
服務器收到客戶端請求后,向客戶端發(fā)出響應,也就是 SeverHello。服務器回應的內(nèi)容有如下內(nèi)容:
(1)確認 SSL/ TLS 協(xié)議版本,如果瀏覽器不支持,則關閉加密通信。
(2)服務器生產(chǎn)的隨機數(shù)(Server Random),也是后面用于生產(chǎn)「會話秘鑰」條件之一。
(3)確認的密碼套件列表,如 RSA 加密算法。
(4)服務器的數(shù)字證書。
3.客戶端回應
客戶端收到服務器的回應之后,首先通過瀏覽器或者操作系統(tǒng)中的 CA 公鑰,確認服務器的數(shù)字證書的真實性。
如果證書沒有問題,客戶端會從數(shù)字證書中取出服務器的公鑰,然后使用它加密報文,向服務器發(fā)送如下信息:
(1)一個隨機數(shù)(pre-master key)。該隨機數(shù)會被服務器公鑰加密。
(2)加密通信算法改變通知,表示隨后的信息都將用「會話秘鑰」加密通信。
(3)客戶端握手結束通知,表示客戶端的握手階段已經(jīng)結束。這一項同時把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個摘要,用來供服務端校驗。
上面第一項的隨機數(shù)是整個握手階段的第三個隨機數(shù),會發(fā)給服務端,所以這個隨機數(shù)客戶端和服務端都是一樣的。
服務器和客戶端有了這三個隨機數(shù)(Client Random、Server Random、pre-master key),接著就用雙方協(xié)商的加密算法,各自生成本次通信的「會話秘鑰」。
4. 服務器的最后回應
服務器收到客戶端的第三個隨機數(shù)(pre-master key)之后,通過協(xié)商的加密算法,計算出本次通信的「會話秘鑰」。
然后,向客戶端發(fā)送最后的信息:
(1)加密通信算法改變通知,表示隨后的信息都將用「會話秘鑰」加密通信。
(2)服務器握手結束通知,表示服務器的握手階段已經(jīng)結束。這一項同時把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個摘要,用來供客戶端校驗。
至此,整個 SSL/TLS 的握手階段全部結束。接下來,客戶端與服務器進入加密通信,就完全是使用普通的 HTTP 協(xié)議,只不過用「會話秘鑰」加密內(nèi)容。
HTTP/1.1、HTTP/2、HTTP/3 演變
HTTP/1.1 相比 HTTP/1.0 提高了什么性能?
HTTP/1.1 相比 HTTP/1.0 性能上的改進:
使用 TCP 長連接的方式改善了 HTTP/1.0 短連接造成的性能開銷。 支持管道(pipeline)網(wǎng)絡傳輸,只要第一個請求發(fā)出去了,不必等其回來,就可以發(fā)第二個請求出去,可以減少整體的響應時間。
但 HTTP/1.1 還是有性能瓶頸:
請求 / 響應頭部(Header)未經(jīng)壓縮就發(fā)送,首部信息越多延遲越大。只能壓縮 Body的部分;發(fā)送冗長的首部。每次互相發(fā)送相同的首部造成的浪費較多; 服務器是按請求的順序響應的,如果服務器響應慢,會招致客戶端一直請求不到數(shù)據(jù),也就是隊頭阻塞; 沒有請求優(yōu)先級控制; 請求只能從客戶端開始,服務器只能被動響應。
HTTP/2 做了什么優(yōu)化?
HTTP/2 協(xié)議是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。

那 HTTP/2 相比 HTTP/1.1 性能上的改進:
1. 頭部壓縮
HTTP/2 會壓縮頭(Header)如果你同時發(fā)出多個請求,他們的頭是一樣的或是相似的,那么,協(xié)議會幫你消除重復的部分。
這就是所謂的 HPACK 算法:在客戶端和服務器同時維護一張頭信息表,所有字段都會存入這個表,生成一個索引號,以后就不發(fā)送同樣字段了,只發(fā)送索引號,這樣就提高速度了。
2. 二進制格式
HTTP/2 不再像 HTTP/1.1 里的純文本形式的報文,而是全面采用了二進制格式,頭信息和數(shù)據(jù)體都是二進制,并且統(tǒng)稱為幀(frame):頭信息幀(Headers Frame)和數(shù)據(jù)幀(Data Frame)。

這樣雖然對人不友好,但是對計算機非常友好,因為計算機只懂二進制,那么收到報文后,無需再將明文的報文轉成二進制,而是直接解析二進制報文,這增加了數(shù)據(jù)傳輸?shù)男?/strong>。
比如狀態(tài)碼 200 ,在 HTTP/1.1 是用 '2''0''0' 三個字符來表示(二進制:110010 110000 110000),如圖:

在 HTTP/2 是用數(shù)字 200 表示(二進制:11001000),如圖:

3. 數(shù)據(jù)流
HTTP/2 的數(shù)據(jù)包不是按順序發(fā)送的,同一個連接里面連續(xù)的數(shù)據(jù)包,可能屬于不同的回應。因此,必須要對數(shù)據(jù)包做標記,指出它屬于哪個回應。
在 HTTP/2 中每個請求或相應的所有數(shù)據(jù)包,稱為一個數(shù)據(jù)流(Stream)。每個數(shù)據(jù)流都標記著一個獨一無二的編號(Stream ID),不同 Stream 的幀是可以亂序發(fā)送的(因此可以并發(fā)不同的 Stream ),因為每個幀的頭部會攜帶 Stream ID 信息,所以接收端可以通過 Stream ID 有序組裝成 HTTP 消息
客戶端和服務器雙方都可以建立 Stream, Stream ID 也是有區(qū)別的,客戶端建立的 Stream 必須是奇數(shù)號,而服務器建立的 Stream 必須是偶數(shù)號。

客戶端還可以指定數(shù)據(jù)流的優(yōu)先級。優(yōu)先級高的請求,服務器就先響應該請求。
4. 多路復用
HTTP/2 是可以在一個連接中并發(fā)多個請求或回應,而不用按照順序一一對應。
移除了 HTTP/1.1 中的串行請求,不需要排隊等待,也就不會再出現(xiàn)「隊頭阻塞」問題,降低了延遲,大幅度提高了連接的利用率。
舉例來說,在一個 TCP 連接里,服務器收到了客戶端 A 和 B 的兩個請求,如果發(fā)現(xiàn) A 處理過程非常耗時,于是就回應 A 請求已經(jīng)處理好的部分,接著回應 B 請求,完成后,再回應 A 請求剩下的部分。

5. 服務器推送
HTTP/2 還在一定程度上改善了傳統(tǒng)的「請求 - 應答」工作模式,服務不再是被動地響應,也可以主動向客戶端發(fā)送消息。
比如,客戶端通過 HTTP/1.1 請求從服務器那獲取到了 HTML 文件,而 HTML 可能還需要依賴 CSS 來渲染頁面,這時客戶端還要再發(fā)起獲取 CSS 文件的請求,需要兩次消息往返,如下圖左邊部分:

如上圖右邊部分,在 HTTP/2 中,客戶端在訪問 HTML 時,服務器可以直接主動推送 CSS 文件,減少了消息傳遞的次數(shù)。
HTTP/2 有什么缺陷?
HTTP/2 通過 Stream 的并發(fā)能力,解決了 HTTP/1 隊頭阻塞的問題,看似很完美了,但是 HTTP/2 還是存在“隊頭阻塞”的問題,只不過問題不是在 HTTP 這一層面,而是在 TCP 這一層。
HTTP/2 是基于 TCP 協(xié)議來傳輸數(shù)據(jù)的,TCP 是字節(jié)流協(xié)議,TCP 層必須保證收到的字節(jié)數(shù)據(jù)是完整且連續(xù)的,這樣內(nèi)核才會將緩沖區(qū)里的數(shù)據(jù)返回給 HTTP 應用,那么當「前 1 個字節(jié)數(shù)據(jù)」沒有到達時,后收到的字節(jié)數(shù)據(jù)只能存放在內(nèi)核緩沖區(qū)里,只有等到這 1 個字節(jié)數(shù)據(jù)到達時,HTTP/2 應用層才能從內(nèi)核中拿到數(shù)據(jù),這就是 HTTP/2 隊頭阻塞問題。
舉個例子,如下圖:

圖中發(fā)送方發(fā)送了很多個 packet,每個 packet 都有自己的序號,你可以認為是 TCP 的序列號,其中 packet 3 在網(wǎng)絡中丟失了,即使 packet 4-6 被接收方收到后,由于內(nèi)核中的 TCP 數(shù)據(jù)不是連續(xù)的,于是接收方的應用層就無法從內(nèi)核中讀取到,只有等到 packet 3 重傳后,接收方的應用層才可以從內(nèi)核中讀取到數(shù)據(jù),這就是 HTTP/2 的隊頭阻塞問題,是在 TCP 層面發(fā)生的。
所以,一旦發(fā)生了丟包現(xiàn)象,就會觸發(fā) TCP 的重傳機制,這樣在一個 TCP 連接中的所有的 HTTP 請求都必須等待這個丟了的包被重傳回來。
HTTP/3 做了哪些優(yōu)化?
前面我們知道了 HTTP/1.1 和 HTTP/2 都有隊頭阻塞的問題:
HTTP/1.1 中的管道( pipeline)雖然解決了請求的隊頭阻塞,但是沒有解決響應的隊頭阻塞,因為服務端需要按順序響應收到的請求,如果服務端處理某個請求消耗的時間比較長,那么只能等相應完這個請求后, 才能處理下一個請求,這屬于 HTTP 層隊頭阻塞。 HTTP/2 雖然通過多個請求復用一個 TCP 連接解決了 HTTP 的隊頭阻塞 ,但是一旦發(fā)生丟包,就會阻塞住所有的 HTTP 請求,這屬于 TCP 層隊頭阻塞。
HTTP/2 隊頭阻塞的問題是因為 TCP,所以 HTTP/3 把 HTTP 下層的 TCP 協(xié)議改成了 UDP!

UDP 發(fā)生是不管順序,也不管丟包的,所以不會出現(xiàn)像 HTTP/2 隊頭阻塞的問題
大家都知道 UDP 是不可靠傳輸?shù)?,但基?UDP 的 QUIC 協(xié)議 可以實現(xiàn)類似 TCP 的可靠性傳輸。
QUIC 有以下 3 個特點。
1、無隊頭阻塞
QUIC 協(xié)議也有類似 HTTP/2 Stream 與多路復用的概念,也是可以在同一條連接上并發(fā)傳輸多個 Stream,Stream 可以認為就是一條 HTTP 請求。
QUIC 有自己的一套機制可以保證傳輸?shù)目煽啃缘摹?strong style="line-height: 1.75em;color: rgb(48, 79, 254);">當某個流發(fā)生丟包時,只會阻塞這個流,其他流不會受到影響,因此不存在隊頭阻塞問題。這與 HTTP/2 不同,HTTP/2 只要某個流中的數(shù)據(jù)包丟失了,其他流也會因此受影響。
所以,QUIC 連接上的多個 Stream 之間并沒有依賴,都是獨立的,某個流發(fā)生丟包了,只會影響該流,其他流不受影響。

2、更快的連接建立
對于 HTTP/1 和 HTTP/2 協(xié)議,TCP 和 TLS 是分層的,分別屬于內(nèi)核實現(xiàn)的傳輸層、openssl 庫實現(xiàn)的表示層,因此它們難以合并在一起,需要分批次來握手,先 TCP 握手,再 TLS 握手。
HTTP/3 在傳輸數(shù)據(jù)前雖然需要 QUIC 協(xié)議握手,這個握手過程只需要 1 RTT,握手的目的是為確認雙方的「連接 ID」,連接遷移就是基于連接 ID 實現(xiàn)的。
但是 HTTP/3 的 QUIC 協(xié)議并不是與 TLS 分層,而是QUIC 內(nèi)部包含了 TLS,它在自己的幀會攜帶 TLS 里的“記錄”,再加上 QUIC 使用的是 TLS/1.3,因此僅需 1 個 RTT 就可以「同時」完成建立連接與密鑰協(xié)商,如下圖:

甚至,在第二次連接的時候,應用數(shù)據(jù)包可以和 QUIC 握手信息(連接信息 + TLS 信息)一起發(fā)送,達到 0-RTT 的效果。
3、連接遷移
基于 TCP 傳輸協(xié)議的 HTTP 協(xié)議,由于是通過四元組(源 IP、源端口、目的 IP、目的端口)確定一條 TCP 連接,那么當移動設備的網(wǎng)絡從 4G 切換到 WIFI 時,意味著 IP 地址變化了,那么就必須要斷開連接,然后重新建立連接。而建立連接的過程包含 TCP 三次握手和 TLS 四次握手的時延,以及 TCP 慢啟動的減速過程,給用戶的感覺就是網(wǎng)絡突然卡頓了一下,因此連接的遷移成本是很高的。
而 QUIC 協(xié)議沒有用四元組的方式來“綁定”連接,而是通過連接 ID來標記通信的兩個端點,客戶端和服務器可以各自選擇一組 ID 來標記自己,因此即使移動設備的網(wǎng)絡變化后,導致 IP 地址變化了,只要仍保有上下文信息(比如連接 ID、TLS 密鑰等),就可以“無縫”地復用原連接,消除重連的成本,沒有絲毫卡頓感,達到了連接遷移的功能。
所以, QUIC 是一個在 UDP 之上的偽 TCP + TLS + HTTP/2 的多路復用的協(xié)議。

QUIC 是新協(xié)議,對于很多網(wǎng)絡設備,根本不知道什么是 QUIC,只會當做 UDP,這樣會出現(xiàn)新的問題,因為有的網(wǎng)絡設備是會丟掉 UDP 包的,而 QUIC 是基于UDP 實現(xiàn)的,那么如果網(wǎng)絡設備無法識別這個是 QUIC 包,那么就會當作 UDP包,然后被丟棄。
所以,HTTP/3 現(xiàn)在普及的進度非常的緩慢,不知道未來 UDP 是否能夠逆襲 TCP。
參考資料:
[1] 上野 宣.圖解HTTP.人民郵電出版社.
[2] 羅劍鋒.透視HTTP協(xié)議.極客時間.
[3] 陳皓.HTTP的前世今.酷殼CoolShell.https://coolshell.cn/articles/19840.html
[4] 阮一峰.HTTP 協(xié)議入門.阮一峰的網(wǎng)絡日志.http://www.ruanyifeng.com/blog/2016/08/http.html
讀者問答
讀者問:“https和http相比,就是傳輸?shù)膬?nèi)容多了對稱加密,可以這么理解嗎?”
建立連接時候:https 比 http多了 TLS 的握手過程;
傳輸內(nèi)容的時候:https 會把數(shù)據(jù)進行加密,通常是對稱加密數(shù)據(jù);
讀者問:“ 我看文中 TLS 和 SSL 沒有做區(qū)分,這兩個需要區(qū)分嗎?”
這兩實際上是一個東西。
SSL 是洋文 “Secure Sockets Layer 的縮寫,中文叫做「安全套接層」。它是在上世紀 90 年代中期,由網(wǎng)景公司設計的。
到了1999年,SSL 因為應用廣泛,已經(jīng)成為互聯(lián)網(wǎng)上的事實標準。IETF 就在那年把 SSL 標準化。標準化之后的名稱改為 TLS(是 “Transport Layer Security” 的縮寫),中文叫做 「傳輸層安全協(xié)議」。
很多相關的文章都把這兩者并列稱呼(SSL/TLS),因為這兩者可以視作同一個東西的不同階段。
讀者問:“為啥 ssl 的握手是 4 次?”
SSL/TLS 1.2 需要 4 握手,需要 2 個 RTT 的時延,我文中的圖是把每個交互分開畫了,實際上把他們合在一起發(fā)送,就是 4 次握手:

另外, SSL/TLS 1.3 優(yōu)化了過程,只需要 1 個 RTT 往返時延,也就是只需要 3 次握手:

