HTTP/2(HTTP/2.0)即超文本传输协议 2.0,是下一代HTTP协议。相对于HTTP/1.x协议的文本传输格式,HTTP/2以二进制的格式进行数据传输。因此,具有更小的传输体积以及负载,相比于文本解析,二进制解析更加方便、高效。HTTP/2与HTTP/1.x相比具有如下特性:
HTTP/2 帧的特性
二进制分帧
HTTP/2在应用层跟传输层 之间增加了一个 二进制分帧层,从而能够达到:在不改动HTTP的语义、HTTP方法、状态码、URI及首部字段的情况下,突破HTTP 1.1的性能限制,改进传输性能,实现低延迟和高吞吐量。

如上图所示,在二进制分帧层上, HTTP 2.0会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码,其中HTTP 1.1的首部信息会被封装到Headers帧,而request body被封装到Data帧里面。然后,HTTP 2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。
header压缩
 HTTP 2.0采用HPACK的压缩算法来压缩头部。并且在客户端和服务器端使用 “首部表” 来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送,通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次。
- 如果请求中不包含首部(例如对同一资源的轮询请求),那么 首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。
 - 如果首部发生变化了,那么只需要发送变化了数据在
Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在HTTP 2.0的连接存续期内始终存在,由客户端和服务器共同渐进地更新。 

多路复用
在HTTP/1.1中,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞。HTTP 2.0 多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行。

HTTP 2.0把HTTP协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个TCP连接上双向交换消息。每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的id将request再归属到各自不同的服务端请求里面。

上图中包含了同一个连接上多个传输中的数据流:客户端正在向服务器传输一个DATA帧(stream 5),与此同时,服务器正向客户端乱序发送stream 1 和stream 3的一系列帧。此时,一个连接上有3个请求/响应并行交换。
服务器推送
HTTP 2.0新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。服务器向客户端推送资源无需客户端明确地请求。
服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。这相当于预加载技术,具体如下:
普通的客户端请求过程:

服务端推送的过程:

更安全的SSL
HTTP2.0使用了TLS的拓展ALPN来做协议升级,除此之外加密这块还有一个改动,HTTP2.0对TLS的安全性做了近一步加强,通过黑名单机制禁用了几百种不再安全的加密算法,一些加密算法可能还在被继续使用。
虽然HTTP2.0中的TLS是可选的,但是现在主流的浏览器像chrome,firefox都只支持基于TLS部署的HTTP2.0协议。所以要想站点升级为HTTP2.0首先要将站点先升级为HTTPS。
HTTP/2性能
HTTP 2.0相比于HTTP 1.x,大幅度的提升了web性能。https://http2.akamai.com/demo这个网站可以看到在HTTP 2.0与HTTP 1.1的环境下的性能比较。
nginx HTTP/2.0 配置
安装nginx
首先确认当前openssl版本,最低要求1.0.2,如果不满足要求还要下载openssl,nginx也用最新的稳定版。
openssl version -a
wget https://www.openssl.org/source/openssl-1.0.2e.tar.gz
wget http://nginx.org/download/nginx-1.12.2.tar.gz
解压:
tar xzf openssl-1.0.2e.tar.gz
tar xzf nginx-1.12.2.tar.gz
编译安装:
cd nginx-1.12.2
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module --with-http_v2_module --with-openssl=../openssl-1.0.2e
make 
sudo make install
配置运行
配置nginx伪证书、配置文件中开启https即可
到需要放置证书的目录(选在nginx的/usr/local/nginx/conf目录下就可以),建立服务器的私钥(此过程需输入密码短语):
openssl genrsa -des3 -out server.key 1024
创建证书签名请求csr:
openssl req -new -key server.key -out server.csr
生成RSA公钥:
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
使用上面的私钥和CSR对证书进行签名:
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
配置nginx:
server {
    listen       443 ssl http2 default_server;
    server_name  www.heqingliang.com;                                                                            
    ssl_certificate      server.crt;
    ssl_certificate_key  server.key;
    location / { 
        root   html;
        index  index.html index.htm;
    }
}
启动nginx:
cd /usr/local/nginx/sbin
sudo ./nginx
把生成的证书server.crt导入进firxfox浏览器,注意firefox的版本要支持HTTP/2,把浏览器的开发模式打开,输入配置中的域名,看到如下请求,则表示HTTP/2配置成功:

