為什麼要升級 HTTP#
許多前端效能優化技巧都帶有「不一定見效、有時甚至會反效果」的性質,但 HTTP 這個應用層協定的升級卻是少數例外:
重點原則 只要環境允許,就盡量把 HTTP 升上去,因為帶來的效能提升是非常顯著的。
雖然版本升級主要靠後端與基礎建設配合,但前端開發者每天都在跟 HTTP 打交道,HTTP/1.1、HTTP/2、HTTP/3 之間的差異也直接影響到許多既有的前端優化策略,因此理解這些網路觀念對前端工程師同樣重要。
HTTP/1.1 的限制#
以下假設讀者已熟悉 TCP(Transmission Control Protocol)建立連線的三次握手與斷線時的四次揮手。HTTP/1.0 因為已經太過古老,不再贅述,這裡直接從仍廣泛使用的 HTTP/1.1 看起。
限制一:Head-of-Line Blocking#
HTTP/1.1 只能以串行(serial)方式處理請求,後面的請求必須等前一個請求完成才能發出去。
為了改善這個情況,曾出現一個叫做 HTTP pipelining 的機制,允許一次送出一組請求,但是回應仍必須按發送順序回傳。例如同時發出兩個請求,第二個本來只需要 300 毫秒,但若第一個花了 5 秒,第二個就只能在後面排隊等候,這就是所謂的 Head-of-Line Blocking(HOL Blocking)。
加上 HTTP pipelining 還有「只支援 GET 與 HEAD」、「proxy 支援度差」等限制,最後幾乎宣告失敗,主流瀏覽器也預設關閉這個機制。
限制二:瀏覽器的同網域連線數限制#
既然串行那麼慢,乾脆多開幾個連線就好?實際上不行——瀏覽器會限制同一個網域同時可建立的 TCP 連線數。例如 Google Chrome 對單一網域的同時請求上限是 6 條、總連線上限為 10 條,超過時請求會被丟到佇列裡排隊。
觀察方式 在 DevTool 的 Network waterfall 欄位可以看到每個請求停留在 queueing 階段的時間,這正是被連線數上限卡住的證據。
限制三:Header 無法壓縮#
在 HTTP/1.x 中,請求與回應的 header 沒辦法壓縮。一個 header 可能就有 2 至 3 KB,且各個請求之間還有大量重複欄位,沒有壓縮機制下會持續耗損頻寬與時間。
由限制衍生的前端優化技巧#
由於上述限制,過去衍生出一條原則:「盡量減少 HTTP 請求數量。」具體做法包括:
- CSS Sprites:把多張小圖合併成一張大圖。
- Bundling:把多個 CSS / JS 模組合併打包。
- Domain Sharding:刻意把資源拆分到不同網域,繞開瀏覽器的同網域連線數限制。
這些技巧在 HTTP/1.x 時代相當有用,但也帶來檔案維護、快取粒度等代價。
HTTP/2#
HTTP/2(簡稱 H2)目前已被廣泛使用,與 HTTP/1.x 相比最大的提升就是「效能」。它主要解決了兩個問題:
- HTTP pipelining 的 Head-of-Line Blocking
- Header 無法壓縮
容易誤解的地方 HTTP/2 並沒有解決 TCP 層級的 Head-of-Line Blocking,它解決的只是 HTTP pipelining 那一層的阻塞。
Header 壓縮#
HTTP/1.1 雖然可以用 gzip、brotli 對 body 做壓縮,但 header 完全無法壓縮,這在 HTTP/2 終於被處理。
多工(Multiplexing)#
HTTP/2 引入了 stream 與 frame 兩個觀念,並把訊息切成更小的二進位 frame 來傳輸:
- Stream:類似一個串流管道,是一條邏輯通道。
- Frame:在 stream 中流動的最小傳輸單位,每個 frame 都帶有所屬 stream 的識別碼。
有了 stream 識別碼,即使回應抵達的順序與請求送出的順序不同,client 仍能根據識別碼把 frame 重組回正確的請求,徹底解決了 HTTP pipelining 因順序而阻塞的問題。
多工為 HTTP/2 帶來的優點:
- 可以並行送出多個請求,彼此互不影響。
- 可以並行接收多個回應,彼此互不影響。
- 在單一連線上同時處理多個 request 與 response。
- 提升網路頻寬的使用效率。
Server Push#
Server Push 讓伺服器可以在 client 還沒解析完 HTML、還沒發出後續請求時,就主動把預期會用到的資源推送過去。
不過 Server Push 並沒有看起來那麼美好:
- 開發者必須自己定義要推送哪些資源。
- 多數 CDN 供應商目前不支援 Server Push,使用它就得放棄 CDN 的優勢。
- Server Push 不會檢查資源是否已經被瀏覽器快取,可能會浪費頻寬重複推送。
- 只能推送可被快取且沒有 response body 的資源,因此僅限 GET 與 HEAD 請求。
如果不確定 Server Push 是否值得使用,也可以考慮 resource hint(例如 prefetch),讓瀏覽器自行決定。
升級到 HTTP/2 後可省略的舊優化#
HTTP/2 的多工特性讓許多舊有的優化技巧(例如 CSS Sprites、inline scripting、Domain Sharding)變得不再必要——它們本來就是為了繞開 HTTP/1.x 的限制,現在限制消失了,那些 trade off 也不必再背了。
如何升級到 HTTP/2#
升級 HTTP/2 通常不需要改動現有程式碼,只要:
- 確保使用 HTTPS 加密連線
- Web Server 支援 HTTP/2
範例的 nginx 設定如下:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/example.com.crt;
ssl_certificate_key /etc/ssl/example.com.key;
}如果不想自己處理 TLS 與 H2 設定,最簡單的方式是接上像 Cloudflare 這種 CDN,它會免費提供 TLS 1.3 憑證與 HTTP/2、HTTP/3 連線。
注意事項 透過 CDN 升級時,僅 client 到 CDN 邊緣節點這段走 HTTP/2 或 HTTP/3,CDN 到起源伺服器仍可能維持 HTTP/1.1。
對於原本不是全站 HTTPS 的網站,升級時還要特別注意 Mixed Content 問題:一旦頁面開始走 HTTPS,瀏覽器會擋掉殘留的 HTTP 資源,導致網站「看似掛掉」。
HTTP/3#
HTTP/2 仍未解決 TCP 層級的 Head-of-Line Blocking。為了徹底處理這個問題,HTTP/3(簡稱 H3)直接把傳輸層的 TCP 換掉,改用以 UDP(User Datagram Protocol)為基礎的 QUIC 協定。
UDP 本身是不可靠的傳輸,但 QUIC 在 UDP 之上重建了可靠傳輸所需的機制(例如重傳、流量控制、加密整合等),同時又避開了 TCP 一個封包遺失就阻塞整條連線的缺陷。
目前 HTTP/3 的支援度仍在擴展中,但已有不少網站開始採用,可預期未來會逐步普及。
小結#
HTTP 版本的升級是少數可以「無痛」帶來顯著效能提升的優化方式之一。回顧一下:
- HTTP/1.1 受制於串行傳輸、連線數限制與 header 無壓縮,為前端帶來各種繞道優化。
- HTTP/2 透過多工、二進位分幀、header 壓縮,解決了多數 HTTP 層級的 HOL 問題,也讓許多舊優化技巧變得多餘。
- HTTP/3 改以 QUIC 取代 TCP,連 TCP 層級的 HOL 都一起處理掉。
身為一個常與 HTTP 共舞的前端開發者,理解這些觀念是十分重要的;遇到可以升級的場景,就大膽地升上去吧。