前幾天晚上前同事陶百貼了個 Tweet,說到 Chrome 要移除 HTTP/2 Server Push 了:
HTTP/2 PUSH is finally going away in Chrome 106: https://t.co/FFL8hmkRyf
-- Patrick Meenan (@patmeenan) August 17, 2022
HTTP 103 is the best way to early-hint out-of-band.
Thanks to the community and teams that made this possible (standards teams for 103, CDNs for implementing, Net team, huge effort)
仔細看一下,發現原來大家用 Server Push 都還是為了提升網頁第一屏的速度,但是 Server Push 一直有一些難解的問題,像是不知道 client 端有沒有 cache,實作和支援比較麻煩,而 Chrome 要移除 Server Push 前,其實先實作了 RFC-8279 的 HTTP 103: Early Hints,為的就要讓 Server Push 現在作的事情先有替代方案。
Early Hints 應該算是 Fastly 提出的,RFC 文件作者是 Kazuho Oku,實際上應該也有其他 Fastly 的人參與構思和試驗,支援 Early Hints 的環境下,一個 HTTP request 看起來就像是下面這樣:
Client request:
GET / HTTP/1.1
Host: example.com
Server response:
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
<!doctype html>
[... rest of the response body is omitted from the example ...]
很特別的,就是在於有兩段 response,第一段就是 103 的 status code,然後內容就是 Link headers 了,接著才是常見的 200 回應,看到這邊,自然的出現第一個問題:現有的瀏覽器能相容嗎?
這個問題在 Stack Overflow 也有人問,結果回答在 RFC 文件內其實就有,只不過是放在第三章的安全性那邊,我一開始也因為先跳過這章而沒發現,總之關於這個問題,就是如果是 HTTP/2 的話,就比較沒問題,HTTP/1.1 的話,理論上應該要可以相容(沒功能但是也不出錯),但是無法保證現在有在用的 HTTP/1.1 client 都有正確的處理 1xx response,所以比較建議是 HTTP/2 才回 103。
過了兩天後,我更仔細的研究一下,發現其實早在 HTTP/1.1 時,就有把 1xx 的處理需求定義好了:
A client MUST be able to parse one or more 1xx responses received prior to a final response, even if the client does not expect one. A user agent MAY ignore unexpected 1xx responses.
就是說早在 HTTP/1.1 時的設計,就允許 1xx 接 200 的回應,而且還應該要支援多個 1xx 回應,而最後的那個 200(其實是 2xx 到 5xx 都可以),則是稱為 final response,至於這處理的方式,在 WHATWG 的 fetch 的 4.7 章則有清楚的寫下流程,在該章節的第九項裡面的第五子項目,寫成程式碼大概長成:
while (true) {
const response = await networkTransmit();
const status = response.statusCode;
if (status >= 100 && status <= 199) {
// handle 1xx response
continue;
} else {
break;
}
}
// handle final response
所以理論上,Early Hints 的設計在正確支援 HTTP/1.1 但是還沒有支援 Early Hints 的瀏覽器就應該要可以正常的略過,而不會把它當成是 final response。
解決完第一個問題後,接著來仔細的看看剛剛範例的 server response:
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
HTTP/1.1 200 OK
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1234
Content-Type: text/html; charset=utf-8
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
<!doctype html>
[... rest of the response body is omitted from the example ...]
不知道會不會有人疑惑,為什麼不直接用 200 response 裡面回應的 Link header 就好了?其實我一開始也是這樣想,不過這完全是因為這個問題落入身為前端工程師的我的盲點之中,因為現在前端開發主流是 SPA,通常 HTTP server 回的就是一個靜態的 HTML 檔案,所以回應速度超快。不過,如果回應的 HTML 文件,是由程式語言動態生成的,或許還需要查詢一下資料庫之類的,那這個回應時間就會變慢了,而 HTTP 103 Early Hints 就是在這種狀態下用的,在你的 server 端程式開始處理 request 之前,就先丟 103 的 status code 和 Early Hints 的內容回給瀏覽器,然後才接著處理資料和生成 HTML 文件,這種情境下,Early Hints 就顯得比較有差異了。Nitropack 的文章就解釋的很清楚,還有附上詳細的說明圖。
相較於 Server Push,其實 Early Hints 的設計簡單很多,所有的傳輸還是從 client 端看有沒有 cache ,決定要不要發 request,而這種操作已經非常成熟(相較於 server push),相信很多地方可以直接使用現有的程式碼來實作,最大的隱憂,就只是不相容 HTTP/1.0,然後會擔心有 HTTP/1.1 的 client 端沒正確實作吧,畢竟 1xx 的處理機制雖然早早就設計好,但是實際上 1xx 有被廣泛使用也是這幾年的事。
目前 Chrome 是從 103 開始支援 Early Hints 的,並且預計在 106 正式移除 Server Push,至於其他瀏覽器則是都還沒有支援, Firefox 是有計畫要支援,進度有點緩慢就是。
最後,Fastly 其實有提供一個測試用的網站:https://early-hints.fastlylabs.com/,不過這個網站不是用來測試你的瀏覽器支不支援 Early Hints 的,而是用來測試先 103 然後接 200 的 response 會不會有非預期的問題(也就是相容性的測試),如果想要直接看看來回的內容,也可以直接用 curl:
curl -v https://early-hints.fastlylabs.com