Go 部落格

HTTP/2 伺服器推送

Jaana Burcu Dogan 和 Tom Bergan
2017 年 3 月 24 日

簡介

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://127.0.0.1:8080,瀏覽器的開發人員工具應該會顯示伺服器已推送 app.jsstyle.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 伺服器推送的支援,讓您擁有更多彈性來最佳化您的網路應用程式。

請至我們的 HTTP/2 伺服器推送展示 頁面,進一步瞭解它的實際運作情況。

下一篇:介紹開發人員體驗工作小組
上一篇:2016 年 Go 問卷調查結果
部落格索引