HTTP/2也被称为HTTP 2.0,相对于HTTP 1.1新增多路复用、压缩HTTP头、划分请求优先级、服务端推送等特性,解决了在HTTP 1.1中一直存在的问题,优化了请求性能,同时兼容了HTTP 1.1的语义。

2015年,HTTP/2 发布。HTTP/2是现行HTTP协议(HTTP/1.1)的替代,但它不是重写,HTTP方法、状态码、语义都与HTTP/1.1一样。HTTP/2 相比于 HTTP/1.1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作。HTTP/2基于SPDY,专注于性能,最大的一个目标是在用户和网站间只用一个连接(connection)。


SPDY 是 HTTP2 的前身,大部分特性与 HTTP2 保持一致,包括服务器端推送,多路复用和帧作为传输的最小单位。但 SPDY 与 HTTP2 也有一些实现上的不同,比如 SPDY 的头部压缩使用的是 DEFLATE 算法,而 HTTP2 使用的是 HPACK 算法,压缩率更高。


【通讯】http2_nginx

HTTP/2新特性

01、二进制传输

HTTP/2传输数据量的大幅减少,主要有两个原因:以二进制方式传输和Header 压缩。先来介绍一下二进制传输,HTTP/2 采用二进制格式传输数据,而非HTTP/1.1 里纯文本形式的报文 ,二进制协议解析起来更高效。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。HTTP/2所有性能增强的核心在于新的二进制分帧层,它定义了如何封装http消息并在客户端与服务器之间传输。

【通讯】http2_服务器_02

02、Header压缩

HTTP/1.1的header带有大量信息,而且每次都要重复发送,HTTP/2并没有使用传统的压缩算法,而是开发了专门的“HPACK”算法,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。

【通讯】http2_服务器_03

03、多路复用

多路复用允许同时通过单一的HTTP/2连接发起多重的请求-响应信息,很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也更容易实现全速传输。

04、服务器推送

【通讯】http2_客户端_04

HTTP2还在一定程度上改变了传统的“请求-应答”工作模式,服务器不再是完全被动地响应请求,也可以新建“流”主动向客户端发送消息。比如,在浏览器刚请求HTML的时候就提前把可能会用到的JS、CSS文件发给客户端,减少等待的延迟,这被称为”服务器推送”( Server Push,也叫 Cache push)。

05、应用层的重置连接

对于 HTTP/1 来说,是通过设置 tcp segment 里的 reset flag 来通知对端关闭连接的。这种方式会直接断开连接,下次再发请求就必须重新建立连接。HTTP/2 引入 RST_STREAM 类型的 frame,可以在不断开连接的前提下取消某个 request 的 stream,表现更好。

06、请求优先级设置

HTTP/2 里的每个 stream 都可以设置依赖 (Dependency) 和权重,可以按依赖树分配优先级,解决了关键请求被阻塞的问题。

07、流量控制

每个 http2 流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。对于每个流来说,两端都必须告诉对方自己还有足够的空间来处理新的数据,而在该窗口被扩大前,另一端只被允许发送这么多数据。

08、HTTP/1 的几种优化可以弃用

合并文件、内联资源、雪碧图、域名分片对于 HTTP/2 来说是不必要的,使用 h2 尽可能将资源细粒化,文件分解地尽可能散,不用担心请求数多。

HTTP/1.1 存在的问题:

1、TCP 连接数限制

对于同一个域名,浏览器最多只能同时创建 6~8 个 TCP 连接 (不同浏览器不一样)。为了解决数量限制,出现了 域名分片 技术,其实就是资源分域,将资源放在不同域名下 (比如二级子域名下),这样就可以针对不同域名创建连接并请求,以一种讨巧的方式突破限制,但是滥用此技术也会造成很多问题,比如每个 TCP 连接本身需要经过 DNS 查询、三步握手、慢启动等,还占用额外的 CPU 和内存,对于服务器来说过多连接也容易造成网络拥挤、交通阻塞等,对于移动端来说问题更明显,

