HTTP2新特性

概述

HTTP/2 基于 SPDY 协议,该协议是谷歌自行开发的,主要是为了解决 HTTP/1.1 效率不高的问题,该协议在 Chrome 浏览器上证明可行后,就被当做 HTTP/2 的基础

HTTP/2是 HTTP 协议自 1999年 HTTP/1.1 发布后的首个更新版本,2015 年,HTTP/2 发布。它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是 HTTP/3

HTTP/2 的优势

与 HTTP/1.1 相比,HTTP/2 有着很多优势:

  • HTTP/2 采用二进制格式而非文本格式
  • HTTP/2 是完全多路复用的,而非有序并阻塞的——只需一个连接即可实现并行
  • HTTP/2 使用报头压缩,降低了开销
  • HTTP/2 让服务器可以将响应主动“推送”到客户端缓存中

总的来说,HTTP/2 相比 HTTP/1.1 的修改并不会破坏现有程序的工作,但是新的程序可以借由新特性得到更好的速度

HTTP/2 基于 SPDY 协议,不同的地方在于:

  • HTTP/2 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
  • HTTP/2 消息头的压缩算法采用HPACK,而 SPDY 采用 DEFLATE

二进制传输

  • HTTP/1.1 的头信息是文本(ASCII编码),数据体可以是文本,也可以是二进制
  • HTTP/2 是一个彻底的二进制,头信息和数据体都是二进制,并且统称为
    • 头信息帧
    • 数据帧

HTTP/2在 应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层,在不改动 HTTP/1.x 的语义、方法、状态码、URI 以及首部字段的情况下, 解决了 HTTP/1.1 的性能限制,改进传输性能,实现低延迟和高吞吐量

在二进制分帧层中, HTTP/2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码,其中 HTTP/1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 `DATA frame`` 里面

二进制协议的一个好处是,可以定义额外的帧。HTTP/2 定义了近十种帧,为将来的高级应用打好了基础。如果使用文本实现这种功能,解析数据将会变得非常麻烦,二进制解析则方便得多

多路复用 (Multiplexing)/ 二进制分帧

多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息

在 HTTP/1.1 中浏览器客户端在同一时间,针对同一域名下的请求由一定的数量限制。因而超过限制数目的请求会被阻塞,这也是一些站点有多个静态资源 CDN 域名的原因之一

多路复用的特性就避免了建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个个的帧,这些帧对应着逻辑流中的消息,并行地在同一个TCP连接上双向交换信息

帧和流的关系

  • 帧是最小的数据单位
  • 每个请求/响应的所有数据包,称为一个数据流,每个流都有独一无二的编号(数据流ID)
    客户端发送的数据流ID为奇数,服务端发出的为偶数
  • 每个帧会标识出该帧属于哪个流,流是多个帧组成的数据流

多路复用的本质

在一个 TCP 连接中存在多个流,也就是可以同时发送多个请求,对端可以通过帧中的标识知道该帧属于哪个请求

在客户端,这些帧乱序发送,到对端后可以通过帧首部的流标识符重新组织,这样就避免了旧版的队头阻塞问题,极大提升了传输性能

传输细节

数据流发送一半时,客户端/服务器可以发送信号(RST_STEAM帧),取消这个流,而 HTTP/1.1取消数据流的唯一方式就是关闭 TCP 连接

客户端还可以指定数据流的优先级,优先级越高,服务器就会越早回应

给每个帧打上流的 ID 去避免依次响应的问题,对方接收到帧之后根据 ID 拼接出流,这样就可以做到乱序响应从而避免请求时的队首阻塞问题。但是 TCP 层面的队首阻塞是 HTTP/2 无法解决的(HTTP 只是应用层协议,TCP 是传输层协议),TCP 的阻塞问题是因为传输阶段可能会丢包,一旦丢包就会等待重新发包,阻塞后续传输,这个问题虽然有滑动窗口(Sliding Window)这个方案,但是只能增强抗干扰,并没有彻底解决

首部压缩(Header Compression)

当一个客户端向相同服务器请求许多资源时,将有大量请求看上去一样的,加之 HTTP 协议无状态,每次请求都必须附上所有信息,所以很多字段都是重复的,每次都传输,会浪费很多带宽且影响速度,故而 HTTP/2 维护了一个头部信息字典,差量进行更新头信息,减少头部信息传输占用的资源

服务端推送

HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送

比如,浏览器只请求了index.html,但是服务器把index.html、style.css、example.png全部发送给浏览器。这样的话,只需要一轮 HTTP 通信,浏览器就得到了全部资源,提高了性能

Nginx 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
listen 443 ssl http2;
server_name localhost;

ssl on;
ssl_certificate /etc/nginx/certs/example.crt;
ssl_certificate_key /etc/nginx/certs/example.key;

ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
http2_push /style.css;
http2_push /example.png;
}
}

最后多了两行http2_push命令。它的意思是,如果用户请求根路径/,就推送style.css和example.png

缓存问题

服务器推送有一个很麻烦的问题。所要推送的资源文件,如果浏览器已经有缓存,推送就是浪费带宽。即使推送的文件版本更新,浏览器也会优先使用本地缓存。

一种解决办法是,只对第一次访问的用户开启服务器推送。下面是 Nginx 官方给出的示例,根据 Cookie 判断是否为第一次访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 443 ssl http2 default_server;

ssl_certificate ssl/certificate.pem;
ssl_certificate_key ssl/key.pem;

root /var/www/html;
http2_push_preload on;

location = /demo.html {
add_header Set-Cookie "session=1";
add_header Link $resources;
}
}


map $http_cookie $resources {
"~*session=1" "";
default "</style.css>; as=style; rel=preload";
}

参考

HTTP 的发展

HTTP/2 服务器推送(Server Push)教程