经过前面几个章节的重复重复的灌输 HTTP/2 服务器推送的知识,想必你现在已经非常熟知要怎么在自己的网站中开启 HTTP/2 服务器端推送了。本章节,我们将使用 「 地球上最好的语言 」 - PHP 来讲解下如何使用 HTTP/2 服务器端推送
Nginx 开启服务器推送支持
我们使用的是最新的 Nginx 服务器,因为它从 1.13.9
版本开始正式支持服务器推送了,也就是说,你的 Nginx 服务器的版本必须是大于 1.13.9
的
其次,我们需要开启 ssl
支持,然后在 server
添加两个配置指令
listen 443 ssl http2; http2_push_preload on;
listen
中的http2
表示开启 HTTP/2 支持http2_push_preload on;
表示开启服务器推送的Link
头部字段支持;
如果你不知道怎么配置,可以参考我的代码
server { listen 80; server_name localhost; rewrite ^(.*)$ https://$host$1 permanent; } server { listen 443 ssl http2; server_name localhost; root /Users/luojianguo/Developer/www/htdocs; http2_push_preload on; ssl on; ssl_certificate /usr/local/etc/nginx/cert/localhost.crt; ssl_certificate_key /usr/local/etc/nginx/cert/localhost.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; ssl_session_timeout 60m; ssl_session_cache builtin:1000 shared:SSL:10m; ssl_dhparam /usr/local/etc/nginx/cert/localhost_dhparam.pem; ssl_stapling on; ssl_stapling_verify on; add_header Strict-Transport-Security "max-age=31536000;includeSubdomains;preload"; add_header X-Frame-Options "SAMEORIGIN"; location / { root /Users/luojianguo/Developer/www/htdocs; index index.html index.htm index.php; expires -1; add_header Cache-Control no-store; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /Users/luojianguo/Developer/www/htdocs; } # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ .*\.php(\/.*)*$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_split_path_info ^(.+\.php)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } location ~ /\.git { return 404; } }
开启服务器端推送的方式,只需在指定预加载的 HTTP 响应中添加 Link
头部字段,Nginx 就会自动将资源推送到支持服务器推送的 Web 浏览器 ( 几乎当前所有主流的浏览器都支持 )
为了说明这是多么容易,我创建了一个 PHP 小页面,该页面使用 PHP header
函数插入适当的 Link
头以通过 Nginx 将图像推送到 Web 浏览器
我们的页面看起来像如下
网页的内容为
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <style> h1 {color:red;} p {color:#111;} img {width:200px;} </style> </head> <body> <h1>你好,世界</h1> <p>简单教程,简单编程</p> <img src="img1.jpg" /> <p>简单教程,IT 编程入门第一站</p> <img src="img2.jpg"/> </body> </html>
其中两张图片的地址如下,你可以把它们下载到你自己的服务器上
如果服务器支持服务器推送,那么这两张图片将会和 index.php
一起被推送到浏览器。这是通过在 index.php
的响应头中添加两个 Link
头部字段来实现的
index.php
的响应看起来类似于
HTTP/1.1 200 OK Server: nginx/1.13.10 Date: Fri, 13 May 2016 10:52:13 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: keep-alive Link: </img1.jpg>; rel=preload; as=image Link: </img2.jpg>; rel=preload; as=image
最底部是两个 Link
头部字段,对应于页面上的两个图像,包含了 W3C 预先加载提案 中指定的 rel=preload
指令
把以上的网页和头部信息放在一起,一个 index.php
文件的内容如下
<?php function pushImage($uri) { header("Link: <{$uri}>; rel=preload; as=image", false); return '<img src="' . $uri . '"/>'; } $image1 = pushImage("/img1.jpg"); $image2 = pushImage("/img2.jpg"); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <style> h1 {color:red;} p {color:#111;} img {width:200px;} </style> </head> <body> <h1>你好,世界</h1> <p>简单教程,简单编程</p> <img src="img1.jpg" /> <p>简单教程,IT 编程入门第一站</p> <img src="img2.jpg"/> </body> </html>
我们必须在产生任何实际输出 ( 例如 HTML 代码 ) 之前生成响应头部信息,因此我们的代码先调用了一个名为 pushImage()
的函数,该函数内部使用 header()
函数生成 Link
响应头且返回图片的 HTML 代码。
请注意,Link
头部字段必须按照如下格式被添加
header("Link: <{$uri}>; rel=preload; as=image", false);
第二个参数 false
用于指示不要覆盖现有的 Link
头部字段。使用该参数,可以添加多个 Link
头部字段,否则,默认会使用最后一次调用 pushImage()
生成的 Link
头部
服务器推送的效果
为了理解服务器推送对范例的影响,我们需要使用 Google Chrome Canary
或最新的版本 ( 作者我使用的是最新的版本 ) ,并加载了两次页面 ( 一次没有 Link
头部,因此不会发生推送,一次也不会 )
下图是没有服务器推送的 HTTP/2 waterfall
「 Initiator 」中的 Parser
表示是由解析 index.php
时需要发起的请求。整个解析和加载过程消耗了 57ms
。但如果你看下图,其实 index.php
的下载过程只消耗了 14.07ms
也就是说,将近有一半的时间用于下载和和加载两张图片。
然后我们来看看开启了服务器推送的 HTTP/2
可以看到,页面加载总耗时 35ms
,其中加载和解析 index.php
使用了 22ms
,速度明显提升了好多
「 Initiator 」中的 「 Push/Other 」 表示这些资源是从服务器推送获取的。
当然了,这并不是 100% 明显发生在那里的事情,如果你深入研究 chrome://net-internals/
,可以看到详细的 HTTP/2
时间线
我已经编辑了协议的一些细节 ( 例如窗口大小的变化 ),以便专注于请求和响应
t=910212 [st= 1] HTTP2_SESSION_SEND_HEADERS --> exclusive = true --> fin = true --> has_priority = true --> :method: GET :scheme: https :path: /index.php pragma: no-cache cache-control: no-cache upgrade-insecure-requests: 1 accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 accept-encoding: gzip, deflate, sdch, br accept-language: en-US,en;q=0.8 cookie: [52 bytes were stripped] --> parent_stream_id = 0 --> priority = 0 --> stream_id = 1 [...] t=910404 [st= 193] HTTP2_SESSION_RECV_HEADERS --> fin = false --> :status: 200 date: Fri, 13 May 2016 11:28:16 GMT content-type: text/html content-encoding: gzip --> stream_id = 1 t=910405 [st= 194] HTTP2_SESSION_RECV_PUSH_PROMISE --> :method: GET :path: /img1.jpg :scheme: https accept-encoding: gzip, deflate, sdch, br --> id = 1 --> promised_stream_id = 2 t=910405 [st= 194] HTTP2_SESSION_RECV_PUSH_PROMISE --> :method: GET :path: /img2.jpg :scheme: https accept-encoding: gzip, deflate, sdch, br --> id = 1 --> promised_stream_id = 4 t=910405 [st= 194] HTTP2_SESSION_RECV_DATA --> fin = false --> size = 298 --> stream_id = 1 t=910405 [st= 194] HTTP2_SESSION_RECV_DATA --> fin = true --> size = 0 --> stream_id = 1 t=910409 [st= 198] HTTP2_SESSION_RECV_HEADERS --> fin = false --> :status: 200 date: Fri, 13 May 2016 11:28:16 GMT content-type: image/jpeg content-length: 49852 set-cookie: [124 bytes were stripped] etag: "5735aac0-12f99" last-modified: Fri, 13 May 2016 10:21:52 GMT cf-cache-status: HIT vary: Accept-Encoding expires: Fri, 13 May 2016 15:28:16 GMT cache-control: public, max-age=14400 accept-ranges: bytes server: cloudflare-nginx --> stream_id = 2 t=910409 [st= 198] HTTP2_SESSION_RECV_DATA --> fin = false --> size = 987 --> stream_id = 2 t=910409 [st= 198] HTTP2_SESSION_RECV_DATA --> fin = false --> size = 1369 --> stream_id = 2 t=910410 [st= 199] HTTP2_SESSION_RECV_DATA --> fin = false --> size = 1369 --> stream_id = 2
从上面可以看到,浏览器会发送 HTTP2_SESSION_SEND_HEADERS
请求,询问网页 ( 就是 index.php
),然后接收响应头部和页面,紧接着两个图像的两个推送承诺。 然后立即开始接收图像数据