2、线头阻塞 (Head Of Line Blocking) 问题

每个 TCP 连接同时只能处理一个请求 - 响应,浏览器按 FIFO 原则处理请求,如果上一个响应没返回,后续请求 - 响应都会受阻。为了解决此问题,出现了 管线化 - pipelining 技术,但是管线化存在诸多问题,比如第一个响应慢还是会阻塞后续响应、服务器为了按序返回相应需要缓存多个响应占用更多资源、浏览器中途断连重试服务器可能得重新处理多个请求、还有必须客户端 - 代理 - 服务器都支持管线化。

3、Header 内容多,而且每次请求 Header 不会变化太多,没有相应的压缩传输优化方案。

4、为了尽可能减少请求数,需要做合并文件、雪碧图、资源内联等优化工作,但是这无疑造成了单个请求内容变大延迟变高的问题,且内嵌的资源不能有效地使用缓存机制

5、明文传输不安全。


帧 - Frame

帧的结构

所有帧都是一个固定的 9 字节头部 (payload 之前) 跟一个指定长度的负载 (payload):

+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
  • Length 代表整个 frame 的长度,用一个 24 位无符号整数表示。除非接收者在 SETTINGS_MAX_FRAME_SIZE 设置了更大的值 (大小可以是 2^14(16384) 字节到 2^24-1(16777215) 字节之间的任意值),否则数据长度不应超过 2^14(16384) 字节。头部的 9 字节不算在这个长度里。
  • Type 定义 frame 的类型,用 8 bits 表示。帧类型决定了帧主体的格式和语义,如果 type 为 unknown 应该忽略或抛弃。
  • Flags 是为帧类型相关而预留的布尔标识。标识对于不同的帧类型赋予了不同的语义。如果该标识对于某种帧类型没有定义语义,则它必须被忽略且发送的时候应该赋值为 (0x0)。
  • R 是一个保留的比特位。这个比特的语义没有定义,发送时它必须被设置为 (0x0), 接收时需要忽略。
  • Stream Identifier 用作流控制,用 31 位无符号整数表示。客户端建立的 sid 必须为奇数,服务端建立的 sid 必须为偶数,值 (0x0) 保留给与整个连接相关联的帧 (连接控制消息),而不是单个流。
  • Frame Payload 是主体内容,由帧类型决定。

共分为十种类型的帧:

  • ​HEADERS​​: 报头帧 (type=0x1),用来打开一个流或者携带一个首部块片段。
  • ​DATA​​: 数据帧 (type=0x0),装填主体信息,可以用一个或多个 DATA 帧来返回一个请求的响应主体。
  • ​PRIORITY​​: 优先级帧 (type=0x2),指定发送者建议的流优先级,可以在任何流状态下发送 PRIORITY 帧,包括空闲 (idle) 和关闭 (closed) 的流。
  • ​RST_STREAM​​: 流终止帧 (type=0x3),用来请求取消一个流,或者表示发生了一个错误,payload 带有一个 32 位无符号整数的错误码 (Error Codes),不能在处于空闲 (idle) 状态的流上发送 RST_STREAM 帧。
  • ​SETTINGS​​​: 设置帧 (type=0x4),设置此 ​​连接​​ 的参数,作用于整个连接。
  • ​PUSH_PROMISE​​: 推送帧 (type=0x5),服务端推送,客户端可以返回一个 RST_STREAM 帧来选择拒绝推送的流。
  • ​PING​​: PING 帧 (type=0x6),判断一个空闲的连接是否仍然可用,也可以测量最小往返时间 (RTT)。
  • ​GOAWAY​​: GOWAY 帧 (type=0x7),用于发起关闭连接的请求,或者警示严重错误。GOAWAY 会停止接收新流,并且关闭连接前会处理完先前建立的流。
  • ​WINDOW_UPDATE​​: 窗口更新帧 (type=0x8),用于执行流量控制功能,可以作用在单独某个流上 (指定具体 Stream Identifier) 也可以作用整个连接 (Stream Identifier 为 0x0),只有 DATA 帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减少多少,如果流量窗口不足就无法发送,WINDOW_UPDATE 帧可以增加流量窗口大小。
  • ​CONTINUATION​​: 延续帧 (type=0x9),用于继续传送首部块片段序列,

