說起http必然先了解《萬維網(World Wide Web)》簡稱WWW。
WWW是基于客戶機<=>服務器方式"利用鏈接跳轉站點" 和 "傳輸超文本標記語言(HTML)" 的技術綜合。
1989年仲夏之夜,蒂姆·伯納斯·李成功開發出世界上第一個Web服務器和第一個Web客戶機,這個時候能做的還只是一本電子版的電話號碼簿。
(資料圖片僅供參考)
而HTTP(HyperText Transfer Protocol)是萬維網的基礎協議,制定了瀏覽器與服務器之間的通訊規則。
通常使用的網絡(包括互聯網)是在TCP/IP協議族的基礎上動作的。而HTTP屬于它的內部的一個子集。
http不斷的實現更多功能,到目前從HTTP 0.9已經演化到了HTTP 3.0。
HTTP/0.9HTTP問世之初并沒有作為標準建立,被正式制定為標準是在1996年公布的HTTP/1.0協議。因此,在這之前的協議被稱為HTTP/0.9。
request只有一行且只有一個GET命令,命令后面跟著的是資源路徑。
GET /index.$html$
reponse僅包含文件內容本身。
HELLO WORLD!
HTTP/0.9沒有header的概念,也沒有content-type的概念,僅能傳遞html文件。同樣由于沒有status code,當發生錯誤的時候是通過傳遞回一個包含錯誤描述的html文件來處理的。
HTTP/1.0隨著互聯網技術的飛速發展,HTTP協議被使用的越來越廣泛,協議本身的局限性已經不能滿足互聯網功能的多樣性。因此,1996年5月HTTP/1.0誕生,其內容和功能都大大增加了。對比與HTTP/0.9,新的版本包含了以下功能:
在每個request的GET一行后面添加版本號在response第一行中添加狀態行在request和response中添加header的概念在header中添加content-type以此可以傳輸html之外類型的文件在header中添加content-encoding來支持不同編碼格式文件的傳輸引入了POST和HEAD命令支持長連接(默認短連接)GET /index.html HTTP/1.0User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)200 OKDate: Tue, 15 Nov 1994 08:12:31 GMTServer: CERN/3.0 libwww/2.17Content-Type: text/html;charset=utf-8 // 類型,編碼。A page with an image content
簡單的文字頁面自然無法滿足用戶的需求,于是1.0加入了更多的文件類型:
常見Content-Type | ||
text/plan | text/html | text/css |
image/jpeg | image/png | image/svg + xml |
application/javascript | application/zip | application/pdf |
也同樣可以用在html中。
Content-encoding
由于支持任意數據格式的發送,因此可以先把數據進行壓縮再發送。HTTP/1.0進入了Content-Encoding來表示數據的壓縮方式。
Content-Encoding: gzip?!颈硎静捎?Lempel-Ziv coding (LZ77) 壓縮算法,以及32位CRC校驗的編碼方式】。Content-Encoding: compress?!静捎?Lempel-Ziv-Welch (LZW) 壓縮算法】。Content-Encoding: deflate?!静捎?zlib 】。客戶端發送請求帶有表明我可以接受gzip、deflate兩種壓縮方式。
Accept-Encoding: gzip, deflate
服務器在Content-Encoding響應首部提供了實際采用的壓縮模式。
Content-Encoding: gzipHTTP/1.0 缺點隊頭阻塞(Head-of-Line Blocking?,每個TCP連接只能發送一個請求。發送數據完畢,連接就關閉,如果還要請求其他資源,就必須再新建一個連接。默認是短連接,即每個HTTP請求都要使用TCP協議通過三次握手和四次揮手實現。僅定義了16種狀態碼。HTTP/1.1
僅僅在HTTP/1.0公布后的幾個月,HTTP/1.1發布了,到目前為止HTTP1.1協議都是作為主流的版本,以至于隨后的近10年時間里都沒有新的HTTP協議版本發布。
對比之前的版本,其主要更新如下:
可以重復使用連接(keep-alive),從而節省時間,不再需要多次打開才能顯示嵌入在單個原始文檔中的資源。添加了Pipeline,這允許在第一個請求的答案完全傳輸之前發送第二個請求這降低了通信的延遲。chunked機制,分塊響應。引入了額外的緩存控制機制。引入了內容協商,包括語言、編碼和類型,客戶端和服務器現在可以就交換哪些內容達成一致。由于Host標頭,從同一 IP 地址托管不同域的能力允許服務器搭配。keep-alive由于建立一個連接的過程需要DNS解析過程以及TCP的三次握手,但在同服務器獲取資源不斷的建立和斷開鏈接需要消耗的資源和時間是巨大的,為了提升連接的效率 HTTP/1.1的及時出現將長連接加入了標準并作為默認實現,服務器端也按照協議保持客戶端的長連接狀態,一個服務端上的多個資源都可以通過這一條連接多個request來獲取。
可以在request header中引入如下信息來告知服務器完成一次request請求后不要關閉連接。
Connection: keep-alive
服務器端也會答復一個相同的信息表示連接仍然有效,但是在當時這只是屬于程序員的自定義行為,在1.0中沒有被納入標準。這其中的提升對于通訊之間的效率提升幾乎是倍增的,
這也為管線化方式(pipelining)打下基礎。
Pipeline (管線化)HTTP/1.1嘗試通過HTTP管線化技術來解決性能瓶頸,誕生了pipeline機制,如圖從每次response返回結果才能進行下一次request,變為一次連接上多個http request不需要等待response就可以連續發送的技術。
不幸的是因為HTTP是一個無狀態的協議,一個體積很大的或慢response仍然會阻塞后面所有的請求,每條request無法知道哪條response是返回給他的,服務端只能根據順序來返回response,這就是隊頭阻塞,這導致主流瀏覽器上默認下該功能都是關閉狀態,在http2.0中會解決這個問題。
host頭域在HTTP1.0中認為每臺服務器都綁定一個唯一的IP地址,因此,請求消息中的URL并沒有傳遞主機名(hostname),1.1中新增的host用來處理一個IP地址上面多個虛擬主機的情況。
在請求頭域中新增了Host字段,其用來指定服務器的域名。有了Host字段,在同一臺服務器上就可以搭建不同的網站了,這也為后來虛擬化的發展建好啦地基。
Host: www.alibaba-inc.comcache機制
Cache不僅可以提高用戶的訪問速率,在移動端設備上還可以為用戶極大節省流量。因此,在HTTP/1.1中新增了很多與Cache相關的頭域并圍繞這些頭域設計了更靈活、更豐富的Cache機制。
Cache機制需要解決的問題包括:
判斷哪些資源可以被Cache及訪問訪問策略。在本地判斷Cache資源是否已經過期。向服務端發起問詢,查看已過期的Cache資源是否在服務端發生了變化。chunked機制建立好鏈接之后客戶端可以使用該鏈接發送多個請求,用戶通常會通過response header中返回的Content-Length來判斷服務端返回數據的大小。但隨著網絡技術的不斷發展,越來越多的動態資源被引入進來,這時候服務端就無法在傳輸之前知道待傳遞資源的大小,也就無法通過Content-Length來告知用戶資源大小。服務器可以一邊動態產生資源,一邊傳遞給用戶,這種機制稱為“分塊傳輸編碼”(Chunkded Transfer Encoding),允許服務端發送給客戶端的數據分為多個部分,此時服務器端需要在header中添加“Transfer-Encoding: chunked”頭域來替代傳統的“Content-Length。
Transfer-Encoding: chunkedHTTP 緩存機制
相比 HTTP 1.0,HTTP 1.1 新增了若干項緩存機制:
強緩存
強緩存,是瀏覽器優先命中的緩存,速度最快。當我們在狀態碼后面看到 (from memory disk) 時,就表示瀏覽器從內存中讀取了緩存,當進程結束后,也就是 tab 關閉以后,內存里的數據也將不復存在。只有當強緩存不被命中的時候,才會進行協商緩存的查找。
Pragma
Pragma頭域是HTTP/1.0的產物。目前僅作為與HTTP/1.0的向后兼容而定義。它現在僅在請求首部中出現,表示要求所有中間服務器不返回緩存的資源,與Cache-Control: no-cache的意義相同。
Pragma: no-cache
Expires
Expires僅在響應頭域中出現,表示資源的時效性當發生請求時,瀏覽器將會把Expires 的值與本地時間進行對比,如果本地時間小于設置的時間,則讀取緩存。
Expires的值為標準的 GMT 格式:
Expires: Wed, 21 Oct 2015 07:28:00 GMT
這里需要注意的是:當header中同時存在Cache-Control: max-age=xx和Expires的時候,以Cache-Control: max-age的時間為準。
Cache-Control
由于Expires的局限性,Cache-Control登場了, 下面說明幾個常用的字段:
no-store:緩存不應存儲有關客戶端請求或服務器響應的任何內容。no-cache:在發布緩存副本之前,強制要求緩存把請求提交給原始服務器進行驗證。max-age:相對過期時間,單位為秒(s),告知服務器資源在多少以內是有效的,無需向服務器請求。協商緩存
當瀏覽器沒有命中強緩存后,便會命中協商緩存,協商緩存由以下幾個 HTTP 字段控制。
Last-Modified
服務端將資源傳送給客戶端的時候,會將資源最后的修改時間以Last-Modified: GMT的形式加在實體首部上返回。
Last-Modified: Fri, 22 Jul 2019 01:47:00 GMT
客戶端接收到后會為此資源信息做上標記,等下次重新請求該資源的時候將會帶上時間信息給服務器做檢查,若傳遞的值與服務器上的值一致,則返回304,表示文件沒有被修改過,若時間不一致,則重新進行資源請求并返回200。
優先級
強緩存 --> 協商緩存Cache-Control ->Expires ->ETag->Last-Modified。
新增了五種請求方法OPTIONS:瀏覽器為確定跨域請求資源的安全做的預請求。
PUT:從客戶端向服務器傳送的數據取代指定的文檔的內容。
DELETE:請求服務器刪除指定的頁面。
TRACE:回顯服務器收到的請求,主要用于測試或診斷。
CONNECT:HTTP/1.1 協議中預留給能夠將連接改為管道方式的代理服務器。
新增一系列的狀態碼可以參考狀態碼大全
Http1.1缺陷高延遲,帶來頁面加載速度的降低,(網絡延遲問題只要由于隊頭阻塞,導致寬帶無法被充分利用)。無狀態特性,帶來巨大的Http頭部。明文傳輸,不安全。不支持服務器推送消息。HTTP/2.0根據時代的發展網頁變得更加復雜。其中一些甚至本身就是應用程序。顯示了更多的視覺媒體,增加了交互性的腳本的數量和大小也增加了。更多的數據通過更多的 HTTP 請求傳輸,這為 HTTP/1.1 連接帶來了更多的復雜性和開銷。為此,谷歌在 2010 年代初實施了一個實驗性協議SPDY。鑒于SPDY的成功,HTTP/2也采用了SPDY作為整個方案的藍圖進行開發。HTTP/2 于 2015 年 5 月正式標準化。
HTTP/2 與 HTTP/1.1 區別:二進制幀層。多路復用協議??梢酝ㄟ^同一連接發出并行請求,從而消除 HTTP/1.x 協議的約束。頭部壓縮算法HPACK。由于一些請求在一組請求中通常是相似的,因此這消除了傳輸數據的重復和開銷。它允許服務器通過稱為服務器推送的機制在客戶端緩存中填充數據一張圖來理解HTTP/2 和 HTTP/1.1。Header壓縮HTTP1.x的header帶有大量信息,而且每次都要重復發送,為 HTTP/2 的專門量身打造的HPACK便是類似這樣的思路延伸。它使用一份索引表來定義常用的 HTTP Header,通訊雙方各自cache一份header fields表,既避免了重復header的傳輸,又減小了需要傳輸的大小。
看上去協議的格式和HTTP1.x完全不同了,實際上HTTP2并沒有改變HTTP1.x的語義,只是把原來HTTP1.x的header和body部分用frame重新封裝了一層而已。
多路復用為了解決HTTP/1.x中存在的隊頭阻塞問題,HTTP/2提出了多路復用的概念。即將一個request/response作為一個stream,并將一個stream根據負載分為多種類型的frame(例如 header frame,data frame等),在同一條connection之上可以混合發送分屬于不同stream的frame,這樣就實現了同時發送多個request的功能,多路復用意味著線頭阻塞將不再是一個問題。
HTTP/2 雖然通過多路復用解決了 HTTP 層的隊頭阻塞,但仍然存在 TCP 層的隊頭阻塞。
服務端推送 server push服務可以主動向客戶端發送消息。在瀏覽器剛請求HTML的時候,服務端會把某些資源存在一定的關聯性JS、CSS等文件等靜態資源主動發給客戶端,這樣客戶端可以直接從本地加載這些資源,不用再通過網絡再次請求,以此來達到節省瀏覽器發送request請求的過程。
使用服務器推送
Link: ; rel=preload; as=style,; rel=preload; as=image
可以看到服務器initiator中的push狀態表示這是服務端進行主動推送。
對于主動推送的文件勢必會帶來多余或已經瀏覽器已有一份的文件
客戶端使用一個簡潔的Cache Digest來告訴服務器,哪些東西已經在緩存,因此服務器也就會知道哪些是客戶端所需要的。
流服務器和客戶端在HTTP/2連接內用于交換幀數據的獨立雙向序列,HTTP/2 在單個 TCP 連接上虛擬出多個 Stream, 多個 Stream 實現對一個 TCP 連接的多路復用, 為了合理地利用傳輸鏈路, 實現在有限資源內達到傳輸性能的最優化。
所有的通信都建立在一個TCP連接上,可以傳遞大量的雙向流通的流。
每個流都有獨一無二的標志和優先級。
每個消息都是邏輯上的請求和相應消息。由一個或者多個幀組成。
來自不同流的幀可以通過幀頭的標志來關聯和組裝起來。
流的概念提出是為了實現多路復用,在單個連接上實現同時進行多個業務單元數據的傳輸。
二進制幀層在HTTP/1.x中,用戶為了提高性能建立多個TCP連接.會導致隊頭阻塞和重要TCP連接不能穩定獲得。HTTP/2中的二進制幀層允許請求和響應數據分割為更小的幀,并且它們采用二進制編碼(http1.0基于文本格式)。多個幀之間可以亂序發送,根據幀首部的流(比如每個流都有自己的id)表示可以重新組裝。
顯然這對二進制的計算機是非常友好,無需再將收到明文的報文轉成二進制,而是直接解析二進制報文,進一步提高數據傳輸的效率。
每一個幀可看做是一個學生,流是小組(流標識符為幀的屬性值),一個班級(一個連接)內學生被分為若干個小組,每一個小組分配不同的具體任務,多個小組任務可同時并行在班級內執行。一旦某個小組任務耗時嚴重,但不會影響到其它小組任務正常執行。
最后我們來看一看理想狀態下http2帶來的提升。
缺點
TCP以及TCP+TLS建立連接的延遲(握手延遲)。http2.0中TCP的隊頭阻塞依然沒有徹底解決,連接雙方的有任一個數據包丟失,或任一方的網絡中斷,整個TCP連接就會暫停,丟失的數據包需要被重新傳輸,從而阻塞該TCP連接中的所有請求,反而在網絡較差或不穩定情況下,使用多個連接表現更好。HTTP/3.0 (HTTP-over-QUIC)在限定條件下,TCP下解決隊頭阻塞的問題相當困難,但是隨著互聯網的爆炸式發展,更高的穩定性和安全性需要得到滿足,谷歌在2016年11月國際互聯網工程任務組(IETF)召開了第一次QUIC(Quick UDP Internet Connections)工作組會議,制定的一種基于UDP的低時延的互聯網傳輸層協議,HTTP-over-QUIC于2018年11月更名為HTTP/3。
0-RTT 握手tcp中客戶端發送syn包(syn seq=x)到服務器, 服務器接收并且需要發送(SYN seq =y; ACK x+1)包給客戶端,客戶端向服務器發送確認包ACK(seq = x+1; ack=y+1),至此客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
1-RTT
客服端生成一個隨機數 a 然后選擇一個公開的加密數 X ,通過計算得出 a*X = A, 將X 和 A發送給服務端??头松梢粋€隨機數 b,通過計算得出 b*X = B, 將B發送給服務端??蛻舳耸褂肊CDH生成通訊密鑰 key = aB = a(b*X)。服務器使用ECDH生成通訊密鑰 key = bA = b(a*X)。sequenceDiagram客服端->>服務端: clinet Hello服務端-->>客服端: Server Hello
所以,這里的關鍵就是 ECDH 算法,a 和 b 是客戶端和服務器的私鑰,是不公開的,即使知道 A、X,通過 A = a*X 公式也是無法推導出 a 的,保證了私鑰的安全性。
0-RTT
0-RTT則是客戶端緩存了 ServerConfig(B=b*X),下次建連直接使用緩存數據計算通信密鑰:
sequenceDiagram客服端->>服務端: clinet Hello + 應用數據服務端-->>客服端: ACK客戶端:生成隨機數 c,選擇公開的大數 X,計算 A=cX,將 A 和 X 發送給服務器,也就是 Client Hello 消息后,客戶端直接使用緩存的 B 計算通信密鑰 KEY = cB = cbX,加密發送應用數據。服務器:根據 Client Hello 消息計算通信密鑰 key = bA = b(c*X)。
客戶端不需要經過握手直接通過緩存的B生成key就可以發送應用數據。
再來思考一個問題:假設攻擊者記錄下所有的通信數據和公開參數A1,A2,一旦服務器的隨機數 b(私鑰)泄漏了,那之前通信的所有數據就都可以破解了。
為了解決這個問題,需要為每次會話都創建一個新的通信密鑰,來保證前向安全性。
有序交付QUIC 是基于 UDP 協議的,而 UDP 是不可靠傳輸協議,QUIC 在每個數據包都設有一個 offset 字段(偏移量),接收端根據 offset 字段就可以對異步到達的數據包進行排序了,保證了有序性。
sequenceDiagram客服端->>服務端: PKN=1;offset=0客服端->>服務端: PKN=2;offset=1客服端->>服務端: PKN=3;offset=2服務端-->>客服端: SACK = 1,3客服端->>服務端: 重傳:PKN=4;offset=1隊頭堵塞
HTTP/2 之所以存在 TCP 層的隊頭阻塞,是因為所有請求流都共享一個滑動窗口,而QUIC中給每個請求流都分配一個獨立的滑動窗口。
A 請求流上的丟包不會影響 B 請求流上的數據發送。但是,對于每個請求流而言,也是存在隊頭阻塞問題的,也就是說,雖然 QUIC 解決了 TCP 層的隊頭阻塞,但仍然存在單條流上的隊頭阻塞。這就是 QUIC 聲明的無隊頭阻塞的多路復用。
連接遷移連接遷移:當客戶端切換網絡時,和服務器的連接并不會斷開,仍然可以正常通信,對于 TCP 協議而言,這是不可能做到的。因為 TCP 的連接基于 4 元組:源 IP、源端口、目的 IP、目的端口,只要其中 1 個發生變化,就需要重新建立連接。但QUIC 的連接是基于 64 位的 Connection ID,網絡切換并不會影響 Connection ID 的變化,連接在邏輯上仍然是通的。
假設客戶端先使用 IP1 發送了 1 和 2 數據包,之后切換網絡,IP 變更為 IP2,發送了 3 和 4 數據包,服務器根據數據包頭部的 Connection ID 字段可以判斷這 4 個包是來自于同一個客戶端。QUIC 能實現連接遷移的根本原因是底層使用 UDP 協議就是面向無連接的。
最后我們一張圖來看一下http的升級。
X 關閉
X 關閉