HTTP 1.x 學習筆記 —— Web 效能權威指南
HTTP 1.0的優化策略非常簡單,就一句話:升級到HTTP 1.1。完了!
改進HTTP的效能是HTTP 1.1工作組的一個重要目標,後來這個版本也引入了大量增強效能的重要特性,其中一些大家比較熟知的有:
-
持久化連線以支援連線重用;
-
分塊傳輸編碼以支援流式響應;
-
請求管道以支援並行請求處理;
-
位元組服務以支援基於範圍的資源請求;
-
改進的更好的快取機制。
當然,這些只是其中一部分,要全面討論HTTP 1.1的所有增強特性,非得用一本書不可。同樣,推薦大家買一本《HTTP權威指南》(David Gourley和Brian Totty合著)放在手邊。另外,提到好的參考書,Steve Souder的《高效能網站建設指南》中概括了14條規則,有一半針對網路優化:
減少DNS查詢
每次域名解析都需要一次網路往返,增加請求的延遲,在查詢期間會阻塞請求。
減少HTTP請求
任何請求都不如沒有請求更快,因此要去掉頁面上沒有必要的資源。
使用CDN
從地理上把資料放到接近客戶端的地方,可以顯著減少每次TCP連線的網路延遲,增加吞吐量。
新增Expires首部並配置ETag標籤
相關資源應該快取,以避免重複請求每個頁面中相同的資源。Expires首部可用於指定快取時間,在這個時間內可以直接從快取取得資源,完全避免HTTP請求。ETag及Last-Modified首部提供了一個與快取相關的機制,相當於最後一次更新的指紋或時間戳。
Gzip資源
所有文字資源都應該使用Gzip壓縮,然後再在客戶端與伺服器間傳輸。一般來說,Gzip可以減少 60%~80% 的檔案大小,也是一個相對簡單(只要在伺服器上配置一個選項),但優化效果較好的舉措。
避免HTTP重定向
HTTP重定向極其耗時,特別是把客戶端定向到一個完全不同的域名的情況下,還會導致額外的DNS查詢、TCP連線延遲,等等。
上面每一條建議都經受了時間檢驗,無論是該書出版的2007年還是今天,都是適用的。這並不是巧合,而是因為所有這些建議都反映了兩個根本方面:消除和減少不必要的網路延遲,把傳輸的位元組數降到最少。這兩個根本問題永遠是優化的核心,對任何應用都有效。
可是,對所有HTTP 1.1的特性和最佳實踐,我們就不能這麼說了。因為有些HTTP 1.1特性,比如請求管道,由於缺乏支援而流產,而其他協議限制,比如隊首響應阻塞,則導致了更多問題。為此,Web開發社群(一直都最有創造性),創造和推行了很多自造的優化手段:域名分割槽、連線檔案、拼合圖示、嵌入程式碼,等等,不下數十種。
對多數Web開發者而言,所有這些都是切實可行的優化手段:熟悉、必要,而且通用。可是,現實當中,我們應該對這些技術有正確的認識:它們都是些針對當前HTTP 1.1協議的侷限性而採用的權宜之計。我們本來不應該操心去連線檔案、拼合圖示、分割域名或嵌入資源。但遺憾的是,“不應該”並不是務實的態度:這些優化手段之所以存在,都是有原因的,在背後的問題被HTTP的下一個版本解決之前,必須得依靠它們。
持久連線的優點
HTTP 1.1的一個主要改進就是引入了持久HTTP連線 。現在我們再演示一下為什麼這個特性對我們的優化策略如此重要。
為簡單起見,我們限定最多隻有一個TCP連線,並且只取得兩個小檔案(每個<4 KB):一個HTML文件,一個CSS檔案,伺服器響應需要不同的時間(分別為40 ms和20 ms)。
假設從紐約到倫敦的單向光纖延遲都是28 ms
每個TCP連線開始都有三次握手,要經歷一次客戶端與伺服器間完整的往返。此後,會因為HTTP請求和響應的兩次通訊而至少引發另一次往返。最後,還要加上伺服器處理時間,才能得到每次請求的總時間。
伺服器處理時間無法預測,因為這個時間因資源和後端硬體而異。不過,這裡的重點其實是由一個新TCP連線傳送的HTTP請求所花的總時間,最少等於兩次網路往返的時間:一次用於握手,一次用於請求和響應。這是所有非持久HTTP會話都要付出的固定時間成本。
伺服器處理速度越快,固定延遲對每個網路請求總時間的影響就越大!要驗證這一點,可以改一改前面例子中的往返時間和伺服器處理時間。”
實際上,這時候最簡單的優化就是重用底層的連線!新增對HTTP持久連線的支援,就可以避免第二次TCP連線時的三次握手、消除另一次TCP慢啟動的往返,節約整整一次網路延遲。
通過持久TCP連線取得HTML和CSS檔案
在我們兩個請求的例子中,總共只節約了一次往返時間。但是,更常見的情況是一次TCP連線要傳送N 次HTTP請求,這時:
-
沒有持久連線,每次請求都會導致兩次往返延遲;
-
有持久連線,只有第一次請求會導致兩次往返延遲,後續請求只會導致一次往返延遲。
在啟用持久連線的情況下,N 次請求節省的總延遲時間就是(N -1)×RTT。還記得嗎,前面說過,在當代Web應用中,N 的平均值是90,而且還在繼續增加。因此,依靠持久連線節約的時間,很快就可以用秒來衡量了!這充分說明持久化HTTP是每個Web應用的關鍵優化手段。
HTTP管道
持久HTTP可以讓我們重用已有的連線來完成多次應用請求,但多次請求必須嚴格滿足先進先出(FIFO)的佇列順序:傳送請求,等待響應完成,再發送客戶端佇列中的下一個請求。HTTP管道是一個很小但對上述工作流卻非常重要的一次優化。管道可以讓我們把
FIFO佇列從客戶端(請求佇列)遷移到伺服器(響應佇列)。
要理解這樣做的好處,我們再看一看通過持久TCP連線取得HTML和CSS檔案示意圖。首先,伺服器處理完第一次請求後,會發生了一次完整的往返:先是響應回傳,接著是第二次請求。在此期間伺服器空閒。如果伺服器能在處理完第一次請求後,立即開始處理第二次請求呢?甚至,如果伺服器可以並行或在多執行緒上或者使用多個工作程序,同時處理兩個請求呢?
通過儘早分派請求,不被每次響應阻塞,可以再次消除額外的網路往返。這樣,就從非持久連線狀態下的每個請求兩次往返,變成了整個請求佇列只需要兩次網路往返!
現在我們暫停一會,回顧一下在效能優化方面的收穫。一開始,每個請求要用兩個TCP連線,總延遲為284 ms。在使用持久連線後,避免了一次握手往返,總延遲減少為228 ms。最後,通過使用HTTP管道,又減少了兩次請求之間的一次往返,總延遲減少為172 ms。這樣,從284 ms到172 ms,這40%的效能提升完全拜簡單的協議優化所賜。
而且,這40%的效能提升還不是固定不變的。這個數字與我們選擇的網路延遲和兩個請求的例子有關。希望讀者自己能夠嘗試一些不同的情況,比如延遲更高、請求更多的情況。嘗試之後,你會驚訝於效能提升效果比這裡還要高得多。事實上,網路延遲越高,請求越多,節省的時間就越多。我覺得大家很有必要自己動手驗證一下這個結果。因此,越是大型應用,網路優化的影響越大。
不過,這還不算完。眼光敏銳的讀者可能已經發現了,我們可以在伺服器上並行處理請求。理論上講,沒有障礙可以阻止伺服器同時處理管道中的請求,從而再減少20 ms的延遲。
可惜的是,當我們想要採取這個優化措施時,發現了HTTP 1.x協議的一些侷限性。HTTP 1.x只能嚴格序列地返回響應。特別是,HTTP 1.x不允許一個連線上的多個響應資料交錯到達(多路複用),因而一個響應必須完全返回後,下一個響應才會開始傳輸。為說明這一點,我們可以看看伺服器並行處理請求的情況(如下圖)。
上圖演示瞭如下幾個方面:
-
HTML和CSS請求同時到達,但先處理的是HTML請求;
-
伺服器並行處理兩個請求,其中處理HTML用時40 ms,處理CSS用時20 ms;
-
CSS請求先處理完成,但被緩衝起來以等候傳送HTML響應;
-
傳送完HTML響應後,再發送伺服器緩衝中的CSS響應。”
即使客戶端同時傳送了兩個請求,而且CSS資源先準備就緒,伺服器也會先發送HTML響應,然後再交付CSS。這種情況通常被稱作隊首阻塞 ,並經常導致次優化交付:不能充分利用網路連線,造成伺服器緩衝開銷,最終導致無法預測的客戶端延遲。假如第一個請求無限期掛起,或者要花很長時間才能處理完,怎麼辦呢?在HTTP 1.1中,所有後續的請求都將被阻塞,等待它完成。
實際中,由於不可能實現多路複用,HTTP管道會導致HTTP伺服器、代理和客戶端出現很多微妙的,不見文件記載的問題:
-
一個慢響應就會阻塞所有後續請求;
-
並行處理請求時,伺服器必須緩衝管道中的響應,從而佔用伺服器資源,如果有個響應非常大,則很容易形成伺服器的受攻擊面;
-
響應失敗可能終止TCP連線,從頁強迫客戶端重新發送對所有後續資源的請求,導致重複處理;
-
由於可能存在中間代理,因此檢測管道相容性,確保可靠性很重要;
-
如果中間代理不支援管道,那它可能會中斷連線,也可能會把所有請求串聯起來。
由於存在這些以及其他類似的問題,而HTTP 1.1標準中也未對此做出說明,HTTP管道技術的應用非常有限,雖然其優點毋庸置疑。今天,一些支援管道的瀏覽器,通常都將其作為一個高階配置選項,但大多數瀏覽器都會禁用它。換句話說,如果瀏覽器是Web應用的主要交付工具,那還是很難指望通過HTTP管道來提升效能。
使用多個TCP連線
由於HTTP 1.x不支援多路複用,瀏覽器可以不假思索地在客戶端排隊所有HTTP請求,然後通過一個持久連線,一個接一個地傳送這些請求。然而,這種方式在實踐中太慢。實際上,瀏覽器開發商沒有別的辦法,只能允許我們並行開啟多個TCP會話。多少個?現實中,大多數現代瀏覽器,包括桌面和移動瀏覽器,都支援每個主機開啟6個連線。
進一步討論之前,有必要先想一想同時開啟多個TCP連線意味著什麼。當然,有正面的也有負面的。下面我們以每個主機開啟最多6個獨立連線為例:
-
客戶端可以並行分派最多6個請求;
-
伺服器可以並行處理最多6個請求;
-
第一次往返可以傳送的累計分組數量(TCP cwnd)增長為原來的6倍。
在沒有管道的情況下,最大的請求數與開啟的連線數相同。相應地,TCP擁塞視窗也要乘以開啟的連線數量,從而允許客戶端繞開由TCP慢啟動規定的分組限制。這好像是一個方便的解決方案。我們再看看這樣做的代價:
-
更多的套接字會佔用客戶端、伺服器以及代理的資源,包括記憶體緩衝區和CPU時鐘週期;
-
並行TCP流之間競爭共享的頻寬;
-
由於處理多個套接字,實現複雜性更高;
-
即使並行TCP流,應用的並行能力也受限制。
實踐中,CPU和記憶體佔用並非微不足道,由此會導致客戶端和伺服器端的資源佔用量上升,運維成本提高。類似地,由於客戶端實現的複雜性提高,開發成本也會提高。最後,說到應用的並行性,這種方式提供的好處還是非常有限的。這不是一個長期的方案。瞭解這些之後,可以說今天之所以使用它,主要有三個原因:
-
作為繞過應用協議(HTTP)限制的一個權宜之計;
-
作為繞過TCP中低起始擁塞視窗的一個權宜之計;
-
作為讓客戶端繞過不能使用TCP視窗縮放”的一個權宜之計。
後兩個針對TCP的問題(視窗縮放和cwnd)最好是通過升級到最新的OS核心來解決。cwnd值最近又提高到了10個分組,而所有最新的平臺都能可靠地支援TCP視窗縮放。這當然是好訊息。但壞訊息是,沒有更好辦法繞開HTTP 1.x的多路複用問題。
只要必須支援HTTP 1.x客戶端,就不得不想辦法應對多TCP流的問題。而這又會帶來一個明顯的問題:為什麼瀏覽器要規定每個主機6個連線呢?恐怕有讀者也猜到了,這個數字是多方平衡的結果:這個數字越大,客戶端和伺服器的資源佔用越多,但隨之也會帶來更高的請求並行能力。每個主機6個連線只不過是大家都覺得比較安全的一個數字。對某些站點而言,這個數字已經足夠了,但對其他站點來說,可能還滿足不了需求。
域名分割槽
HTTP 1.x協議的一項空白強迫瀏覽器開發商引入並維護著連線池,每個主機最多6個TCP流。好的一方面是對這些連線的管理工作都由瀏覽器來處理。作為應用開發者,你根本不必修改自己的應用。不好的一方面呢,就是6個並行的連線對你的應用來說可能仍然不夠用。
根據HTTP Archive的統計,目前平均每個頁面都包含90多個獨立的資源,如果這些資源都來自同一個主機,那麼仍然會導致明顯的排隊等待(如下圖所示)。實際上,何必把自己只限制在一個主機上呢?我們不必只通過一個主機(例如www.example.com)提供所有資源,而是可以手工將所有資源分散到多個子域名:{shard1, shardn}.example.com。由於主機名稱不一樣了,就可以突破瀏覽器的連線限制,實現更高的並行能力。域名分割槽使用得越多,並行能力就越強!
由於每個主機只能同時發起6個連線而導致的資源錯列
當然,天下沒有免費的午餐,域名分割槽也不例外:每個新主機名都要求有一次額外的DNS查詢,每多一個套接字都會多消耗兩端的一些資源,而更糟糕的是,站點作者必須手工分離這些資源,並分別把它們託管到多個主機上。
實踐中,域名分割槽經常會被濫用,導致幾十個TCP流都得不到充分利用,其中很多永遠也避免不了TCP慢啟動,最壞的情況下還會降低效能。此外,如果使用的是HTTPS,那麼由於TLS握手導致的額外網路往返,會使得上述代價更高。此時,請大家注意如下幾條:
-
首先,把TCP利用好;
-
瀏覽器會自動為你開啟6個連線;
-
資源的數量、大小和響應時間都會影響最優的分割槽數目;”
-
客戶端延遲和頻寬會影響最優的分割槽數目;
-
域名分割槽會因為額外的DNS查詢和TCP慢啟動而影響效能。
域名分割槽是一種合理但又不完美的優化手段。請大家一定先從最小分割槽數目(不分割槽)開始,然後逐個增加分割槽並度量分割槽後對應用的影響。現實當中,真正因同時開啟十幾個連線而提升效能的站點並不多,如果你最終使用了很多分割槽,那麼你會發現減少資源數量或者將它們合併為更少的請求,反而能帶來更大的好處。
DNS查詢和TCP慢啟動導致的額外消耗對高延遲客戶端的影響最大。換句話說,移動(3G、4G)客戶端經常是受過度域名分割槽影響最大的!
度量和控制協議開銷
HTTP 0.9當初就是一個簡單的只有一行的ASCII請求,用於取得一個超文字文件,這樣導致的開銷是最小的。HTTP 1.0增加了請求和響應首部,以便雙方能夠交換有關請求和響應的元資訊。最終,HTTP 1.1把這種格式變成了標準:伺服器和客戶端都可以輕鬆擴充套件首部,而且始終以純文字形式傳送,以保證與之前HTTP版本的相容。
今天,每個瀏覽器發起的HTTP請求,都會攜帶額外500~800位元組的HTTP元資料:使用者代理字串、很少改變的接收和傳輸首部、快取指令,等等。有時候,500~800位元組都少說了,因為沒有包含最大的一塊:HTTP cookie。現代應用經常通過cookie進行會話管理、記錄個性選項或者完成分析。綜合到一起,所有這些未經壓縮的HTTP元資料經常會給每個HTTP請求增加幾千位元組的協議開銷。
HTTP首部的增多對它本身不是壞事,因為大多數首部都有其特定用途。然而,由於所有HTTP首部都以純文字形式傳送(不會經過任何壓縮),這就會給每個請求附加較高的額外負荷,而這在某些應用中可能造成嚴重的效能問題。舉個例子,API驅動的Web應用越來越多,這些應用需要頻繁地以序列化訊息(如JSON)的形式通訊。在這些應用中,額外的HTTP開銷經常會超過實際傳輸的資料淨荷一個數量級:
“$> curl --trace-ascii - -
d'{"msg":"hello"}'
http://www.igvita.com/api”
對應的結果:
== Info: Connected to www.igvita.com => Send header, 218 bytes ➊ POST /api HTTP/1.1 User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 ... Host: www.igvita.com Accept: */* Content-Length: 15 ➋ Content-Type: application/x-www-form-urlencoded => Send data, 15 bytes (0xf) {"msg":"hello"} <= Recv header, 134 bytes ➌ HTTP/1.1 204 No Content Server: nginx/1.0.11 Via: HTTP/1.1 GWA Date: Thu, 20 Sep 2012 05:41:30 GMT Cache-Control: max-age=0, no-cache
-
HTTP請求首部:218位元組
-
應用靜荷15位元組({"msg”:"hello"})
-
伺服器的204響應:134位元組
在前面的例子中,寥寥15個字元的JSON訊息被352位元組的HTTP首部包裹著,全部以純文字形式傳送——協議位元組開銷佔96%,而且這還是沒有cookie的最好情況。減少要傳輸的首部資料(高度重複且未壓縮),可以節省相當於一次往返的延遲時間,顯著提升很多Web應用的效能。
“Cookie在很多應用中都是常見的效能瓶頸,很多開發者都會忽略它給每次請求增加的額外負擔。
連線與拼合
最快的請求是不用請求。不管使用什麼協議,也不管是什麼型別的應用,減少請求次數總是最好的效能優化手段。可是,如果你無論如何也無法減少請求,那麼對HTTP 1.x而言,可以考慮把多個資源捆綁打包到一塊,通過一次網路請求獲取:
-
連線 :把多個JavaScript或CSS檔案組合為一個檔案。
-
拼合:把多張圖片組合為一個更大的複合的圖片。
對JavaScript和CSS來說,只要保持一定的順序,就可以做到把多個檔案連線起來而不影響程式碼的行為和執行。類似地,多張圖片可以組合為一個“圖片精靈”,然後使用CSS選擇這張大圖中的適當部分,顯示在瀏覽器中。這兩種技術都具備兩方面的優點。
-
減少協議開銷:通過把檔案組合成一個資源,可以消除與檔案相關的協議開銷。如前所述,每個檔案很容易招致KB級未壓縮資料的開銷。
-
應用層管道:說到傳輸的位元組,這兩種技術的效果都好像是啟用了HTTP管道:來自多個響應的資料前後相繼地連線在一起,消除了額外的網路延遲。實際上,就是把管道提高了一層,置入了應用中。
連線和拼合技術都屬於以內容為中心的應用層優化,它們通過減少網路往返開銷,可以獲得明顯的效能提升。可是,實現這些技術也要求額外的處理、部署和編碼(比如選擇圖片精靈中子圖的CSS程式碼),因而也會給應用帶來額外的複雜性。此外,把多個資源打包到一塊,也可能給快取帶來負擔,影響頁面的執行速度。
要理解為什麼這些技術會傷害效能,可以考慮一種並不少見的情況:一個包含十來個JavaScript和CSS檔案的應用,在產品狀態下把所有檔案合併為一個CSS檔案和一個JavaScript檔案。
-
相同型別的資源都位於一個URL(快取鍵)下面。
-
資源包中可能包含當前頁面不需要的內容。
-
對資源包中任何檔案的更新,都要求重新下載整個資源包,導致較高的位元組開銷。
-
JavaScript和CSS只有在傳輸完成後才能被解析和執行,因而會拖慢應用的執行速度。
實踐中,大多數Web應用都不是隻有一個頁面,而是由多個檢視構成。每個檢視都有自己的資源,同時資源之間還有部分重疊:公用的CSS、JavaScript和圖片。實際上,把所有資源都組合到一個檔案經常會導致處理和載入不必要的位元組。雖然可以把它看成一種預獲取,但代價則是降低了初始啟動的速度。
對很多應用來說,更新資源帶來的問題更大。更新圖片精靈或組合JavaScript檔案中的某一處,可能就會導致重新傳輸幾百KB資料。由於犧牲了模組化和快取粒度,假如打包資源變動頻率過高,特別是在資源包過大的情況下,很快就會得不償失。如果你的應用真到了這種境地,那麼可以考慮把“穩定的核心”,比如框架和庫,轉移到獨立的包中。
記憶體佔用也會成為問題。對圖片精靈來說,瀏覽器必須分析整個圖片,即便實際上只顯示了其中的一小塊,也要始終把整個圖片都儲存在記憶體中。瀏覽器是不會把不顯示的部分從記憶體中剔除掉的!
最後,為什麼執行速度還會受影響呢?我們知道,瀏覽器是以遞增方式處理HTML的,而對於JavaScript和CSS的解析及執行,則要等到整個檔案下載完畢。JavaScript和CSS處理器都不允許遞增式執行。
CSS和JavaScript檔案大小與執行效能
CSS檔案越大,瀏覽器在構建CSSOM前經歷的阻塞時間就越長,從而推遲首次繪製頁面的時間。類似地,JavaScript檔案越大,對執行速度的影響同樣越大;小檔案倒是能實現“遞增式”執行。打包檔案到底多大合適呢?可惜的是,沒有理想的大小。然而,谷歌PageSpeed團隊的測試表明,30~50 KB(壓縮後)是每個JavaScript檔案大小的合適範圍:既大到了能夠減少小檔案帶來的網路延遲,還能確保遞增及分層式的執行。具體的結果可能會由於應用型別和指令碼數量而有所不同。
總之,連線和拼合是在HTTP 1.x協議限制(管道沒有得到普遍支援,多請求開銷大)的現實之下可行的應用層優化。使用得當的話,這兩種技術可以帶來明顯的效能提升,代價則是增加應用的複雜度,以及導致快取、更新、執行速度,甚至渲染頁面的問題。應用這兩種優化時,要注意度量結果,根據實際情況考慮如下問題。
-
你的應用在下載很多小型的資源時是否會被阻塞?
-
有選擇地組合一些請求對你的應用有沒有好處?
-
放棄快取粒度對使用者有沒有負面影響?
-
組合圖片是否會佔用過多記憶體?
-
首次渲染時是否會遭遇延遲執行?
在上述問題的答案間求得平衡是一種藝術。
嵌入資源
嵌入資源是另一種非常流行的優化方法,把資源嵌入文件可以減少請求的次數。比如,JavaScript和CSS程式碼,通過適當的script 和style 塊可以直接放在頁面中,而圖片甚至音訊或PDF檔案,都可以通過資料URI(data:[mediatype][;base64],data )的方式嵌入到頁面中:
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAA AAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==" alt="1x1 transparent (GIF) pixel" />
資料URI適合特別小的,理想情況下,最好是隻用一次的資源。以嵌入方式放到頁面中的資源,應該算是頁面的一部分,不能被瀏覽器、CDN或其他快取代理作為單獨的資源快取。換句話說,如果在多個頁面中都嵌入同樣的資源,那麼這個資源將會隨著每個頁面的載入而被載入,從而增大每個頁面的總體大小。另外,如果嵌入資源被更新,那麼所有以前出現過它的頁面都將被宣告無效,而由客戶端重新從伺服器獲取。
最後,雖然CSS和JavaScript等基於文字的資源很容易直接嵌入頁面,也不會帶來多餘的開銷,但非文字性資源則必須通過base64編碼,而這會導致開銷明顯增大:編碼後的資源大小比原大小增大33%!
base64編碼使用64個ASCII符號和空白符將任意位元組流編碼為ASCII字串。編碼過程中,base64會導致被編碼的流變成原來的4/3,即增大33%的位元組開銷。
實踐中,常見的一個經驗規則是隻考慮嵌入1~2 KB以下的資源,因為小於這個標準的資源經常會導致比它自身更高的HTTP開銷。然而,如果嵌入的資源頻繁變更,又會導致宿主文件的無效快取率升高。嵌入資源也不是完美的方法。如果你的應用要使用很小的、個別的檔案,在考慮是否嵌入時,可以參照如下建議:
-
如果檔案很小,而且只有個別頁面使用,可以考慮嵌入;
-
如果檔案很小,但需要在多個頁面中重用,應該考慮集中打包;
-
如果小檔案經常需要更新,就不要嵌入了;
-
通過減少HTTP cookie的大小將協議開銷最小化。
關於 HTTP 系列文章:
-
HTTP 概述
-
TCP 三次握手和四次揮手圖解(有限狀態機)
-
從你輸入網址,到看到網頁——詳解中間發生的過程
-
深入淺出 HTTPS (詳解版)
-
漫談 HTTP 連線
-
漫談 HTTP 效能優化
-
HTTP 報文格式簡介
-
深入淺出:HTTP/2