DATA 帧格式

+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
  • ​Pad Length​​: ? 表示此字段的出现时有条件的,需要设置相应标识 (set flag),指定 Padding 长度,存在则代表 PADDING flag 被设置
  • ​Data​​: 传递的数据,其长度上限等于帧的 payload 长度减去其他出现的字段长度
  • ​Padding​​: 填充字节,没有具体语义,发送时必须设为 0,作用是混淆报文长度,与 TLS 中 CBC 块加密类似,

HTTP/2 演示

访问地址


​https://http2.akamai.com/demo​


【通讯】http2_服务器_05

可以直接从访问速度上看到两者的最直观区别。

浏览器支持情况

HTTP/2正在或即将被所有主流的浏览器支持

Chrome 40支持了HTTP/2第14草案,但是默认情况下没有启用。HTTP/2第17草案(最终草案)被Chrome Canary 43(预发布开发版)所使用。目前仅仅基于TLS(加密)的HTTP/2才被支持。

想要在Chrome浏览器启用启用HTTP/2,可以访问链接:

chrome://flags/#enable-spdy4

火狐浏览器已经支持了HTTP/2并且从36版本开始默认是开启的。34版本中火狐浏览器开始实验性质地支持HTTP/2。目前仅仅实施了基于TLS的HTTP/2。

IE11仅仅在 Windows 10 beta版本支持HTTP/2,默认情况下是启用的,目前也是仅仅支持基于TLS的HTTP/2。

Spartan浏览器被期待支持基于TLS的HTTP/2,微软为Windows 10打造的新浏览器。

Safari在Mac OS X Yosemite (10.10)和iOS 8默认支持SPDY。预计在2015年底去全面支持HTTP/2。

Opera默认也是支持SPDY。一旦HTTP/2草案在Chrome浏览器中默认支持的时候,Opera也会全面支持。

【通讯】http2_服务器_06

服务端支持情况

支持HTTP/2

IIS (Internet Information Services) 在Windows 10 beta版本中支持HTTP/2。

OpenLiteSpeed在1.3.8和1.4.5中支持HTTP/2草案17.

支持SPDY, 但是不支持HTTP/2

Apache通过mod_spdy模块支持老版本的SPDY,但是目前这个模块已经停止开发了。

LiteSpeed Web Server目前支持SPDY/3.1。

Nginx通过模块提供实验性质的而支持SPDY (草案 3.1),并且计划在2015年底开始支持HTTP/2。

这里先说明一下,要完成http2的请求需要客户端和服务端同时支持,如下表格可以看出,只要客户端或服务端任意一端不支持http2,都会自动降级到http1.1:

【通讯】http2_服务器_07

如何支持http2

安装最新的nginx

安装完毕之后会告诉我们一些有用的信息:

Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
brew services start nginx
Or, if you don't want/need a background service you can just run:
nginx

开启HTTP2支持

从上面可以知道,nginx默认的配置文件是/usr/local/etc/nginx/nginx.conf,打开该文件可以看到最后一行:

