[译] 通过 Node.js, Express.js 实现 HTTP/2 Server Push

原文:Optimize Your App with HTTP/2 Server Push Using Node and Express
作者:Azat Mardan
代码:http2-node-server-push


什么是 HTTP/2 Server Push

HTTP/2 是 Web 开发的新标准,拥有很多不错的优点能够让 Web 访问更快且开发的工作更轻松简单。比如,引入多路复用传输不用合并资源,服务器推送(Server Push)资源让浏览器预加载。

该文不会讲述 HTTP/2 的所有优势。你可以通过上篇文章了解更多{% post_link http2-node-express %}。该文主要关注于在 Node.js 环境使用 Express.jsHTTP/2spdy

服务器推送(Server Push)工作方式是通过在一个 HTTP/2 请求中捆绑多个资源。在底层,服务器会发送一个 PUSH_PROMISE,客户端(包括浏览器)就可以利用它且不基于 HTML 文件是否需要该资源。如果浏览器检测到需要该资源,就会匹配到收到的服务器推送的 PROMISE 然后让该资源表现的就像正常的浏览器 Get 请求资源。显而易见,如果匹配到有推送,浏览器就不需要重新请求,然后直接使用客户端缓存。这推荐几篇文章关于服务器推送(Server Push)的好处:

这是个关于在 Node.js 实现服务器推送(Server Push)实践教程。为了更清晰精简,我们只实现一个路由地址 /pushyNode.jsExpress.js 服务器,它会推送一个 JS 文件,正如之前所说,我们会用到一个 HTTP/2spdy

HTTP/2 和 Node.js

先解释一下,为啥在 Node.js 环境选择 HTTP/2spdy。当前来说,为 Node.js 主要有两个库实现了 HTTP/2 :

两个库都跟 Node.js 核心模块的 httphttps 模块 api 很相似。这就意味着如果你不使用 Express ,这两个库就没什么区别。然而, spdy 库支持 HTTP/2Express,而 http2 库当前不支持 Express。这就是为什么我们选择使用 spdyExpressNode.js 适合搭配的实践标准的服务框架。之所以叫 spdy是来自于 Google 的 SPDY 协议后来升级成 HTTP/2

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.jsonscripts 中添加两个脚本行,去简化命令(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 服务器

编写脚本

首先,创建 index.js 脚本,并引入以及实例化依赖,看看查看上面的项目目录结构。其中,我使用了 ES6/ES2015 的语法 const 来声明依赖,如果你不熟悉该声明语法,你可以进一步阅读Top 10 ES6 Features Every Busy JavaScript Developer Must Know

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 对象有两个方法 onend。前者监听了 errorfinish 事件,而后者则监听完成传输 end,然后就会 main.js 就会触发弹窗。

或者,如果你拥有多个数据块,你可以选择使用 res.write() 然后最后使用 res.end(),其中 res.end() 会自动关闭结束 responseres.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.`)
  }
)

该实现的关键就在于,围绕着 streams(流)。不是树林中的河流,而是指开发者使用的从源头到客户端的建立起的数据通道流。如果你几乎不懂流以及 Node.jsExpress.js 的 HTTP 的请求和返回信息,你可以看看该文章 You Don’t Know Node

启动和对比 HTTP/2 Server Push

使用命令 node index.js 或者 npm stat 运行服务端脚本,然后访问 https://localhost:3000/pushy,就可以看到弹窗!而且我们在该路由不存在文件,你可以查看服务器终端的 logs ,只会有一个请求,而不是没使用服务器推送的时候的两个请求(一个 HTML、一个 JS)。

可以在浏览器中检测收到服务器端推送的行为。Chrome 启动开发者工具,打开 Network 标签,你可以看到 main.js 不存在绿色时间条,就是说明没有等待时间 TTFB (Time to First Byte)详细

服务器推送的效果

再仔细看,可以看到请求是由 Push 开始发起的(Initiator列查看),没有使用服务器推送的 HTTP/2 服务器或者 HTTP/1,这一列就会显示文件名称,如 index.html 发起的显示就是 index.html

实践就结束了,使用了 Express 和 Spdy 简单就实现了推送 JS 资源,而该资源可以用于后面 HTML 中 <script> 标签引入的。当然你也可以在脚本中使用 fs 来读取文件资源。事实上,这就是作者实现的 Express HTTP/2 静态资源中间件 设计原理,可以看看这篇文章

总结

HTTP/2 拥有很多很好的特性,服务器推送是最被看好的特性之一。它的好处就在于当浏览器请求页面的时候,同时发送必需的资源文件(图片,CSS 样式,JS 文件),而不需要等待客户端浏览器请求这些资源,从而做到更快的第一次渲染时间

HTTP/2spdy 让开发者在基于 Express 的应用能更容易的实现服务器推送特性。

可以下载参考本文的源码,然后为你自己的服务器编写服务器推送你的资源。

PS:
我的博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Node.jsExpress框架,结合文件系统模块(fs模块)来实现基于文件的增删改查操作。具体步骤如下: 1. 安装Express框架和body-parser模块,用于解析POST请求中的参数。 ``` npm install express body-parser --save ``` 2. 创建一个用于存储数据的JSON文件,例如data.json。 3. 创建一个Express应用,并设置路由。 ```javascript const express = require('express'); const bodyParser = require('body-parser'); const fs = require('fs'); const app = express(); const dataPath = './data.json'; // 解析POST请求中的参数 app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // 获取所有数据 app.get('/api/data', (req, res) => { const rawData = fs.readFileSync(dataPath); const data = JSON.parse(rawData); res.json(data); }); // 添加数据 app.post('/api/data', (req, res) => { const rawData = fs.readFileSync(dataPath); const data = JSON.parse(rawData); const newData = req.body; data.push(newData); const stringifyData = JSON.stringify(data); fs.writeFileSync(dataPath, stringifyData); res.json({ success: true }); }); // 删除数据 app.delete('/api/data/:id', (req, res) => { const rawData = fs.readFileSync(dataPath); const data = JSON.parse(rawData); const id = req.params.id; const newData = data.filter(item => item.id !== id); const stringifyData = JSON.stringify(newData); fs.writeFileSync(dataPath, stringifyData); res.json({ success: true }); }); // 修改数据 app.put('/api/data/:id', (req, res) => { const rawData = fs.readFileSync(dataPath); const data = JSON.parse(rawData); const id = req.params.id; const index = data.findIndex(item => item.id === id); if (index !== -1) { const newData = req.body; data[index] = newData; const stringifyData = JSON.stringify(data); fs.writeFileSync(dataPath, stringifyData); res.json({ success: true }); } else { res.status(404).json({ success: false, message: '数据不存在' }); } }); // 查询数据 app.get('/api/data/:id', (req, res) => { const rawData = fs.readFileSync(dataPath); const data = JSON.parse(rawData); const id = req.params.id; const result = data.find(item => item.id === id); if (result) { res.json(result); } else { res.status(404).json({ success: false, message: '数据不存在' }); } }); // 监听端口 const port = 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); }); ``` 在实际使用中,可以根据具体的需求进行修改和完善。注意在修改和删除操作中,需要判断数据是否存在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值