HTTP/2 Server Push
Jaana Burcu Dogan, Tom Bergan
24 March 2017
介绍
HTTP / 2 旨在解决 HTTP/1.x 的许多问题。在现代网页使用许多资源(HTML,样式表,脚本,图像等)在HTTP/1.x 中都必须显式的逐个请求这些资源。这通常是一个缓慢的过程。浏览器首先获取HTML,然后在解析和评估页面时逐步监测到需要获取的更多资源。由于服务器必须等待浏览器发出每个文件获取请求,因此网络通常处于空闲状态且利用率较低。
为了减少延迟,HTTP/2引入了 Server Push ,它允许服务器在显式请求资源之前将资源推送到浏览器。服务器通常知道页面需要的许多其他资源,并且可以在响应初始请求时开始推送这些资源。这使服务器可以充分利用原本空闲的网络并缩短页面加载时间。
在协议级别,HTTP/2服务器推送由PUSH_PROMISE
帧实现。 PUSH_PROMISE
描述服务器预测浏览器将在不久的将来发出的请求。一旦浏览器收到PUSH_PROMISE
,它就会知道服务器将交付资源。如果浏览器后来发现它需要此资源,它将等待推送完成,而不是发送新请求。这减少了浏览器在网络上花费的时间。
在 net/http 包中的 Server Push
Go 1.8 引入了支持从 http.Server
推送响应的支持。如果正在运行的服务器是支持 HTTP/2 的服务器,并且传入连接使用 HTTP/2,则可以使用此功能。在任何 HTTP handler 程序中都可以通过检查 http.ResponseWriter 是否实现新的 [http.Pusher
](golang.org/pkg/net/http/# Pusher) 接口来判定是否可以使用 Server Push。
例如,如果服务器知道需要 app.js
来呈现页面,则当 http.Pusher
可用时 handler 程序可以启动推送:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if pusher, ok := w.(http.Pusher); ok {
// 支持 Push
if err := pusher.Push("/app.js", nil); err != nil {
log.Printf("Failed to push: %v", err)
}
}
// ...
})
Push调用为/app.js
创建一个请求,将该请求使用了PUSH_PROMISE
帧并将将之发送到请求处理程序生成响应。 Push 的第二个参数指定要包含在PUSH_PROMISE
中的其他 headers。例如,如果对/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)
}
}
// ...
})
完整的示例可以参考:
$ go get golang.org/x/blog/content/h2push/server
如果运行服务器并启动 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 则可以避免这种可能性。
何时使用 Server Push
当网络链接空闲时,请考虑使用 Server Push。刚为您的网络应用发送了HTML?不要浪费时间等待而是直接将客户端所需的资源推送。是否将资源内联到 HTML 文件中以减少延迟?请尝试推送而停止使用内联。重定向是使用 Server Push 的另一个好时机,因为客户端遵循重定向时,几乎总是浪费了时间进行二次请求。另外也有很多可以使用 Server Push 的场景 —— 以上仅仅是一些小例子。
另外有一些注意事项。首先,您只能推送服务器具有权威性的资源,即不能推送第三方服务器或 CDN 上托管的资源。其次,除非您确信客户端实际需要这些资源否则不要推送资源,否则反而会浪费带宽。例如是当客户端可能已经缓存了资源时则不要再推送。第三,在页面上不加思考的推送所有资源通常会使性能变差。如有疑问,可以进行一下评估测试。
阅读以下链接有助于您更好地实现:
- HTTP/2 Push: The Details
- Innovating with HTTP/2 Server Push
- Cache-Aware Server Push in H2O
- The PRPL Pattern
- Rules of Thumb for HTTP/2 Push
- Server Push in the HTTP/2 spec
总结
Go 1.8 标准库提供了对 HTTP/2 Server Push 的开箱即用支持,从而为您提供了更大的灵活性来优化Web应用程序。
可以访问 HTTP/2 Server Push 演示页面以查看它的行为。
Start Your Pushes Before You Respond
It's a good idea to call the Push method before sending any bytes of the response. Otherwise it is possible to accidentally generate duplicate responses. For example, suppose you write part of an HTML response:
<html>
<head>
<link rel="stylesheet" href="a.css">...
Then you call Push("a.css", nil). The browser may parse this fragment of HTML before it receives your PUSH_PROMISE, in which case the browser will send a request for a.css
in addition to receiving your PUSH_PROMISE
. Now the server will generate two responses for a.css
. Calling Push before writing the response avoids this possibility entirely.
When To Use Server Push
Consider using server push any time your network link is idle. Just finished sending the HTML for your web app? Don't waste time waiting, start pushing the resources your client will need. Are you inlining resources into your HTML file to reduce latency? Instead of inlining, try pushing. Redirects are another good time to use push because there is almost always a wasted round trip while the client follows the redirect. There are many possible scenarios for using push -- we are only getting started.
We would be remiss if we did not mention a few caveats. First, you can only push resources your server is authoritative for -- this means you cannot push resources that are hosted on third-party servers or CDNs. Second, don't push resources unless you are confident they are actually needed by the client, otherwise your push wastes bandwidth. A corollary is to avoid pushing resources when it's likely that the client already has those resources cached. Third, the naive approach of pushing all resources on your page often makes performance worse. When in doubt, measure.
The following links make for good supplemental reading:
- HTTP/2 Push: The Details
- Innovating with HTTP/2 Server Push
- Cache-Aware Server Push in H2O
- The PRPL Pattern
- Rules of Thumb for HTTP/2 Push
- Server Push in the HTTP/2 spec
Conclusion
With Go 1.8, the standard library provides out-of-the-box support for HTTP/2 Server Push, giving you more flexibility to optimize your web applications.
Go to our HTTP/2 Server Push demo page to see it in action.
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。