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。