什么是 HTTP/2 Server Push
HTTP/2
是 Web 开发的新标准,拥有很多不错的优点能够让 Web 访问更快且开发的工作更轻松简单。比如,引入多路复用传输不用合并资源,服务器推送(Server Push)资源让浏览器预加载。
服务器推送(Server Push)工作方式是通过在一个 HTTP/2
请求中捆绑多个资源。在底层,服务器会发送一个 PUSH_PROMISE
,客户端(包括浏览器)就可以利用它且不基于 HTML
文件是否需要该资源。如果浏览器检测到需要该资源,就会匹配到收到的服务器推送的 PROMISE
然后让该资源表现的就像正常的浏览器 Get
请求资源。显而易见,如果匹配到有推送,浏览器就不需要重新请求,然后直接使用客户端缓存。这推荐几篇文章关于服务器推送(Server Push)的好处:
HTTP/2 和 Node.js
HTTPS密钥和证书
要在浏览器(Firefox, Safari, Chrome, 或者 Edge)中访问使用 HTTPS ,你需要生成密钥和证书。去搜索 “ssl 密钥生成” 或者按照以下步骤去生成密钥、证书。在该文提供的源码中没有上传生成的密钥和证书
$ mkdir http2-node-server-push
$ cd http2-node-server-push
$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048
...
$ openssl rsa -passin pass:x -in server.pass.key -out server.key
writing RSA key
$ rm server.pass.key
$ openssl req -new -key server.key -out server.csr
...
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
...
A challenge password []:
...
$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
按照以上步骤,你就会产生三个 SSL 文件:
- server.crt
- server.csr
- server.key
你就可以在 Node.js 的 server
脚本中读取 server.key 和 server.crt。
搭建项目
首先,通过 package.json 初始化项目和下载项目依赖:
npm init -y
npm i express@4.14.0 morgan@1.7.0 spdy@3.4.0 --save
npm i node-dev@3.1.1 --save-dev
当前的目录结构如下
/http2-node-server-push
/node_modules
- index.js
- package.json
- server.crt
- server.csr
- server.key
然后,在 package.json
的 scripts
中添加两个脚本行,去简化命令(node-dev、自动重载):
"start": "./node_modules/.bin/node-dev .",
"start-advanced": "./node_modules/.bin/node-dev index-advanced.js"
现在就可以开始使用 Node.js 、 Express.js 、 spdy 编写这个简单实现的带服务器推送 HTTP/2 服务器
编写脚本
const http2 = require('spdy')
const logger = require('morgan')
const express = require('express')
const app = express()
const fs = require('fs')
然后,设置 morgan logger
来监听服务器服务了哪些请求。
app.use(logger('dev'))
设置主页,该页面显示了 /pushy
是我们服务器推送的页面。
app.get('/', function (req, res) {
res.send(`hello, http2! go to /pushy`)
})
服务器推送只需简单的调用 spdy
实现的 res.push
,我们将文件路径名传输进去作为第一个参数,浏览器会使用这个路径名来匹配 push promise
资源。res.push()
的第一个参数 /main.js
一定得跟 HTML 文件中需要的文件名相匹配。
而第二个参数是一个可选的对象,设置了该资源的一些资源信息描述。
app.get('/pushy', (req, res) => {
var stream = res.push('/main.js', {
status: 200, // optional
method: 'GET', // optional
request: {
accept: '*/*'
},
response: {
'content-type': 'application/javascript'
}
})
stream.on('error', function() {
})
stream.end('alert("hello from push stream!");')
res.end('<script src="/main.js"></script>')
})
你可以看到,stream
对象有两个方法 on
和 end
。前者监听了 error
和 finish
事件,而后者则监听完成传输 end
,然后就会 main.js
就会触发弹窗。
或者,如果你拥有多个数据块,你可以选择使用 res.write()
然后最后使用 res.end()
,其中 res.end()
会自动关闭结束 response
而 res.write()
则让它保持开启。(该文的源码中未实现这种情况)
最后,读取 HTTPS 密钥和证书并使用 spdy
启动运转服务器。
var options = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt')
}
http2
.createServer(options, app)
.listen(8080, ()=>{
console.log(`Server is listening on https://localhost:8080.
You can open the URL in the browser.`)
}
)
启动和对比 HTTP/2 Server Push
服务器推送的效果再仔细看,可以看到请求是由 Push
开始发起的(Initiator列查看),没有使用服务器推送的 HTTP/2 服务器或者 HTTP/1,这一列就会显示文件名称,如 index.html
发起的显示就是 index.html
。
总结
HTTP/2 拥有很多很好的特性,服务器推送是最被看好的特性之一。它的好处就在于当浏览器请求页面的时候,同时发送必需的资源文件(图片,CSS 样式,JS 文件),而不需要等待客户端浏览器请求这些资源,从而做到更快的第一次渲染时间