# 6. http2协议
背景介绍就到此为止了,历史的脚步将我们推到了今天这一步。现在让我们深入看看该协议的规范,看看那些细节和概念。
## 6.1. 二进制
http2是一个二进制协议。
仔细想想,如果你是一个曾经跟互联网协议打过交道,那你很可能本能反对二进制协议,你甚至准备好了一大堆理由来证明基于文本/ascii的协议是多么的有用,正如你曾无数次地通过手工输入HTTP请求来通过telnet远程登陆。
二进制的http2可以使成帧更便捷。在HTTP1.1和其他基于文本的协议中,识别帧的起始和结束相当复杂。而在移除掉可选的空白符和其他冗余后,实现这些会变得更容易。
另一方面,从帧结构中分离出协议本身的部分也变得更容易。而在HTTP1中,各个部分相互交织,一团乱麻。
协议的压缩特点和其经常运行在TLS之上的事实让纯文本的属性值变得毫无作用,毕竟也无法从数据流上看到文本。我们需要习惯于使用类似Wireshark的工具来从协议层面对http2一探究竟。
调试这样的协议将需要curl这样的工具,要进一步地分析网络数据流需要类似Wireshark的http2解析器。
## 6.2. 二进制格式
http2发送二进制帧。帧的类型有很多种,但他们都有如下的公共字段:
Type, Length, Flags, Steam Identifier和frame payload
![](https://box.kancloud.cn/2015-08-09_55c75bc1d0f63.png)
http2的规范一共定义了10种不同的帧,其中最基础的两种分别对应于HTTP 1.1的DATA和HEADERS。之后我会更详细的介绍其中一些帧。
## 6.3. 多路复用的流
上一节提到的Stream Identifier定义了二进制帧的格式,http2连接上传输的每个帧都关联到一个“流”。流是一个逻辑上的联合,一个独立的,双向的帧序列。这一系列帧在客户端和服务器中通过http2连接进行交换。
每个单独的http2连接都可以包含多个并发的流,这些流中交错的包含着来自两端的帧。流既可以被客户端/服务器端单方面的建立和使用,也可以被双方共享,或者被任意一边关闭。在流里面,每一帧发送的顺序非常关键。接收方会按照收到帧的顺序来进行处理。
流的多路复用意味着在同一连接中来自各个流的数据包被混合在一起。两个(或者更多)独立的“数据列车”被拼凑到了一辆列车上,最终在终点站被分开。下图就是两列“数据火车”的示例
![](https://box.kancloud.cn/2015-08-09_55c75bc1e45d9.png)
它们就是这样通过多路复用的方式被组装到了同一列火车上。
![](https://box.kancloud.cn/2015-08-09_55c75bc2174c2.png)
在http2里面,我们很容易可以看到10个甚至100个同时并存的流。创建一个新的流的代价也非常低。
## 6.4. 优先级和依赖性
每个流都包含一个优先级,优先级被用来告诉对端哪个流更重要。
优先级的工作机制在协议中被改变多次,至今仍在讨论。重点在于要让客户端能指定哪个流更重要,并且提供一个依赖参数来指定流的依赖关系。
优先级能动态的被改变。这样当用户滚动一个全是图片的页面的时候,浏览器能够指定哪个图片有更高的优先级。或者是在你切换标签页的时候,浏览器可以提升新切换到页面所包含流的优先级。
## 6.5. 头压缩
HTTP是无状态协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为http2没有改变这个范式,所以它也需要这样(携带所有细节)。
这也保证了HTTP可重复性。当一个客户端从同一服务器请求一些资源(例如页面的图片)的时候,这些请求看起来几乎是一致的。而这些大量一致的东西正好值得被压缩。
当每个页面资源的个数上升的时候,cookies和请求的大小都会增加,而每个请求都会包含的cookie几乎是一模一样的。
![](https://box.kancloud.cn/2015-08-09_55c75bc23b50c.png)
HTTP 1.1请求的大小变得越来越大,有时甚至会大于TCP窗口的初始大小,这会严重拖累发送请求的速度。因为它们需要等待带着ACK的响应回来以后,才能继续被发送。这也是需要压缩的理由。
**6.5.1. 压缩是非常棘手的课题**
HTTPS和SPDY的压缩机制被发现有受[BREACH](http://en.wikipedia.org/wiki/BREACH_%28security_exploit%29)和[CRIME](http://en.wikipedia.org/wiki/CRIME)攻击的隐患。通过向流中注入一些已知的文本来观察输出的变化,攻击者可以推出原始发送的数据。
为协议的动态内容进行压缩并使其免于被攻击,需要仔细且全面的考虑。而这正是HTTPbis小组尝试去做的。
[HPACK](http://www.rfc-editor.org/rfc/rfc7541.txt),_HTTP/2头部压缩_,顾名思义它是一个专为http2头部设计的压缩格式。确切的讲,它甚至被制定写入在另外一个单独的草案里。新的格式同时引入了一些其他对策让破解压缩变得困难,例如采用帧的可选填充和用一个bit作为标记,来让中间人不压缩指定的头部。
用Roberto Peon(HPACK的设计者之一)的话说,“HPACK旨在提供一个一致性的实现使信息量的损失尽可能少,使编解码快速而方便,使接收方能控制压缩文本的大小,允许代理重新建立索引(如,通过代理在前后端共享状态),以及对哈夫曼编码串的更快速比较”。
## 6.6. 重置 - 后悔药
HTTP 1.1的有一个缺点是:当一个含有确切值的Content-Length的HTTP消息被送出之后,你就很难中断它了。当然,通常你可以断开整个TCP链接(但也不总是可以这样),但这样导致的代价就是需要重新通过三次握手建立一个新的TCP连接。
一个更好的方案是只终止当前传输的消息并重新发送一个新的。在http2里面,我们可以通过发送RST_STREAM帧来实现这种需求,从而避免浪费带宽和中断已有的连接。
## 6.7. 服务器推送
这个功能通常被称作“缓存推送”。主要的思想是:当一个客户端请求资源X,而服务器知道它很可能也需要资源Z的情况下,服务器可以在客户端发送请求前,主动将资源Z推送给客户端。这个功能帮助客户端将Z放进缓存以备将来之需。
服务器推送需要客户端显式的允许服务器提供该功能。但即使如此,客户端依然能自主选择是否需要中断该推送的流。如果不需要的话,客户端可以通过发送一个RST_STREAM帧来中止。
## 6.8. 流量控制
http2上面每个流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。如果你正好知道SSH的工作原理的话,这两者非常相似。
对于每个流来说,两端都必须告诉对方自己还有更多的空间来接受新的数据,而在该窗口被扩大前,另一端只被允许发送这么多数据。只有数据帧受流量控制。