HTTP/2 Server Push 教程

2020-06-28

Ann Ann

一、传统的网页请求方式

下面是一个非常简单的 HTML 网页文件index.html

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>hello world</h1>
  <img src="example.png">
</body>
</html>

这个网页包含一张样式表style.css和一个图片文件example.png。为了渲染这个网页,浏览器会发出三个请求。第一个请求是index.html

GET /index.html HTTP/1.1

服务器收到这个请求,就把index.html发送给浏览器。浏览器发现里面包含了样式表和图片,于是再发出两个请求。

GET /style.css HTTP/1.1
GET /example.png HTTP/1.1

这就是传统的网页请求方式。它有两个问题,一是至少需要两轮 HTTP 通信,二是收到样式文件之前,网页都会显示一片空白,这个阶段一旦超过2秒,用户体验就会非常不好。

二、传统方式的改进

一种解决办法就是把外部资源合并在网页文件里面,减少 HTTP 请求。比如,把样式表的内容写在<style>标签之中,把图片改成 Base64 编码的 Data URL

另一种方法就是资源的预加载(preload)。网页预先告诉浏览器,立即下载某些资源。比如,上例可以写成下面这样。

<link rel="preload" href="/styles.css" as="style">
<link rel="preload" href="/example.png" as="image">

对于上例来说,preload命令并没有什么帮助。但是,如果前一个网页就使用这个命令,预加载后一个网页需要的资源,那么用户打开后一个网页时,就会感觉速度飞快。

这两种方法都有缺点。第一种方法虽然减少了 HTTP 请求,但是把不同类型的代码合并在一个文件里,违反了分工原则。第二种方法只是提前了下载时间,并没有减少 HTTP 请求。

三、服务器推送的概念

服务器推送(server push)指的是,还没有收到浏览器的请求,服务器就把各种资源推送给浏览器。

比如,浏览器只请求了index.html,但是服务器把index.htmlstyle.cssexample.png全部发送给浏览器。这样的话,只需要一轮 HTTP 通信,浏览器就得到了全部资源,提高了性能。

四、Nginx 实现

Nginx 从 1.13.9 版开始,支持服务器推送。前一篇教程已经做好了 Nginx 容器,接着就来体验一下。

首先,进入工作目录,把原来的首页删除。

$ cd nginx-docker-demo
$ rm html/index.html

然后,新建html/index.html文件,写入本文第一节的网页源码。

另外,html子目录下面,还要新建两个文件example.pngstyle.css。前者可以随便找一张 PNG 图片,后者要在里面写一些样式。

h1 {
  color: red;
}

最后,打开配置文件conf/conf.d/default.conf,将 443 端口的部分改成下面的样子。

server {
    listen 443 ssl http2;
    server_name  localhost;

    ssl                      on;
    ssl_certificate          /etc/nginx/certs/example.crt;
    ssl_certificate_key      /etc/nginx/certs/example.key;

    ssl_session_timeout  5m;

    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers   on;

    location / {
      root   /usr/share/nginx/html;
      index  index.html index.htm;
      http2_push /style.css;
      http2_push /example.png;
    }
}

其实就是最后多了两行http2_push命令。它的意思是,如果用户请求根路径/,就推送style.cssexample.png

现在可以启动容器了。

$ docker container run \
  --rm \
  --name mynginx \
  --volume "$PWD/html":/usr/share/nginx/html \
  --volume "$PWD/conf":/etc/nginx \
  -p 127.0.0.2:8080:80 \
  -p 127.0.0.2:8081:443 \
  -d \
  nginx

打开浏览器,访问 https://127.0.0.2:8081 。浏览器会提示证书不安全,不去管它,继续访问,就能看到网页了。

网页上看不出来服务器推送,必须打开“开发者工具”,切换到 Network 面板,就可以看到其实只发出了一次请求,style.cssexample.png都是推送过来的。

查看完毕,关闭容器。

$ docker container stop mynginx

五、Apache 实现实现

