关于HTTP/2协议的那些事

Node.js v8.12.0 LTS 已经提供http/2试验性的模块,而Node.js v10.10.0版本已经把http/2设定为稳定模块。
目前使用HTTP/2的网站有:淘宝、简书、谷歌等,在未来http/2必定会被广泛使用。
点击查看HTTP/2模块

HTTP2介绍

基于谷歌开发的SPDY协议的传输协议第二版,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。
相比于HTTP1.1,HTTP2具备以下优点:

一、HTTP2 使用二进制协议,而不是文本协议

二进制协议其实就是在应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。
在二进制分帧层中,HTTP2将所有传输的信息分割成更小的消息和帧,并采用二进制编码格式的编码,原来HTTP1.1的首部信息封装到HEADERS frame,而请求体则封装到DATA frame中。
906e22193e61cd561325d93aae0f1e07_hd
总之:

  • 二进制协议带来更好的解析协议,几乎没有解析代价
  • 二进制协议没有冗余字段,占用带宽更少

二、HTTP2 是完全多路复用的

HTTP2允许同时通过单一的连接发起多重的请求-响应消息。
b1e608ddb7493608efea3e76912aabe1_hd
在HTTP/1.1 协议中, 存在一个线端阻塞的问题,即浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制,超过限制数目的请求会被阻塞。当然不同浏览器对这个限制数目不同。正是因为这个线端阻塞问题,很多网站都需要部署多个静态资源CDN.
同时,TCP存在一种慢启动机制,TCP连接会随着时间进行自我调节,起初会限制连接的最大速度,如果数据传输成功,会随时间提高传输速度。由于这种机制,让原本很多具有突发性和短时效性的HTTP连接十分低效。所以在过去,HTTP性能优化关键是并不是高带宽,而是降低延迟。
HTTP2启动多路复用后,让所有请求响应流都在同一个连接中,更加高效的使用TCP连接。同时单一连接还能减低服务端的链接压力,内存占用更少,连接吞吐量更大。

三、HTTP2 使用首部压缩,降低了开销

假定一个页面有80个资源需要加载, 而每一次请求都有1400字节的消息头(因为Cookie和引用等东西的存在), 至少要7到8个来回去才能获得这些消息头。
TCP的慢启动机制会基于已知有多少包,来确定还要回去获取哪些包,这很明显的限制了最初的几个来回可以发送的数据包的数量。相比之下,即使是头部轻微的压缩也可以是让那些请求只需一个来回就能搞定,有时候甚至一个包就可以了。这种开销是可以被节省下来的。

四、HTTP2 能够让服务器将响应主动推送到客户端缓存中

服务器推送服务是一种在客户端请求之前发送数据的机制,通过“推送”那些它认为客户端将会需要的内容到客户端的缓存中,以此来避免往返的延迟。
d9f07162d5391641b7a2645a61b24fc0_hd

Node使用http2示例

这里使用官方文档的两个示例做简单介绍Node中如何开启http2服务。

生成自签发秘钥和证书

首先使用openssl生成自签发的证书

openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -keyout localhost-privkey.pem -out localhost-cert.pem

服务端代码

const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('localhost-privkey.pem'),
  cert: fs.readFileSync('localhost-cert.pem')
});
server.on('error', (err) => console.error(err));

server.on('stream', (stream, headers) => {
  // stream is a Duplex
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.end('<h1>Hello World</h1>');
});

server.listen(8443);

由于使用自签发的证书,通过浏览器打开(https://localhost:8443/)浏览器会有安全提示信息,点击继续前往localhost(不安全)即可。
WX20180912-101405@2x
然后在浏览器中打开新的标签页,输入如下地址(chrome://net-internals/#http2),验证是否成功开启HTTP2
QQ20180913-002029@2x

客户端代码

const http2 = require('http2');
const fs = require('fs');
const client = http2.connect('https://localhost:8443', {
  ca: fs.readFileSync('localhost-cert.pem')
});
client.on('error', (err) => console.error(err));

const req = client.request({ ':path': '/' });

req.on('response', (headers, flags) => {
  for (const name in headers) {
    console.log(`${name}: ${headers[name]}`);
  }
});

req.setEncoding('utf8');
let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => {
  console.log(`\n${data}`);
  client.close();
});
req.end();

HTTP2一定要加密吗?

HTTP官方工作组并不要求一定要加密处理,只不过目前所知的浏览器只支持带加密(例如TLS)的HTTP2.

参考文档

HTTP/2.0 相比1.0有哪些重大改进?