一、引言:
HTTP/2 是 HTTP 协议的一个升级,它的主要目的是为了解决 HTTP/1.x 中不好实现功能,包括通过支持完整的请求与响应复用来减少延迟,通过有效压缩 HTTP 标头字段将协议开销降至最低,增加对服务器推送的支持。
二、HTTP 1.1 和 HTTP 2.0 的区别
1、二进制协议:
在
HTTP/1.1
版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。
HTTP/2 彻底的二进制协议
,头信息和数据体都是二进制,并且统称为"帧",可以分为头信息帧和数据帧。 帧的概念是它实现多路复用的基础。概念:
帧
:HTTP/2通信的最小单位,包含帧头。
消息
:与逻辑请求或响应消息对应的完整的一系列帧。
数据流
: 已建立连接内的双向字节流,可以承载一条或多条消息。
帧、消息、数据流之间的关系
:一个 TCP 连接上承载任意数量的数据流,每个数据流承载双向消息,每条消息包含一或多个帧,帧是承载特定数据的最小单位。
二进制分帧层
:在应用层新增一个二进制分帧层,用来处理所有 http/2 新增的特性,对于通过 http/2 传输的信息细分为消息和帧,使用二进制格式编码。
2、持久连接:
在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接,每次请求都会重新建立和断开 TCP 连接,代价过大。所以虽然标准中没有设定,某些服务器对 Connection: keep-alive 的 Header 进行了支持。这样的好处是连接可以被重新使用,之后发送 HTTP 请求的时候不需要重新建立 TCP 连接,以及如果维持连接,那么 SSL 的开销也可以避免。
既然维持 TCP 连接好处这么多,HTTP/1.1 就把 Connection 头写进标准,并且默认开启持久连接,除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。
HTTP/2 的链接都是永久的。
3、多路复用:
HTTP/1.1 存在一个问题: 单个 TCP 连接在同一时刻只能处理一个请求(
串行
)。
意思是说:
客户端可以发起多个请求,可以减少整体的响应时间,但是服务器还是按照顺序回应请求,如果前面的回应特别慢,后面就会有许多请求排队等着。
HTTP2 提供了 Multiplexing 多路传输特性,可以在一个 TCP 连接中同时完成多个 HTTP 请求(
并行
)
4、数据流
:
HTTP/2 的消息都是在一个 TCP 连接内完成,通过把消息分成一些列的帧,把这些帧交错传输从而达到复用的目的,每个帧都包含数据流标识符,接收端根据其在重新组装成一条消息。
5、头信息压缩
:
由于
HTTP 1.1
协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。
HTTP/2
对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。
6、服务器推送:
HTTP/2 允许服务器未经请求,主动向客户端发送资源。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的。
三、node.js搭建一个http2 server:
1、安装http2
npm i --save http2
2、生成ssl证书
可以查一下如何生成,这里不详细说明,就用别人生成好的证书来测试了。
3、新建项目、初始化项目并安装spdy和express
npm init -y
npm i express spdy -D
当前来说,为 Node.js 主要有两个库实现了 HTTP/2 :
http2
两个库都跟 Node.js 核心模块的 http 和 https 模块 api 很相似。这就意味着如果你不使用 Express ,这两个库就没什么区别。然而, spdy 库支持 HTTP/2 和 Express,而 http2 库当前不支持 Express。这就是为什么我们选择使用 spdy , Express 是Node.js 适合搭配的实践标准的服务框架。之所以叫 spdy是来自于 Google 的 SPDY 协议后来升级成 HTTP/2。
4、搭建项目的目录结构
5、serve.js文件 项目中最核心的就是server文件,http2.createSecureServer(options, callback),options表示你的证书或者其他有关的配置选项,但是证书是必备的。
'use strict'
const fs = require('fs')
const path = require('path')
const http2 = require('http2')
const helper = require('./helper')
const PORT = process.env.PORT || 8080
const PUBLIC_PATH = path.join(__dirname, '../public')
const publicFiles = helper.getFiles(PUBLIC_PATH)
// 读取证书
const options = {
cert: fs.readFileSync(path.join(__dirname, '../ssl/cert.pem')),
key: fs.readFileSync(path.join(__dirname, '../ssl/key.pem'))
// 创建HTTP2服务器
const server = http2.createSecureServer(options, onRequest)
// Request 事件
function onRequest(req, res) {
// 路径指向 index.html
const reqPath = req.url === '/' ? '/index.html' : req.url
//获取html资源
const file = publicFiles.get(reqPath)
// 文件不存在
if (!file) {
res.statusCode = 404
res.end()
return
res.stream.respondWithFD(file.fileDescriptor, file.headers)
server.listen(PORT)
helper.js:可以看到代码中用到了fs读取文件,helper也是获取文件的插件。
'use strict'
const fs = require('fs')
const path = require('path')
const mime = require('mime')
function getFiles(baseDir) {
const files = new Map()
fs.readdirSync(baseDir).forEach((fileName) => {
const filePath = path.join(baseDir, fileName)
const fileDescriptor = fs.openSync(filePath, 'r')
const stat = fs.fstatSync(fileDescriptor)
const contentType = mime.lookup(filePath)
files.set(`/${fileName}`, {
fileDescriptor,
headers: {
'content-length': stat.size,
'last-modified': stat.mtime.toUTCString(),
'content-type': contentType
return files
module.exports = {
getFiles
关键点2 html文件
这里没有引用第三方支持库,用fetch()和for循环的来模拟客户端向服务器发起100个请求,让我们更加直观的看到http2请求多个资源的情况。(了解更多关于:fetch->developer.mozilla.org/en-US/docs/… )
<meta charset="UTF-8">
</head>
<h1>Test HTTP2 server</h1>
</body>
<script src="test1.js"></script>
<script src="test2.js"></script>
<script>
for (var i = 0; i < 100; i++) {
fetch('//localhost:8080/network.png');
</script>
</html>
test1.js/test2.js
'use strict'
console.log('test 1')
document.body.innerHTML += '<p>第一个js</p>'
6、启动服务
浏览器打开页面测试 https://localhost:8080/, 因为我们创建的证书并没有经过CA机构认证,所以会提示页面不安全,点击继续访问即可。 查看网络请求协议可以看到网站使用了HTTP/2(h2)
$ node index.js
7、测试结果
从测试结果来看,可以回顾一下http2的知识,非常明显的一点是:同个域名只需要占用一个 TCP 连接
8、测试一下服务端推送Server Push
为了改善延迟,HTTP/2引入了 Server Push ,这允许服务器在明确的请求之前将资源推送到浏览器。这就允许服务器充分利用空闲的网络来改善加载时间。
我们新建一个sever1.js来测试服务推送功能。
服务器推送只需简单的调用 spdy 实现的 res.push ,我们将文件路径名传输进去作为第一个参数,浏览器会使用这个路径名来匹配push promise 资源。res.push() 的第一个参数 /test1.js 一定得跟 HTML 文件中需要的文件名相匹配。而第二个参数是一个可选的对象,设置了该资源的一些资源信息描述。
const port = 8080
const spdy = require('spdy')
const fs = require('fs') 。
const express = require('express')
const app = express() // https://www.it1352.com/1058471.html
const path = require('path')
const options = {
cert: fs.readFileSync(path.join(__dirname, '../ssl/cert.pem')),
key: fs.readFileSync(path.join(__dirname, '../ssl/key.pem'))
app.use('/', express.static(path.join(__dirname, 'public2')))
app.get('/', (req, res) => {
var stream = res.push('/test1.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!");')
stream.end(fs.readFileSync('./public2/test1.js'))
res.end(fs.readFileSync('./public2/index.html'))
.createServer(options, app)
.listen(port, (error) => {
if (error) {
console.error(error)
return process.exit(1)
} else {
console.log('Listening on port: ' + port + '.')
你可以看到,stream 对象有两个方法 on 和 end。前者监听了 error 和 finish 事件,而后者则监听完成传输 end,然后就会main.js 就会触发弹窗。(例子中弹窗代码被我注掉了,可以放开测一下)
或者,如果你拥有多个数据块,你可以选择使用 res.write() 然后最后使用 res.end(),其中 res.end() 会自动关闭结束response 而 res.write() 则让它保持开启。
最后,读取 HTTPS 密钥和证书并使用 spdy 启动运转服务器。-->spdy .createServer(options, app)
该实现的关键就在于,围绕着 streams(从源头到客户端的建立起的数据通道流)
index.js
'use strict'
// require('./src/server')
require('./src/server1')
9、启动和对比 HTTP/2 Server Push
可以在浏览器中检测收到服务器端推送的行为。Chrome 启动开发者工具,打开 Network 标签,你可以看到 test1.js 不存在绿色时间条,没有等待时间 TTFB (Time to First Byte)详细。
使用了http2 server Push截图如下:
再仔细看,可以看到请求是由 Push 开始发起的(Initiator列查看),没有使用服务器推送的 HTTP/2 服务器或者 HTTP/1,这一列就会显示文件名称,如 index.html 发起的显示就是 index.html。
不使用server push截图如下:
实践就结束了,使用了 Express 和 Spdy 简单就实现了push JS 资源,而该资源可以用于后面 HTML 中 script 标签引入的。当然你也可以在脚本中使用 fs 来读取文件资源。
HTTP/2 Server Push的好处就在于当浏览器请求页面的时候,同时发送必需的资源文件(图片,CSS 样式,JS 文件),而不需要等待客户端浏览器请求这些资源,从而做到更快的第一次渲染时间。
HTTP/2 库 spdy 让开发者在基于 Express 的应用能更容易的实现服务器推送特性。
参考:zhuanlan.zhihu.com/p/76302817
参考:segmentfault.com/a/119000001…