HTTP 103 Early Hints

前幾天晚上前同事陶百貼了個 Tweet,說到 Chrome 要移除 HTTP/2 Server Push 了:

仔細看一下,發現原來大家用 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