前言
为了降低加载时间, 相信大多数人都做过如下尝试
Keep-alive: TCP 持久连接, 增加了 TCP 连接的复用性, 但只有当上一个请求 / 响应完全完成后, client 才能发送下一个请求
Pipelining: 可同时发送多个请求, 但是服务器必须严格按照请求的先后顺序返回响应, 若第一个请求的响应迟迟不能返回, 那后面的响应都会被阻塞, 也就是所谓的队头阻塞
请求合并: 雪碧图, CSS/JS 内联, CSS/JS 合并等, 然而请求合并又会带来缓存失效, 解析变慢, 阻塞渲染, 木桶效应等诸多问题
域名散列: 绕过了同域名最多 6 个 TCP 的限制, 但增加了 DNS 开销和 TCP 开销, 也会大幅降低缓存的利用率
......
不可否认, 这些优化在一定程度上降低了网站加载时间, 但对于一个 web 应用庞大的请求量来说, 这些只是冰上一角, 隔靴搔痒.
以上问题归根结底是 HTTP1.1 协议本身的问题, 若要从根本上解决 HTTP1.1 的低效, 只能从协议本身入手. 为此 Google 开发了 SPDY 协议, 主要是为了降低传输时间; 基于 SPDY 协议, IETF 和 SPDY 组全体成员共同开发了 HTTP/2, 并在 2015 年 5 月以 RFC 7504 https://tools.ietf.org/html/rfc7540 正式发表. SPDY 或者 HTTP/2 并不是一个全新的协议, 它只是修改了 HTTP 的请求与应答在网络上的传输方式, 增加了一个 spdy 传输层, 用于处理, 标记, 简化和压缩 HTTP 请求, 所以它们并不会破坏现有程序的工作, 对于支持的场景, 使用新特性可以获得更快的速度, 对于不支持的场景, 也可以实现平稳退化.
HTTP/2 继承了 spdy 的多路复用, 优先级排序等诸多优秀特性, 也额外做了不少改进. 其中较为显著的改进是 HTTP/2 使用了一份经过定制的压缩算法, 以此替代了 SPDY 的动态流压缩算法, 用于避免对协议的 Oracle 攻击.
多数主流浏览器已在 2015 年底支持了该标准(划重点). 具体支持度如下:
数据来源 https://caniuse.com/#search=HTTP/2
可以看到国内有 58.55% 的浏览器已经完全支持 HTTP/2, 而全球的支持度更是高达 85.66%. 这么高的支持度, so, 你心动了吗
why HTTP/2
二进制格式传输
我们知道 HTTP/1.1 的头信息肯定是文本(ASCII 编码), 数据体可以是文本, 也可以是二进制(需要做自己做额外的转换, 协议本身并不会转换). 而在 HTTP/2 中, 新增了二进制分帧层, 将数据转换成二进制, 也就是说 HTTP/2 中所有的内容都是采用二进制传输.
使用二进制有什么好处吗? 当然! 效率会更高, 而且最主要的是可以定义额外的帧, 如果用文本实现帧传输, 解析起来将会十分麻烦. HTTP/2 共定义了十种帧, 较为常见的有数据帧, 头部帧, PING 帧, SETTING 帧, 优先级帧和 PUSH_PROMISE 帧等, 为将来的高级应用打好了基础.
如上图, Binary Framing 就是新增的二进制分帧层.
多路复用
二进制分帧层把数据转换为二进制的同时, 也把数据分成了一个一个的帧. 帧是 HTTP/2 中数据传输的最小单位; 每个帧都有 stream_ID 字段, 表示这个帧属于哪个流, 接收方把 stream_ID 相同的所有帧组合到一起就是被传输的内容了. 而流是 HTTP/2 中的一个逻辑上的概念, 它代表着 HTTP/1.1 中的一个请求或者一个响应, 协议规定 client 发给 server 的流的 stream_ID 为奇数, server 发给 client 的流 ID 是偶数. 需要注意的是, 流只是一个逻辑概念, 便于理解和记忆的, 实际并不存在.
理解了帧和流的概念, 完整的 HTTP/2 的通信就可以被形象地表示为这样:
可以发现, 在一个 TCP 链接中, 可以同时双向地发送帧, 而且不同流中的帧可以交错发送, 不需要等某个流发送完, 才发送下一个. 也就是说在一个 TCP 连接中, 可以同时传输多个流, 即可以同时传输多个 HTTP 请求和响应, 这种同时传输不需要遵循先入先出等规定, 因此也不会产生阻塞, 效率极高.
在这种传输模式下, HTTP 请求变得十分廉价, 我们不需要再时刻顾虑网站的 http 请求数是否太多, TCP 连接数是否太多, 是否会产生阻塞等问题了.
HPACK 首部压缩
为什么需要压缩?
在 HTTP/1 中, HTTP 请求和响应都是由「状态行, 请求 / 响应头部, 消息主体」三部分组成. 一般而言, 消息主体都会经过 gzip 压缩, 或者本身传输的就是压缩过后的二进制文件(例如图片, 音频), 但状态行和头部却没有经过任何压缩, 直接以纯文本传输.
随着 Web 功能越来越复杂, 每个页面产生的请求数也越来越多, 根据 HTTP Archive 的统计, 当前平均每个页面都会产生上百个请求. 越来越多的请求导致消耗在头部的流量越来越多, 尤其是每次都要传输 UserAgent,Cookie 这类不会频繁变动的内容, 完全是一种浪费.
为了减少冗余的头部信息带来的消耗, HTTP/2 采用 HPACK 算法压缩请求和响应的 header. 下面这张图非常直观地表达了 HPACK 头部压缩的原理:
- <!DOCTYPE HTML>
- <HTML>
- <head>
- <link rel="stylesheet" href="style.css">
- <script src="user.js">
- </script>
- </head>
- <body>
- <h1>
- hello http2
- </h1>
- </body>
- </HTML>
- - src/
- - img/
- - JS/
- - page1.HTML
- - server.JS
- $ 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 x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
- ....
- $ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
- // server.JS
- const http2 = require('spdy')
- const express = require('express')
- const App = express()
- const publicPath = 'src'
- App.use(express.static(publicPath))
- App.get('/', function (req, res) {
- res.setHeader('Content-Type', 'text/html')
- res.sendFile(__dirname + '/src/page1.html')
- })
- var options = {
- key: fs.readFileSync('./ca/server.key'),
- cert: fs.readFileSync('./ca/server.crt')
- }
- http2.createServer(options, App).listen(8080, () => {
- console.log(`Server is listening on https://127.0.0.1:8080 .`)
- })
- App.get('/', function (req, res) {
- + push('/img/yunxin1.png', res, 'image/png')
- + push('/img/yunxin2.png', res, 'image/png')
- + push('/js/log3.js', res, 'application/javascript')
- res.setHeader('Content-Type', 'text/html')
- res.sendFile(__dirname + '/src/page1.html')
- })
- function push (reqPath, target, type) {
- let content = fs.readFileSync(path.join(__dirname, publicPath, reqPath))
- let stream = target.push(reqPath, {
- status: 200,
- method: 'GET',
- request: { accept: '*/*' },
- response: {
- 'content-type': type
- }
- })
- stream.on('error', function() {})
- stream.end(content)
- }
- w3c-preload https://w3c.github.io/preload/
- HTTP/2 Server Push with Node.JS https://blog.risingstack.com/node-js-http-2-push/
来源: https://www.cnblogs.com/nuannuan7362/p/10397536.html