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 片段,在这种情况下,浏览器将发送一个对 a.css
的请求,同时接收您的 PUSH_PROMISE
。现在服务器将为 a.css
生成两个响应。在写入响应之前调用 Push 可以完全避免这种情况。
何时使用服务器推送
只要您的网络链路处于空闲状态,就可以考虑使用服务器推送。刚发送完 Web 应用的 HTML?不要浪费时间等待,开始推送客户端需要的资源。您是否将资源内联到 HTML 文件中以减少延迟?尝试推送而不是内联。重定向是使用推送的另一个好时机,因为在客户端跟随重定向的过程中几乎总会浪费一个往返。使用推送的场景有很多——我们才刚刚开始。
如果我们不提及一些注意事项,那就是不负责任的。首先,您只能推送您的服务器负责的资源——这意味着您不能推送托管在第三方服务器或 CDN 上的资源。其次,除非您确信客户端确实需要这些资源,否则不要推送它们,否则您的推送会浪费带宽。推论是避免在客户端可能已经缓存了这些资源时推送资源。第三,推送页面上所有资源的朴素方法通常会使性能变差。如有疑问,请测量。
以下链接是很好的补充阅读材料
结论
使用 Go 1.8,标准库提供了开箱即用的 HTTP/2 服务器推送支持,让您更灵活地优化 Web 应用程序。
访问我们的 HTTP/2 服务器推送演示 页面,查看其实际效果。
下一篇文章:介绍开发者体验工作组
上一篇文章:Go 2016 年度调查结果
博客索引