include servers/*;

所以我们可以在servers中新建一个www.flydean.com.conf的文件作为今天要开启HTTP2支持的域名。

默认情况下,nginx监听的端口是80,如下所示:

listen 80 default_server;
listen [::]:80 default_server;

为什么会有两个listen呢?第一个listen指的是所有的IPv4连接,第二个listen指的是IPv6连接。

因为HTTP2需要开启SSL支持,所以我们这里将其修改为443,并且加上http2支持如下所示:

listen       443 ssl http2;
server_name www.flydean.com;

上面的配置中我们还指定了server_name,这就是要访问的域名地址,这里我们使用www.flydean.com。

添加SSL支持

要想添加SSL支持就需要添加证书,一种方式是购买或者在网上有一些免费的SSL证书可用,如果只是在测试环境中的话,还可以生成自签名证书。

这里我们介绍一下如何生的自签名证书。这里我们使用openssl命令来完成这个工作。

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout selfsigned.key -out selfsigned.crt
Generating a RSA private key

执行完成上面的命令,会要求你输入一些证书的信息如下:

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:SH
Locality Name (eg, city) []:SH
Organization Name (eg, company) [Internet Widgits Pty Ltd]:flydean
Organizational Unit Name (eg, section) []:flydean
Common Name (e.g. server FQDN or YOUR name) []:127.0.0.1
Email Address []:flydean@163.com

然后就生成了两个文件:selfsigned.crt和selfsigned.key。

这里稍微讲解一下自签名证书生成的命令。

openssl是一个非常强大的密钥生成工具,可以完成绝大多数的密钥生成工作。

req表示的是这是一个X.509 certificate signing request (CSR)。

-x509表示我们希望生成的是一个自签名的证书。

-nodes表示我们不需要对生成的密钥进行密码加密。

-days 365表示证书的有效期。

-newkey rsa:2048表示使用RSA算法同时生成证书和key,key的长度是2048。

-keyout:指定key的生成路径。

-out:指定证书的生成路径。

这里即使是使用了SSL,为了保证安全,我们还可以使用一项叫做完美的向前保密的技术,这里需要生成Diffie-Hellman group:

openssl dhparam -out dhparam.pem 2048

这个命令会需要一些时间,生成之后,我们就可以开始nginx的SSL配置了。

ssl_certificate      ssl/selfsigned.crt;
ssl_certificate_key ssl/selfsigned.key;

修改加密算法

我们知道已经存在很多加密算法,随着密码学技术的发展,很多算法已经被证明是不安全的。所以这里我们需要对默认的加密算法进行修改。

默认的算法是:

ssl_ciphers  HIGH:!aNULL:!MD5;

我们将其修改为:

ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;

Diffie–Hellman对消息进行加密

虽然我们使用private key配置了客户端和服务器端的加密连接,在建立连接之后,在ServerKeyExchange这一步,双方还会询问对信息的加密方式来用来构建加密通道。

ServerKeyExchange的内容可能包含两种形式:

  • 如果选择的是RSA协议,那么传递的就是RSA构建公钥密码的参数(E,N)。我们回想一下RSA中构建公钥的公式:密文=明文E mod N密文=明文E mod N, 只要知道了E和N,那么就知道了RSA的公钥,这里传递的就是E,N两个数字。
  • 如果选择的是Diff-Hellman密钥交换协议,那么传递的就是密钥交换的参数,

这里我们选择使用Diffie–Hellman,还记得上一小节,我们创建的Diffie–Hellman文件吗?这里直接使用即可。

默认情况下Nginx使用的是1028-bit DHE (Ephemeral Diffie-Hellman) key,这个比较容易被破解,所以需要使用我们自己生成的文件。

ssl_dhparam  ssl/dhparam.pem;

重定向所有的HTTP请求到HTTPS

默认情况下我们访问网站都是HTTP的,所以需要将HTTP请求重定向到HTTPS:

server {
listen 80;
listen [::]:80;
server_name www.flydean.com;
return 301 https://$server_name$request_uri;
}

启动nginx并测试

好了,到此为止所有的nginx配置都完成了,我们使用下面的命令测试nginx文件和启动:

nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

nginx

要访问网站,还需要配置一下host将 www.flydean.com 指到你的nginx server上。

然后就可以访问www.flydean.com了。

这里可能会出现一个问题,如果你是自签名的证书,在chrome默认的安全环境中会认为这个证书是无效的,还需要将该证书加入证书的信任链中。

怎么看出这个网站到底使用的那种协议呢?

打开浏览器的调试开关,到网络的tab,点击访问的页面,可以看到下面的内容:

【通讯】http2_服务器_08

可以看到版本是HTTP/2并且响应头带有X-Firefox-Spdy h2。


关注公众号 soft张三丰 

【通讯】http2_nginx_09