0%

【译】HTTP/2 Server Push

入门

HTTP/2 旨在解决许多 HTTP/1.x 的缺点。现在的网页使用很多 HTML、CSS、JavaScript、图片等资源,在 HTTP/1.x 中,每个资源必须被显式请求。这会是一个漫长的过程。浏览器先获取 HTML,然后一边解析页面,一边获取更多资源。服务器必须等着浏览器发起每次请求,这就导致网络会经常空闲、未充分利用起来。

为了改善延迟,HTTP/2 引入了 Server PushServer Push 允许资源在明确被请求之前,由服务器推送这些资源给浏览器。服务器通常知道需要加载哪些额外资源,因此可以在响应初始请求时一起推送这些资源。这让服务器能充分利用剩余空闲带宽来改善页面加载时间。

serverpush.svg

在协议层,HTTP/2 Server PushPUSH_PROMISE 帧发起。 一个 PUSH_PROMISE 表明了服务器预测浏览器将会发起的请求。浏览器一接收了该 PUSH_PROMISE,就会知道服务器将要分发资源。若浏览器之后发现需要这个资源,它就会等着该 Push 完成,而不是发送一个新请求。这就减少了浏览器在网络上花的时间。

net/http 包中的 Server Push

Go 1.8 从 http.Server 中引入了对 Server Push 响应的支持。如果运行的是 HTTP/2 的服务器,并且进来的连接使用了 HTTP/2 ,这个特性就能用了。在每个 HTTP 的处理程序中,你都可以通过检查 http.ResponseWriter 是否实现了 http.Pusher接口,来判断服务器是否支持 Server Push

例如,如果服务器知道渲染页面时需要 app.js,处理程序可以在 http.Pusher 可用时这样初始化一个 Push:

1
2
3
4
5
6
7
8
9
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 创建了一个合成请求,把 app.js 合并到 Push_Promise 中,然后将合成请求转发给服务器处理程序,服务器处理程序会生成响应。Push 的第二个参数指定了包含在 Push_PROMISE中的额外 header。 例如,如果 app.js 的响应有 Accept-Encoding 上的不同,那 PUSH_PROMISE 应该包括 Access-Encoding 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}
}
// ...
})

完整示例见:

1
$ go get golang.org/x/blog/content/h2push/server

运行服务器,加载 https://localhost:8080,就能在浏览器开发者工具上看到服务器 推送了 app.jsstyle.css

networktimeline.png

在响应之前 Push

在发送响应之前调用 Push 方法是个好主意,否则可能会意外产生重复响应。例如,假设写入了部分响应:

1
2
3
<html>
<head>
<link rel="stylesheet" href="a.css">...

然后调用 Push("a.css", nil),浏览器可能会在接收到 PUSH_PROMISE 之前开始解析 HTML。这会导致除了接收到 PUSH_PROMISE ,还会再发起一个 a.css 请求,服务器会生成 2 次 a.css。 在写入响应之前调用 Push 完全避免掉了这种可能。

什么时候使用 Server Push

考虑在网络空闲的情况下使用 Server Push。刚给你的 web 应用发送完 HTML?别等了,开始使用 Server Push 推送客户端需要的资源吧。还在使用内联资源(inline-style-sheet)来减小延迟吗?别用了,换成 Server Push吧。重定向是另一个使用 Server Push 的好时机,因为客户端这时候正在往返请求上浪费时间。还有很多可以使用 Server Push 的情形,我们仅仅是做个入门。

要是一下几点不提一下的话是我们的失职。

  1. 只能推送你服务器有权推送的资源,第三方服务器或者 CDN 的资源无法推送。

  2. 除非确信客户端需要这些资源,否则别推送,不然的话会浪费带宽。要是浏览器已经缓存了这些资源的话,必须要避免重复推送。

  3. 天真地推送所有资源给页面会让性能更糟,在不能确定的情况下,要慎重。

以下资源做了不错的额外补充:

尾声

在 Go 1.8 中,标准库为 HTTP/2 Server Push 提供了一个开箱即用的支持,为优化你的网页应用提供了更多灵活性。

打开 HTTP/2 Server Push demo 看一下 Server Push 的实际效果。

image.png

参考资料 & 说明

[ 1 ] 本文翻译自 Go Blog 的 HTTP/2 Server Push

[ 2 ] 本文在翻译过程中参考了 在 Go 中使用 HTTP/2 Server Push。该文章源地址在 在 Go 中使用 HTTP/2 Server Push

[ 3 ] 本文在翻译过程中进行了小部分的演绎,请知悉。由于本人水平有限,如有错讹,可以在留言区指出,不胜感激。