HTTP/2 帧格式
使用wireshark抓包工具,可以看到在TLS请求包中带有h2,这表示请求使用的是HTTP 2.0

如果服务器支持HTTP 2.0,则客户端会发送一个24个字节(PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)开始的连接序言。如果不支持HTTP 2.0,则使用HTTP/1.x。关于HTTP 2.0的请求过程可以参考RFC。

要看到HTTP/2 帧格式,首先要把上面wireshark抓的TLS数据包是解开。可以参考这篇文章,https://ismisepaul.wordpress.com/2015/03/04/http2-traffic-in-wireshark/,解开TLS数据包后,可以看到:

帧头

固定的9个字节((24+8+8+1+31)/8=9)呈现,变化的为帧的Frame Payload,Frame Payload的内容是由帧类型(Type)定义。
帧长度(Length): 无符号的自然数,24个比特表示,仅表示帧的Frame Payload所占用字节数,不包括帧头所占用的9个字节。 默认大小区间为为0~16,384(2^14),一旦超过默认最大值2^14(16384),发送方将不再允许发送,除非接收到接收方定义的SETTINGS_MAX_FRAME_SIZE(一般此值区间为2^14 ~ 2^24)值的通知。 
帧类型(Type): 8个比特表示,定义了帧负载的具体格式和帧的语义,HTTP/2规范定义了10个帧类型。

帧的标志位(Flags): 8个比特表示,表示具体帧的类型,默认值为0x0。8个比特可以容纳8个不同的标志,比如,PADDED值为0x8,二进制表示为00001000;END_HEADERS值为0x4,二进制表示为00000100;END_STREAM值为0X1,二进制为00000001。可以同时在一个字节中传达三种标志位,二进制表示为00001101,即0x13。因此,后面的帧结构中,标志位一般会使用8个比特表示,若某位不确定,使用问号?替代,表示此处可能会被设置标志位。
帧保留比特位(R): HTTP/2语境下为保留的比特位,固定值为0X0。
流标识符(Stream Identifier): 无符号的31比特表示无符号自然数。0x0值表示为帧仅作用于连接,不隶属于单独的流。
HEADERS 帧
报头(type = 0x1)主要载体,请求头或响应头。

Pad Length: 受制于PADDED标志(flags标志中第3位)控制是否设置,8个比特表示填充的字节数。
E: 一个比特表示流依赖是否专用,可选项,只在流优先级PRIORITY(flags标志中第5位)被设置时有效。
Stream Dependency: 31个比特表示流依赖,只在流优先级PRIORITY(flags标志中第5位)被设置时有效。
Weight: 8个比特(一个字节)表示无符号的自然数流优先级,值范围自然是(1~256)。只在流优先级PRIORITY(flags标志中第5位)被设置时有效。
Header Block Fragment: 报头块分片,采用HPACK压缩。
Padding: 填充的字节,受制于PADDED标志(flags标志中第3位)标志控制是否设置,长度由Pad Length字段决定。
标志位END_HEADERS(0X4): 表示报头块的最后一个帧,否则后面还会跟随CONTINUATION帧。
如图是未设置流优先级PRIORITY的HEADERS帧:

如图是设置流优先级PRIORITY的HEADERS帧:

HPACK
HTTP 2.0的头部使用的是HPACK压缩,关于HPACK可以参考RFC。
对于HPACK,可以使用nghttp2库进行压缩和解压缩。把上面的HEADERS帧的导出字节流,可以使用下面程序进行解压缩。
1  | 
  | 
