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://localhost: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 年度调查结果
博客索引