HTTP/2 的主要作用就是提高网页的性能
HTTP 中的头部字段是直接发送文本格式的,但在 HTTP/2 中则会经过压缩后再发送。过去,在相同的 TCP 连接中,只有当上一个响应发送完成后,服务器才会开始发送下一个响应。但在 HTTP/2 中,则可以一起发送多个响应。
使用 HTTP/2 协议时,服务器端推送是唯一要求开发人员自己配置的功能,而对于其它功能,游览器和 Web 服务器都可以自动实现。
本文接下来的章节,将会介绍 HTTP/2 服务器端推送的原理和配置方法
传统的方法
假设有一个非常简单的 HTML 页面 index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <link rel="stylesheet" href="forum.css" /> </head> <body> <h1>你好,世界</h1> <p>简单教程,简单编程</p> <img src="img1.jpg"> </body> </html>
该页面包含了一个样式表 forum.css
和一张简单的图片 img1.jpg
。当我们访问这个页面时,浏览器会发起三个请求。第一个请求就是 index.html
GET /index.html HTTP/1.1
服务器端接收到这个请求后,就会发送 index.html
的源码给浏览器。浏览器接收到响应后,解析然后会判断出该页面包含了样式表和图片,于是,就会继续发起另外两个请求
GET forum.css HTTP/1.1
GET img1.jpg HTTP/1.1
这就是传统的 Web 请求方法。这样的方法有两个问题:
- 首先,它至少需要少两轮的 HTTP 通讯。
- 其次,在收到样式表之前,页面会短暂的显示 HTTP 标签的默认样式,一旦这种显示超过 2 秒,用户体验将非常不友好
对传统方法的改进
一种解决方案是将外部的资源整合到 Web 文件中以减少 HTTP 请求。例如,将样式表的内容放入<style> 标记中,并将图像更改为 Base64
编码的 DATA URL
。
另一种解决方案就是预加载资源。也就是使用某些标记,提前告诉浏览器应该立即下载某些资源。例如,上面的代码可以改写为如下形式
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <link rel="preload" href="forum.css" as="style"> <link rel="preload" href="img1.jpg" as="image"> <link rel="stylesheet" href="forum.css" /> </head> <body> <h1>你好,世界</h1> <p>简单教程,简单编程</p> <img src="img1.jpg"> </body> </html>
对于上面这个范例,其实 preload
命令是不会发生啥作用的。但是,如果前一个网页使用该命令预加载下一个网页所需的资源,那么用户在打开下一个网页时会感觉很快
这两种方法看起来能解决我们的问题,但是它们也有几个缺点:
- 虽然第一种方法减少了 HTTP 请求,但它将不同类型的代码合并到一个文件中,这不利于代码优化。
- 第二种方法只是提前下载,不会减少 HTTP 请求。
服务器端推送
服务器推送的作用是服务器将各种资源推送到浏览器,尽管浏览器可能尚未收到来自服务器的任何请求。
例如,浏览器可能只是发起了对 index.html
的请求,但服务器可以将 index.html
、 forum.css
和 img1.jpg
一起发送给浏览器。在这种情况下,只需要进行一轮 HTTP 通信,浏览器将获得所有资源并提高性能
Nginx 实现
从 1.13.9
版本开始,Nginx 开始支持服务器端推送。
我们假设你已经安装好了高于 1.13.9
的 Nginx ,且配置文件位置为 /usr/local/nginx/conf/nginx.conf
。并把 localhost
的域名绑定到了 /www/localhost/html
目录
server { listen 80 http2; server_name localhost; location / { root /www/localhost/html; index index.html index.htm; } }
首先,移除可能存在的 /www/localhost/html/index.html
文件,然后创建一个新的 /www/locahost/html/index.html
文件然后输入我们刚开始那个范例中的代码,也就是
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <link rel="stylesheet" href="forum.css" /> </head> <body> <h1>你好,世界</h1> <p>简单教程,简单编程</p> <img src="img1.jpg"> </body> </html>
其次,在 /www/localhost/html
目录下新创建两个文件 forum.css
和 img1.jpg
。样式文件 forum.css
的内容如下
h1 { color: red; } p { color:#111; line-height:1.5; } img { height:100px; }
而对于 img1.jpg
,你可以直接下载 https://www.twle.cn/static/i/img1.jpg
最后,打开 Nginx 配置文件 /usr/local/nginx/conf/nginx.conf
输入以下内容
server { listen 80 http2; server_name localhost; location / { root /www/localhost/html; index index.html index.htm; http2_push /forum.css; http2_push /img1.jpg; } }
实际上,它只是在代码末尾添加了两行 http2_push
命令,如果用户请求的是 /
,那么服务器会同时推送 forum.css
和 img1.jpg
然后我们使用命名 nginx -s reload
重新加载配置文件,打开浏览器,输入 http://localhost/ 我们就可以看到下面的页面
在该页面上我们并不能直接看到服务器端推送。我们必须打开 「 开发者工具 」 然后切换到 「 network 面板 」下。然后我们就会看到只有发送了一个请求,但 forum.css
和 img1.jpg
都被推送了
HTTP/2 Apache 实现
在 Apache Web 服务器中实现 HTTP/2 是类似的。我们可以在配置文件 httpd.conf
或 .htaccess
中添加以下配置来打开 HTTP/2 推送
<FilesMatch "\index.html$"> Header add Link "</styles.css>; rel=preload; as=style" Header add Link "</img1.jpg>; rel=preload; as=image" </FilesMatch>
HTTP/2 后端实现
我们上面所讲的 HTTP/2 服务器端推送需要写入服务器配置文件中。这显然很不方便,因为每次进行更改时都必须重新启动服务。此外,也不应将应用程序和服务器配置放在一起。
还有另一种方式可以实现服务器端推送。后端应用程序可以生成头部字段 Link
命令。一旦服务器发现了该头部信息,就会启用服务器推送
Link: </styles.css>; rel=preload; as=style
如果我们想要推送多个资源,可以采用下面这种格式
Link: </styles.css>; rel=preload; as=style, </img1.jpg>; rel=preload; as=image
这样,我们的 Nginx 配置文件就可以改成如下格式
server { listen 80 ssl http2; # ... root /var/www/html; location = / { proxy_pass http://upstream; http2_push_preload on; } }
如果服务器或浏览器不支持 HTTP/2,则浏览器会预加载指定的资源文件以处理头部信息。
实际上,头部消息是由预加载标准提出的,其语法和 as
属性值都写在该标准中
缓存问题
但是,服务器端推送存在一个非常令人头疼的问题:如果浏览器已经缓存了需要被推送的资源,那么服务器端推送这个资源就会浪费带宽。即使需要被推送的文件的版本需要更新,浏览器也将会优先使用本地的缓存
一种解决方案是仅为首次使用的用户启用服务器推送。以下是 Nginx 官方给出的示例,它根据 cookie 确定是否是第一次访问
server { listen 80 http2 default_server; root /www/localhost/html; http2_push_preload on; location = /demo.html { add_header Set-Cookie "session=1"; add_header Link $resources; } } map $http_cookie $resources { "~*session=1" ""; default "</style.css>; as=style; rel=preload"; }
性能增强
服务器推送可以提高性能。作为在线评估的结果,启用该功能比未启用时快 8%
,比将所有资源都嵌入在网页中的 HTTP/1 快 5%
可以看出,时间并没有减少很多,大约是几百毫秒。此外,不建议一次推送太多资源,这会影响性能,因为浏览器必须处理所有推送的资源。仅推送 CSS 样式表可能是一个不错的选择