编译程序:
gcc inflate.c -lnghttp2
运行程序:
./a.out header.bin
输出:
00000000: 00 00 E3 01 25 00 00 00 0D 00 00 00 0B 1F 82 05 
00000010: 85 62 72 D1 41 D8 41 8E F1 E3 C2 F3 97 B1 AA 9A 
00000020: 83 0E A9 97 21 E9 87 7A BA D0 7F 66 A2 81 B0 DA 
00000030: E0 53 FA FC 08 7E D4 E1 1D B5 26 DF B5 33 9A AB 
00000040: 7C A9 E5 E7 22 71 AF B5 2C EF 71 B0 2E 0F ED 4C 
00000050: 45 27 53 B0 20 04 00 08 02 A6 13 58 59 4F E5 86 
00000060: C0 B8 3F 53 B0 49 7C A5 89 D3 4D 1F 43 AE BA 0C 
00000070: 41 A4 C7 A9 8F 33 A6 9A 3F DF 9A 68 FA 1D 75 D0 
00000080: 62 0D 26 3D 4C 79 A6 8F BE D0 01 77 FE BE 58 F9 
00000090: FB ED 00 17 7B 51 8B 2D 4B 70 DD F4 5A BE FB 40 
000000A0: 05 DB 50 8D 9B D9 AB FA 52 42 CB 40 D2 5F A5 23 
000000B0: B3 40 92 B6 B9 AC 1C 85 58 D5 20 A4 B6 C2 AD 61 
000000C0: 7B 5A 54 25 1F 81 0F 68 96 DF 3D BF 4A 04 4A 43 
000000D0: 5D 8A 08 01 79 40 BD 71 A6 6E 08 0A 62 D1 BF 69 
000000E0: 8B FE 5B 19 25 1B C4 79 60 8C 7F CF 00 00 04 08 
000000F0: 00 00 00 00 0D 00 BE 00 00 
------------- headers frame -------------
:method: GET
:path: /hello/
:authority: www.heqingliang.com
:scheme: https
user-agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate, br
upgrade-insecure-requests: 1
if-modified-since: Thu, 12 Apr 2018 18:43:20 GMT
if-none-match: "5acfa8c8-1aa"
------------- window_update frame -------------
DATA帧
一个或多个DATA帧(type = 0x0)作为请求、响应内容载体:

Pad Length:  一个字节表示填充的字节长度,取决于PADDED(flags标志中第3位)标志是否被设置。
Data: 应用数据,真正大小需要减去其他字段(比如填充长度和填充内容)长度。
Padding: 填充内容为若干个0x0字节,受PADDED(flags标志中第3位)标志控制是否设置。接收端处理时可忽略验证填充内容。
标志位END_STREAM (0x1): 标志此帧为对应标志流最后一个帧,流进入了半关闭/关闭状态。
如图是服务器返回的数据:

把上面的服务器返回的数据导出字节流,运行上面的程序,输出以下结果:
00000000: 00 00 6B 01 04 00 00 00 11 88 76 89 AA 63 55 E5 
00000010: 80 AE 11 2E 2F 61 96 E4 59 3E 94 0B CA 43 5D 8A 
00000020: 08 01 79 40 0A E3 40 B8 16 94 C5 A3 7F 5F 87 49 
00000030: 7C A5 89 D3 4D 1F 5C 03 31 31 39 6C 96 DD 6D 5F 
00000040: 4A 01 E5 21 AE C5 04 00 BC A0 03 70 0F 5C 13 CA 
00000050: 62 D1 BF 00 83 2A 47 37 8B FE 5B 19 1F 72 37 88 
00000060: B3 AE FF 3F 00 89 19 08 5A D2 B5 83 AA 62 A3 84 
00000070: 8F D2 4A 8F 00 00 77 00 01 00 00 00 11 3C 68 74 
00000080: 6D 6C 3E 0D 0A 3C 68 65 61 64 3E 0D 0A 20 20 20 
00000090: 20 3C 74 69 74 6C 65 3E 48 6F 6D 65 3C 2F 74 69 
000000A0: 74 6C 65 3E 0D 0A 3C 2F 68 65 61 64 3E 0D 0A 3C 
000000B0: 62 6F 64 79 3E 0D 0A 20 20 20 20 3C 68 31 20 73 
000000C0: 74 79 6C 65 3D 22 66 6F 6E 74 2D 73 74 79 6C 65 
000000D0: 3A 69 74 61 6C 69 63 22 3E 48 6F 6D 65 3C 2F 68 
000000E0: 31 3E 0D 0A 3C 2F 62 6F 64 79 3E 0D 0A 3C 2F 68 
000000F0: 74 6D 6C 3E 
------------- headers frame -------------
:status: 200
server: nginx/1.12.2
date: Wed, 18 Apr 2018 02:40:14 GMT
content-type: text/html
content-length: 119
last-modified: Sun, 08 Apr 2018 01:08:28 GMT
etag: "5ac96b8c-77"
accept-ranges: bytes
------------- data frame -------------
<html>
<head>
    <title>Home</title>
</head>
<body>
    <h1 style="font-style:italic">Home</h1>
</body>
</html>
其他数据帧的格式可以参考RFC。