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 服务器推送支持,让您能够更灵活地优化 Web 应用程序。

访问我们的HTTP/2 服务器推送演示页面以查看其运行情况。

下一篇文章:介绍开发者体验工作组
上一篇文章:Go 2016 年调查结果
博客索引