Go 部落格
HTTP/2 服務端推送
引言
HTTP/2 的設計旨在解決 HTTP/1.x 的許多不足。現代網頁使用大量資源:HTML、樣式表、指令碼、影像等等。在 HTTP/1.x 中,每個資源都必須被明確請求。這可能是一個緩慢的過程。瀏覽器首先獲取 HTML,然後隨著解析和評估頁面,逐步瞭解更多資源。由於伺服器必須等待瀏覽器發出每個請求,網路通常處於空閒和利用不足的狀態。
為了改善延遲,HTTP/2 引入了伺服器推送,它允許伺服器在資源被明確請求之前就將其推送到瀏覽器。伺服器通常知道頁面所需的許多額外資源,並在響應初始請求時開始推送這些資源。這使得伺服器能夠充分利用原本空閒的網路並提高頁面載入時間。
在協議層面,HTTP/2 伺服器推送由 PUSH_PROMISE
幀驅動。PUSH_PROMISE
描述了伺服器預測瀏覽器在不久的將來會發出的請求。一旦瀏覽器收到 PUSH_PROMISE
,它就知道伺服器將傳送該資源。如果瀏覽器稍後發現它需要這個資源,它會等待推送完成,而不是傳送新的請求。這減少了瀏覽器等待網路的時間。
net/http 中的伺服器推送
Go 1.8 引入了從 http.Server
推送響應的支援。如果執行的伺服器是 HTTP/2 伺服器且傳入連線使用 HTTP/2,則此功能可用。在任何 HTTP 處理程式中,您都可以透過檢查 http.ResponseWriter 是否實現了新的 http.Pusher
介面來斷言它是否支援伺服器推送。
例如,如果伺服器知道渲染頁面需要 app.js
,則處理程式可以在 http.Pusher
可用時啟動推送
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if pusher, ok := w.(http.Pusher); ok { // Push is supported. if err := pusher.Push("/app.js", nil); err != nil { log.Printf("Failed to push: %v", err) } } // ... })
Push 呼叫會為 /app.js
建立一個合成請求,將該請求合成為一個 PUSH_PROMISE
幀,然後將合成請求轉發給伺服器的請求處理程式,該處理程式將生成推送的響應。Push 的第二個引數指定了要包含在 PUSH_PROMISE
中的附加頭。例如,如果對 /app.js
的響應因 Accept-Encoding 而異,則 PUSH_PROMISE
應包含 Accept-Encoding 值
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if pusher, ok := w.(http.Pusher); ok { // Push is supported. options := &http.PushOptions{ Header: http.Header{ "Accept-Encoding": r.Header["Accept-Encoding"], }, } if err := pusher.Push("/app.js", options); err != nil { log.Printf("Failed to push: %v", err) } } // ... })
一個完整的示例在此處提供。
如果您執行伺服器並載入 https://:8080,您的瀏覽器開發者工具應該顯示 app.js
和 style.css
是由伺服器推送的。

在響應之前開始推送
在傳送響應的任何位元組之前呼叫 Push 方法是個好主意。否則可能會意外生成重複的響應。例如,假設您編寫了部分 HTML 響應
<html>
<head>
<link rel="stylesheet" href="a.css">...
然後您呼叫 Push(“a.css”, nil)。瀏覽器可能在收到您的 PUSH_PROMISE 之前解析此 HTML 片段,在這種情況下,瀏覽器除了收到您的 PUSH_PROMISE
外,還會發送一個請求獲取 a.css
。現在伺服器將為 a.css
生成兩個響應。在寫入響應之前呼叫 Push 可以完全避免這種情況。
何時使用伺服器推送
只要您的網路連線處於空閒狀態,就可以考慮使用伺服器推送。剛剛傳送完您的 Web 應用的 HTML?不要浪費時間等待,開始推送客戶端所需的資源。您是否將資源內聯到 HTML 檔案中以減少延遲?與其內聯,不如嘗試推送。重定向是另一個使用推送的好時機,因為客戶端跟隨重定向時幾乎總是會浪費一次往返。使用推送的可能性有很多——我們才剛剛開始。
如果我們不提一些注意事項,那將是失職的。首先,您只能推送您的伺服器具有權威性的資源——這意味著您不能推送託管在第三方伺服器或 CDN 上的資源。其次,除非您確信客戶端確實需要這些資源,否則不要推送,否則您的推送會浪費頻寬。一個推論是,當客戶端很可能已經快取了這些資源時,避免推送。第三,天真地推送頁面上的所有資源往往會使效能變差。有疑問時,進行衡量。
以下連結是很好的補充閱讀材料
結論
透過 Go 1.8,標準庫提供了對 HTTP/2 伺服器推送的開箱即用支援,為您最佳化 Web 應用提供了更大的靈活性。
前往我們的 HTTP/2 伺服器推送演示頁面,親身體驗一下。
下一篇文章:介紹開發者體驗工作組
上一篇文章:Go 2016 年度調查結果
部落格索引