Apache 也类似,可以在配置文件httpd.conf或者.htaccess里面打开服务器推送。

<FilesMatch "\index.html$">
    Header add Link "</styles.css>; rel=preload; as=style"
    Header add Link "</example.png>; rel=preload; as=image"
</FilesMatch>

六、后端实现

上面的服务器推送,需要写在服务器的配置文件里面。这显然很不方便,每次修改都要重启服务,而且应用与服务器的配置不应该混在一起。

服务器推送还有另一个实现方法,就是后端应用产生 HTTP 回应的头信息Link命令。服务器发现有这个头信息,就会进行服务器推送。

Link: </styles.css>; rel=preload; as=style

如果要推送多个资源,就写成下面这样。

Link: </styles.css>; rel=preload; as=style, </example.png>; rel=preload; as=image

可以参考 GoNodePHP 的实现范例。

这时,Nginx 的配置改成下面这样。

server {
    listen 443 ssl http2;

    # ...

    root /var/www/html;

    location = / {
        proxy_pass http://upstream;
        http2_push_preload on;
    }
}

如果服务器或者浏览器不支持 HTTP/2,那么浏览器就会按照 preload 来处理这个头信息,预加载指定的资源文件。

事实上,这个头信息就是 preload 标准提出的,它的语法和as属性的值都写在了标准里面。

七、缓存问题

服务器推送有一个很麻烦的问题。所要推送的资源文件,如果浏览器已经有缓存,推送就是浪费带宽。即使推送的文件版本更新,浏览器也会优先使用本地缓存。

一种解决办法是,只对第一次访问的用户开启服务器推送。下面是 Nginx 官方给出的示例,根据 Cookie 判断是否为第一次访问。

server {
    listen 443 ssl http2 default_server;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/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";
}

八、性能提升

服务器推送可以提高性能。网上测评的结果是,打开这项功能,比不打开时的 HTTP/2 快了8%,甚至比将资源都嵌入网页的 HTTP/1 快了5%。

可以提到,提升程度也不是特别多,大概是几百毫秒。而且,也不建议一次推送太多资源,这样反而会拖累性能,因为浏览器不得不处理所有推送过来的资源。只推送 CSS 样式表可能是一个比较好的选择。

近期开课hot

Python零基础入门

start2025/02/12 03:14 (Sydney)

Web全栈班24期 NodeJS方向

start2024/12/08 11:30 (Sydney)

logo

Follow Us

linkedinfacebooktwitterinstagramweiboyoutubebilibilitiktokxigua

We Accept

/image/layout/pay-paypal.png/image/layout/pay-visa.png/image/layout/pay-master-card.png/image/layout/pay-stripe.png/image/layout/pay-alipay.png

地址

Level 10b, 144 Edward Street, Brisbane CBD(Headquarter)
Level 2, 171 La Trobe St, Melbourne VIC 3000
四川省成都市武侯区桂溪街道天府大道中段500号D5东方希望天祥广场B座45A13号
Business Hub, 155 Waymouth St, Adelaide SA 5000

Disclaimer

footer-disclaimerfooter-disclaimer

JR Academy acknowledges Traditional Owners of Country throughout Australia and recognises the continuing connection to lands, waters and communities. We pay our respect to Aboriginal and Torres Strait Islander cultures; and to Elders past and present. Aboriginal and Torres Strait Islander peoples should be aware that this website may contain images or names of people who have since passed away.

匠人学院网站上的所有内容,包括课程材料、徽标和匠人学院网站上提供的信息,均受澳大利亚政府知识产权法的保护。严禁未经授权使用、销售、分发、复制或修改。违规行为可能会导致法律诉讼。通过访问我们的网站,您同意尊重我们的知识产权。 JR Academy Pty Ltd 保留所有权利,包括专利、商标和版权。任何侵权行为都将受到法律追究。查看用户协议

© 2017-2024 JR Academy Pty Ltd. All rights reserved.

ABN 26621887572