HTTP/2 服务器推送 - PHP 中的 Server Push

yufei       6 年, 4 月 前       3058

经过前面几个章节的重复重复的灌输 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 ),然后接收响应头部和页面,紧接着两个图像的两个推送承诺。 然后立即开始接收图像数据

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.