HTTP/2
/ aitch-tee-tee-pee TWO /
到了 2010 年代,網頁已成長到動輒數百項資源,HTTP/1.1 不堪重負。它的大問題是:即便在持久連線上,那條連線上的請求仍是一次一個、依序回應,因此單一個慢檔案就會卡住排在它後面的一切。瀏覽器只好每個站台同時開六條以上連線來繞過這點——既浪費又笨拙。HTTP/2 於 2015 年標準化,它沿用了完全相同的方法、狀態碼、標頭與 HTTP 的語意,卻徹底重建了訊息打包到線路上的方式。
兩項改變承擔了主要的工作。第一,多工(multiplexing):HTTP/2 把每個請求與回應拆成一個個帶標籤的小訊框(frame),並在單一條 TCP 連線上交錯傳送。如今許多請求真的能在同一條連線上同時進行,而一個慢回應在 HTTP 這一層不再卡住其他人——快回應的訊框就直接溜過去。第二,標頭壓縮(HPACK):HTTP/1.1 在每則請求都把同樣龐大的標頭(Cookie、user-agent)原封不動重送一遍;HTTP/2 把它們壓縮,並記住已送過什麼,於是重複的標頭幾乎不花成本。它還加入了伺服器推送(server push),讓伺服器在你開口前就先送出它知道你會需要的資源(不過推送證明難以用好,如今大致已被棄用)。
誠實的癥結在於,HTTP/2 仍跑在單一條 TCP 連線上,而 TCP 本身嚴格依序交付位元組。所以一旦某個 TCP 封包遺失,TCP 就會把它後面的所有位元組全扣住,直到重送的封包抵達——即使那些位元組屬於一條完全不同、毫不相干的 HTTP/2 串流。HTTP/2 治好了 HTTP 這一層的行頭阻塞,卻沒治好底下 TCP 那一層的。這個殘留在 TCP 層的行頭阻塞,正是 HTTP/3 改用 QUIC 所要消除的問題。
一個頁面要載入 50 個小圖示。在 HTTP/1.1 下,瀏覽器開 6 條連線、把請求分配到各條上,而一個卡住的圖示會阻塞它那條佇列。在 HTTP/2 下,單一條連線把 50 個全當作交錯的訊框一次承載;瀏覽器立刻請求所有東西,回應一準備好就送達,而不必在隊伍裡等輪到自己。
HTTP/2 在單一條連線上交錯傳送許多請求/回應的訊框,並壓縮重複的標頭。
HTTP/2 並沒有改變 HTTP 的語意——方法、狀態碼與標頭都一模一樣,它只改了線路上的編碼格式。而且它只解決了 TCP 之上的行頭阻塞,沒解決 TCP 之內的;這就是 HTTP/3 存在的原因。