HTTP2.0 简明笔记

前言

RFC2616发布以来,一直是互联网发展的基石。HTTP协议也成为了可以在任何领域使用的核心协议,基于这个协议人们设计和部署了越来越多的应用。HTTP的简单本质是其快速发展的关键,但随着越来越多的应用被部署到WEB上,HTTP的问题慢慢凸显出来。今天,用户和开发者都迫切需要通过THHP1.1达到一种几近实时的响应速度和协议性能,而要满足这个需求,只在原有协议上进行修补是不够的。为了应对这些挑战,HTTP必须继续发展。HTTP工作组已经在2012年宣布要设计和开发HTTP2.0。HTTP2.0的主要目标是改进传输性能,实现低延迟和高吞吐量。

在HTTP2.0真正诞生之前,谷歌开发了一个实验性质的协议-SPDY,它定位于解决HTTP1.1中的一些性能限制,来减少网页的延时。自从2009年SPDY发布之后,这个协议得到了众多浏览器厂商和大型网站的支持,实践证明它确实可以很大幅度的提升性能,已经具备成为一个标准的条件。于是,HTTP-WG于2012年初提出要重在SPDY的一些实践基础上新设计和开发HTTP2.0,以期使数据传输具有更好的性能和更少的延时。SPDY是HTTP2.0的先驱,但二者并不能初略的划为等号,SPDY V2草案是HTTP2.0标准制定的起点,从此之后SPDY标准并没有停滞,而是在不断进化,它成为了HTTP2.0新功能及新建议的实验场,为HTTP2.0标准收纳的每一项建议,提供事前的测试和评估手段,总体来说SPDY比HTTP2.0更为激进。HTTP2.0协议版本发布历程如下:

  • 2012年3月,HTTP2.0征集建议;
  • 2012年11月,HTTP2.0第一稿;
  • 2014年8月,HTTP2.0 draft-17和HPACK draft-12发布;
  • 2014年8月,工作组最后征集HTTP2.0建议;
  • 2015年2月,IESG批准HTTP2.0和HPACK草稿;
  • 2015年5月,RFC 7540 (HTTP2.0) 和 RFC 7541 (HPACK) 发布;

在新的协议中,将会从根本上解决以往HTTP1.1版本中所做的“特殊优化”,将在这些解决方案内置在传输层中,使数据传输更加便捷和高效,如HTTP1.1及以前的版本中影响性能的很大一个问题,就是队首阻塞问题,在HTTP2.0中会将会通过新的组帧机制来解决这个问题,使连接可以多路复用,再通过压缩HTTP首部字段将协议开销降到最低。HTTP2.0不会改动HTTP语义,很好的继承以往版本的HTTP方法、状态码、URI及首部字段等核心概念,下面将对这些内容进行细致的描述。(本文最初发布于公司内网,外网原文地址:腾云阁--HTTP 2.0 简明笔记)

1.二进制分帧

HTTP2.0的根本改进还是新增的二进制分帧层,不同于HTTP1.X使用换行符分割纯文本,二进制分帧层更加简洁,处理起来也更加高效。这里所谓的层,指的是位于套接字接口与应用可见的高层HTTP API间的一个新机制:HTTP语义,包括各种动词、方法、首部,都不受影响,不同的是传输它们的编码方式变了。HTTP 1.X以换行符作为纯文本的分隔符,而HTTP 2.0将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。示例如下:

由上图可知,在HTTP1.1传输数据时,首部和数据是一块发送的,每次传输都是需要带有首部信息;在HTTP2.0中,首部信息被分为一个独立的帧进行发送,数据帧在在首部帧发送后,再进行发送,而且采用首部压缩后,每次传输只需要发送改动的部分即可,极大提高了数据发送的效率。

1.1.分帧首部

在建立HTTP2.0连接之后,客户端与服务器会通过交换帧进行通信,帧是HTTP2.0协议的最小单位。所有的帧都拥有一个8字节的首部,具体格式如下:

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | R |     Length (14)           |   Type (8)    |   Flags (8)   |
 +-+-+-----------+---------------+-------------------------------+
 |R|                 Stream Identifier (31)                      |
 +=+=============================================================+
 |                   Frame Payload (0...)                      ...
 +---------------------------------------------------------------+
  • R : 保留的2位字段。这些字节的语义是未定义的,并且在发送的时候必须保持未设置(0),接收的时候必须被忽略此字段。
  • Length : 14位无符号整数的帧主体长度。8字节长度的帧报头信息不计算在此内,主体最大可能长度为2^14-1(16383)字节,整个帧(包括首部)的最大长度是最大的帧长度是16391字节。
  • Type : 帧的8位类型。帧类型定义了剩余的帧报头和帧主体将如何被解释。具体实现必须在收到未知帧类型(任何未在文档中定义的帧)时作为连接错误中的类型协议错误(PROTOCOL_ERROR)处理。
  • Flags : 为帧类型保留的8字节字段有具体的布尔标识。 标识针对确定的帧类型赋予特定的语义。确定帧类型定义语义以外的标示必须被忽略,并且必须在发送的时候保留未设置(0)。
  • R : 1位的保留字段。这个字段的语义未设置并且必须在发送的时候保持未设置(0),在接受的时候必须被忽略。
  • Stream Identifier : 31字节的流标识符,唯一标识HTTP2.0的流。0是保留的,标明帧是与连接相关作为一个整体而不是一个单独的流。

通过这个共享的8字节首部,可以很快确定帧的类型、标志和长度,而且每一帧的长度也是事先定义好的,解析器也可以迅速的找到下一帧的开始,并进行解析,这对于HTTP1.1X来说也是一个很大的提升。

1.2.帧类型

不管是在连接管理或单独的流中,每种帧都为了特定的目的而服务。在HTTP2.0中,共定义了以下10种帧类型:

  • DATA:用于传输HTTP消息体;
  • HEADERS:用于传输关于流的额外的首部字段;
  • PRIORITY:用于指定或重新指定引用资源的优先级;
  • RST_STREAM:用于知道流的非正常终止;
  • SETTINGS:用于通知两端通信方式的配置数据;
  • PUSH_PROMISE:用于发出创建流和服务器引用资源的要约;
  • PING:用于计算往返时间,执行活性检查;
  • GOWAY:通知远端对等端不要在这个连接上建立新流;
  • WINDOW_UPDATE:用于针对个别流或个别连接实现流量控制;
  • CONTINUATION:用于继续一系列首部片段。

注:服务器可以利用GOWAY类型的帧告诉客户端要处理的最后一个流ID,从而消除一些请求竞争,而且浏览器也可以据此智能地重试或取消“悬着的”请求。这也是保证复用连接安全的一个重要和必要的功能。

HTTP2.0性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装HTTP消息并在客户端与服务器之间传输。由于在HTTP2.0中使用了新的组帧方式,这样一来,客户端和服务器为了相互理解,必须都使用新的二进制编码机制,所以HTTP1.x客户端无法理解只支持HTTP2.0的服务器,反之亦然。但这并不影响我们使用HTTP2.0,现有的应用不用关注这些变化,因为客户端和服务器会替它们完成必要的分帧工作。

1.3.流、消息和帧

新的二进制分帧机制改变了客户端与服务器之间交互数据的方式,为了更好的理解HTTP2.0中这一核心变化,下面介绍流、消息、帧这三个概念及区别:

  • 流(stream):已建立的连接上的双向字节流,HTTP/2连接中的虚拟通道。
  • 消息(message):与逻辑消息对应的完整的一系列数据帧。
  • 帧(frame):HTTP2.0通信的最小单位,包括根据帧类型结构的字节的报头和可变长度的序列,每个帧包含首部。

所有HTTP2.0通信都在一个TCP连接上完成,这个连接可以承载任意数量的双向数据流。相应的,每个数据流以消息的形式发送,而消息由一个或多个帧组成,这些帧可以乱序发送,然后根据每个帧首部的流标识符重新组装。帧是HTTP2.0中的最小通信单位,不同类型的帧有特殊的作用。

2.多路复用

2.1.队列传输与多路复用

在HTTP1.x中,如果客户端想发送多个并行的请求以及改进性能,那么必须使用多个TCP连接,而且存在队首阻塞问题,严重影响数据的传输效率。在HTTP2.0中,二进制分帧机制突破了这些限制,实现了多向请求和响应,客户端和服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,然后在接收端重新组装,这样就完成了消息的传输。示例如下:

由上图可知,在同一个连接中,有三个流在同时传输,有两个是从服务端发给客户端的,也有一个是从客户端发送到服务端,这就大大的提高了连接的利用率。
这个新的机制是革命性的,会在整个WEB技术栈中引发一系列连锁反应,从而带来巨大的性能提升,因为:

  • 可以并行交错地发送请求,请求之间互不影响;
  • 可以并行交错地发送响应,响应之间互不干扰;
  • 只使用一个连接即可并行发送多个请求和响应;
  • 消除不必要的延迟,从而减少页面加载时间;
  • 不必再为绕过HTTP1.X限制而做多余的工作。

HTTP2.0的二进制分帧机制解决了HTTP1.X中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。这样,就会使应用更快、开发更简单、部署成本更低,也会减少客户端和服务器的CPU及内存占用。

2.2.每个来源一个连接

在可以实现连接的多路复用后,HTTP2.0不再依赖多个TCP连接去实现多流并行了。现在,多个数据流都拆分成很多帧,而这些帧可以交错,还可以分别优先级。于是,所有的 HTTP2.0连接都是持久化的,而且客户端与服务器之间也只需要一个连接即可。每个来源一个连接显著减少了相关的资源占用:连接路径上的套接字管理工作少了,内存占用少了,连接吞吐量大了。所以,很多层面上都有不小的提高:

  • 所有数据流的优先级次序始终如一;
  • 压缩上下文单一使得压缩效果更好;
  • 由于TCP连接减少而使网络拥塞状况得以改观;
  • 慢启动时间减少,拥塞和丢包恢复速度更快。

大多数HTTP连接的时间都很短,而且是突发性的,但TCP只在长时间连接传输大块数据时效率才高。HTTP2.0通过让所有数据流共用同一个连接,可以更有效地使用TCP连接。HTTP2.0不仅能够减少网络延迟,还有助于提高吞吐量和降低运营成本。
注:任何事物都有其两面性,每个来源一个连接当然也会带来一些问题:

  • 虽然消除了HTTP队首阻塞现象,但TCP层次上仍然存在队首阻塞;
  • 如果TCP窗口缩放被禁用,那带宽延迟积效应可能会限制连接的吞吐量;
  • 丢包时,TCP拥塞窗口会缩小。

虽然有上述问题影响HTTP2.0的性能,但实验表明一个TCP连接仍然是目前最佳的策略:压缩和优先级排定带来的性能提升,已经超过了队首阻塞造成的负面效应。与所有其他的性能优化一样,去掉一个性能瓶颈,又会带来新的瓶颈。对HTTP2.0而言,TCP很可能就是下一个瓶颈。这也是服务器端TCP配置对HTTP2.0至关重要的一个原因。

3.请求优先级

在HTTP消息被分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,可以让最紧要的帧优先发送,以确保关键任务的快速展开。那么如何定义这些帧的发送顺序就是一个难题,为方便起见,在HTTP / 2标准允许每个流具有相关联的权重和依赖性:

  • 每个流可被分配1~256之间的权重值,具有相同父节点的流应该根据权重比例来分配资源;
  • 每个流可声明对另一流的显式依赖性,包含一个依赖偏好设置表示分配资源给特定的流而不是所依赖的流。

通过流依赖和权重,客户端可以构建一个“优先级树”(如下图所示),将这个树发送到服务端,表达客户端愿意以怎样的方式接收响应;服务端在接收到该“优先级树”后,可以根据这个信息,通过协调相关服务器资源返回响应,如CPU、存储器、网络等资源,优先处理高优先级的流。并且一旦响应数据是可用的,服务端将分配更多的带宽以确保高优先级的快速响应。

HTTP 2.0内的一个流只能设置一个唯一的流依赖,被依赖流就是当前流的父节点流,如果省略了流依赖的声明,则默认依赖于“根流”。声明一个流的依赖性表明,如果可能的话,父流应先于子流进行资源分配和响应,例如请处理和响应C前交付响应D。
具有相同的父流的同级流应该按照其权重比例分配服务器资源。例如,如果流A具优先级重量为12,他的同级兄弟流B的权重为4,那么确定二者应分配的资源比例为:

  • 计算所有流的权重总和:4 + 12 = 16;
  • 计算每个流所占总和的比例:A = 12 / 16,6B = 4/16。

由上可知,如A流得到四分之三可用资源分配的话,B流应得到可利用的资源的四分之一。为了更好的理解优先级机制,下面从左到右依次介绍上图的优先级重组情况:

  • 无论是流A或B指定一个相同的父依赖,还是是依赖于隐式的“根流”:A的优先级权重为12,B的优先级权重为4,因此基于比例权重--流B应该得到分配给流A资源的三分之一;
  • D是依赖于根流,C是依赖D。因此,D应提前于C,此时权重对资源分配是无关紧要的,因为没有其他流与C同级;
  • D应在C之前接收到资源的充分分配;C应在A和B之前接收资源的充分分配;B流应该接收分配给A流资源的三分之一;
  • 在E和C之前,D应该得到充分的资源分配;E和C应在A和B之前得到平等的分配;A和B应根据它们的权重进行比例分配。

从上述示例可知,流依赖和权重的组合提供了一个表达资源优先次序的方式,这可以为不同的资源定义优先级,可以更好的提高性能。HTTP/2协议也允许客户端在任何时间点更新优先级信息,优先级信息可以像它们被创建一样使用报头帧或者使用优先级帧来明确指定或者改变。有了这个优先级标识,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。优先级的目的是允许终端表达它如何让对等端管理并发流时分配资源。更重要的是,在发送容量有限时优先级能用来选择流来传输帧。提供优先级信息是可选的,没有明确指定时使用默认值。
流依赖和权重表达一个传输偏好,而不是一个要求,因此不保证一个特定的处理或传输顺序。也就是说,客户端不能强制服务器以流优先级的优先级来处理特定顺序的流。在选择HTTP2.0服务器时,需要考虑以下几个问题:

  • 如果服务器对所有优先级视而不见怎么办?
  • 高优先值流一定优先处理吗?
  • 是否存在不同优先级的流应该交错的情况?

如果服务器不理睬所有优先值,那么可能会导致应用响应变慢:浏览器明明在等关键的CSS和JavaScript,服务器却在发送图片,从而造成渲染阻塞。然而,严格按照规定的优先级次序也可能带来次优的结果,因为这或许会再次引入队首阻塞问题,即某个高优先级的慢请求会不必要地阻塞其他资源的交付。
所以,服务器可以并且应该交错发送不同优先级别的帧,只要可能,高优先级流都应该优先传输,不过为了更高效地利用底层连接,不同优先级的混合也是必须的。因此优先级的表达仅仅是一个建议。

4.流量控制

流量控制的定义是用来保护端点在资源约束条件下的操作。流量控制解决的情况是接收端在一个流上处理数据的同时同样想继续处理同个连接上的其他流。在同一个TCP连接上传输多个数据流,就意味着要共享带宽。标定数据流的优先级有助于按序交付,但只有优先级还不足以确定多个数据流或多个连接间的资源分配,为解决这个问题,HTTP2.0为数据流和连接的流量控制提供了一个简单的机制:

  • 流量控制基于每一跳进行,而非端到端的控制;
  • 流量控制基于窗口更新帧进行,即接收方广播自己准备接收某个数据流的多少字节,以及对整个连接要接收多少字节;
  • 流量控制窗口大小通过WINDOW_UPDATE帧更新,这个字段指定了流ID和窗口大小递增机制;
  • 每个新的流及整个连接的流量控制窗口初始值是65,535字节;
  • 流控制方向性,即接收方可能根据自己的情况为每个流乃至整个连接设置任意窗口大小;流量控制可以由接收方禁用,包括针对个别的流和针对整个连接。
  • 帧类型决定了是否适用流量控制规则。本文档定义的帧中,只有DATA帧受流量控制;所有其他的帧不受广播的流量控制窗口影响。这保证了重要的控制帧不因流量控制所阻塞。

HTTP/2只标准化WINDOW_UPDATE帧格式。HTTP2.0标准没有规定任何特定的算法、值,或者什么时候发送WINDOW_UPDATE帧,因此实现可以选择自己的算法以匹配自己的应用场景,从而求得最佳性能。
流量控制方案等确保同一连接上的流相互之间不会造成破坏性的干扰。流量控制在单个流及整个连接过程中使用,HTTP/2 通过使用WINDOW_UPDATE帧类型来提供流量控制。上面的机制和TCP流量控制是一样的思路,然而仅凭TCP流量控制是不能对一条HTTP2.0连接内的多个流实行差异化策略,所以专门有了HTTP2.0流量控制机制的出现。

5.服务器推送

5.1.服务器端推送

HTTP2.0新增了一个强大的功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,出了最初请求的相应外,服务器还可以额外向用户端推送资源,而无需客户端明确地请求。示例如下:

这样一个机制需要解决的问题是什么呢?我们知道,通常一个web应用往往包含数十个资源,客户端需要分析服务器提供的文档才能逐个找到它们。那么为什么不让服务器提前就把这些资源推送给客户端,从而减少额外的延时呢?服务器已经知道客户端下一步要请求什么资源了,这时候服务器端推荐就可以大展拳脚了。事实上,网页上嵌入的CSS及JS,或通过URI嵌入的其他资源,也可以算是服务器端推送。把资源直接插入到文档中,就是把资源直接推送给客户端,而无需客户端主动请求。在HTTP2.0中,唯一的不同就是可以把这个过程从应用中拿出来,放到HTTP协议本身来实现,而且带来了如下好处:

  • 客户端可以缓存推送的资源;
  • 客户端可以拒绝推送的资源;
  • 推送资源可以由不同的页面共享;
  • 推送资源可以与其他资源一起复用;
  • 服务器可以按照优先级推送资源.

有了服务器推送后,HTTP1.X时代的插入或嵌入资源的做法就可以退出历史舞台了。唯一有必要采用嵌入资源方式的情况就是该资源只供一个页面使用,而且编码代价不大,除此之外,其他所有的场景都应该使用服务器端推送。

注:所有服务器推送流都由PUSH_PROMISE发端,它除了对原始请求的响应之外,服务器向客户端发出的有意推送所述资源的信号。PUSH_PROMISE帧中只包含有要约资源的HTTP首部。

客户端在接收到PUSH_PROMISE帧之后,可以视自身需求选择接收或拒绝这个流。服务端推送也有一些限制:

  • 首先,服务必须遵循请求-响应的循环,只能借着请求的响应推送资源,服务器端并不能随意发起推送;
  • 其次,PUSH_PROMISE帧必须在返回响应之前发送,以免客户端出现竞争状态,否则会出现客户端请求的恰好服务器打算推送的情景。

因为推送的响应是有效地逐跳,中介端接从服务端接收到推送响应的可以选择不转发这些到客户端。也就是说,如何使用推送响应取决于这些中介端。相等的,中介可能选择不推送的额外的响应给客户端,不需要服务端进行任何操作。服务端只能推送可以被缓存的响应;被承诺的请求必须是安全的,而且绝对不能包含一个请求主体。

5.2.如何实现服务器端推送

服务器推送为优化应用的资源交付提供了很多可能,然而,服务器到底该如何确定哪些资源可以或应该推送呢?HTTP2.0并没有给出详尽的规定,那么就有可能出现多种策略,每种策略可能会考虑一种应用或服务器使用场景。

  • 应用可以在自身的代码中明确发起服务器推送;
  • 应用可以通过额外的HTTP首部向服务器发送信号,列出它希望推送的资源;
  • 服务器可以不依赖应用而自动学习相关资源,通过分析来推测出需要推送的资源。

上面只是几个可能的策略,当然也有很多其他的实现方式,可能是手工调用低级的API,也可能是一种全自动的实现。总之,服务器推送领域将会出现很多有意思的创新。推送的资源将直接进入客户端缓存,就像客户端请求了一样,不存在客户端API或JS回调方法等通知机制,可以用于确定资源何时到达,整个过程对运行在浏览器中的web应用来说好像根本不存在。

虽然现在还不知道如何确定哪些资源可以或应该推送,但是如何发起推送的机制已经建立,由服务端先发起推送请求,客户端再进行推送响应。具体如下:

Push Requests

服务端推送语义上等同于服务端响应一个请求;然而,这种情况下请求也是由服务端发送的,作为一个PUSH_PROMISE帧。PUSH_PROMISE包含了一个报头区块,含有完整的服务端属性请求报头字段。推送的响应总是与客户端的一个明确的请求相关。服务端在这个明确的请求流上发送PUSH_PROMISE帧。PUSH_PROMISE帧一般包含被承诺的流标识符,从可用的服务端流标识符中选择。服务端应当在发送任何被承诺的响应之前发送一个PUSH_PROMISE帧。这避免了客户端在收到任何PUSH_PROMISE帧前发出请求而出现的竞赛。PUSH_PROMISE可以由服务端在任意由客户端打开的流上发送。

Push Responses

发送PUSH_PROMISE帧后,服务端可以开始接收被推送进来的响应作为一个由服务端发起的使用被承诺流标识的流的响应。一旦客户端接收到PUSH_PROMISE帧并且选择接受推送的响应,客户端不应该对被承诺的响应发起请求,直到被承诺的流被关闭为止。如果客户端以任何理由决定不希望接受服务端推送的响应,或者服务端花费太长时间才开始发送承诺的响应,客户端可以发送一个RST_STREAM帧,使用CANCEL或者REFUSED_STREAM码来关联被推送的流标识符。

6.首部压缩

HTTP的每一次通信都会携带一组首部,用于描述传输的资源及其属性。在HTTP1.X中,这些元数据是以纯文本形式发送的,通常会给每个请求增加500-800字节的负荷。如果算上COOKIE,增加的负荷会达到上千字节,为了减少这些开销并提升性能,HTTP2.0会压缩首部元数据:

  • HTTP2.0在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;
  • 首部表在HTTP2.0的连接存续期内始终存在,由客户端和服务器共同渐进的更新;
  • 每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值。

于是,HTTP2.0连接的两端都知道已经发送了哪些首部,这些首部的值是什么,从而可以针对之前的数据只编码发送这些差异数据。在通信期间几乎不会改变的键值对只需发送一次即可,这样就大大提高了数据的载荷。示例如下:

HTTP/1 的状态行信息(Method、Path、Status 等),在 HTTP/2 中被拆成键值对放入头部(冒号开头的那些),同样可以享受到字典和哈夫曼压缩。另外,HTTP/2 中所有头部名称必须小写。
头部压缩需要客户端和服务器端做好以下工作:

  • 维护一份相同的静态字典(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合;
  • 维护一份相同的动态字典(Dynamic Table),可以动态地添加内容;
  • 支持基于静态哈夫曼码表的哈夫曼编码(Huffman Coding)。

静态字典的作用有两个:

  • 1)对于完全匹配的头部键值对,例如 :method: GET,可以直接使用一个字符表示;
  • 2)对于头部名称可以匹配的键值对,例如 cookie: xxxxxxx,可以将名称使用一个字符表示。

同时,浏览器可以告知服务端,将 cookie: xxxxxxx 添加到动态字典中,这样后续整个键值对就可以使用一个字符表示了。类似的,服务端也可以更新对方的动态字典。需要注意的是,动态字典跟具体连接上下文有关,需要为每个 HTTP2.0 连接维护不同的字典。使用字典可以极大地提升压缩效果,其中静态字典在首次请求中就可以使用。对于静态、动态字典中不存在的内容,还可以使用哈夫曼编码来减小体积。HTTP2.0 使用了一份静态哈夫曼码表,也需要内置在客户端和服务端之中。哈夫曼编码的核心理念就是使用最少的位数表示最多的信息,HTTP2.0中这份哈夫曼编码表是根据一个大样本的HTTP报头的统计数据生成,经常出现的字符会用较短的二进制数标识,出现频率较低的字符用较长的二进制数标识,这样就保证了综合来看报头信息占用了较少的空间,进一步压缩了报头信息。

在服务端接收到压缩过的报头信息后,会先进行哈夫曼编码解码,得到报首信息后,再结合维护的静态字典和动态字典信息得出完整的报首信息,随后进行请求的处理和响应。在需要更新动态字典信息时,对字典进行更新。

HTTP2.0压缩算法:SPDY早期版本使用zlib和自定义的字典压缩所有的HTTP首部,可以减少85%-88%的首部开销,从而显著减少加载页面的时间。然而,在2012年夏天,出现了针对TLS和SPDY压缩算法的CRIME安全攻击,于是,zlib算法被撤销,取而代之的是前面介绍的新索引表算法。该算法没有类似的安全问题,但可以实现相差无几的性能提升。

7.HTTP2.0升级与发现

向HTTP2.0的迁移不可能瞬间完成,无论服务器端还是客户端都需要进行必要的更新升级才能使用。好消息是,大多数现代浏览器都内置有高效的后台升级机制,对大多数既有用户来说,这些浏览器可以很快的支持HTTP2.0,不会带来很大困扰。然而,服务器端和中间设备的升级、更新就不是那么容易,是一个长期的过程,而且很费力、费钱。

HTTP1.X至少还会存续十年以上,大多数服务器和客户端在此期间必须同时支持1.x和2.0标准。于是,支持HTTP2.0的客户端在发起新请求之前,必须能发现服务器及中间设备是否支持HTTP2.0协议。有以下三种情况:

  • 通过TLS和ALPN发起的新的HTTPS连接;
  • 根据之前的信息发起的新的HTTP连接;
  • 没有之前的信息而发起新的HTTP连接;

HTTPS协商过程中有一个环节会使用ALPN发现和协商HTTP2.0支持情况。有 ALPN 的情况下 TLS 握手信息中包含了客户端支持的协议列表,服务端直接选择 HTTP2,所有协商在握手阶段一次完成,无需额外的报文。
注:应用层协议谈判(ALPN)是一个TLS扩展,支持在TLS握手过程中进行协议协商,从而省去通过HTTP的Upgrade机制所需的额外往返延迟。过程如下:

  • 客户在ClientHello消息添加新的ProtocolNameList字段,包含支持的应用程序协议列表。
  • 该服务器检查ProtocolNameList字段,并在ServerHello消息中返回一个ProtocolName字段,用来指示服务器端选择的协议。
  • 服务器可能只响应其中一个协议,如果它不支持任何客户端要求的协议,那么它可能选择中止连接。其结果是,TLS握手完成后,安全隧道建立好了,客户端和服务端也协商好了所使用的应用协议 - 它们可以立即开始通信。

通过非加密信道建立HTTP2.0连接需要多做一些工作,因为HTTP1.0和HTTP2.0都使用80端口,又没有服务器是否支持HTTP2.0的其他任何信息,此时客户端只能使用HTTP upgrade 机制通过协商确定适当的协议:

GET /default.htm HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

不支持HTTP/2的服务端对请求返回一个不包含升级的报头字段的响应:

HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
...

支持HTTP/2的服务端可以返回一个101(转换协议)响应来接受升级请求:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
...

在101空内容响应终止后,服务端可以开始发送HTTP/2帧。这些帧必须包含一个发起升级的请求的响应。第一个被服务端发送的HTTP/2帧是一个设置(SETTINGS)帧。在收到101响应后,客户端发送一个包含设置(SETTINGS)帧的连接序言。

使用这种Upgrade流,如果服务器不支持HTTP2.0,就立即返回HTTP1.1响应。否则,服务器就会以HTTP1.1格式返回101 switching protocols响应,然后立即切换到HTTP2.0并使用新的二进制分帧协议返回响应。无论哪种情况,都不需要额外往返。

最后,如果客户端因为自己保存有或通过其他手段(如dns记录,手工配置)获得了HTTP2.0的支持信息,也可以直接发送HTTP2.0分帧,而不必依赖Upgrade机制。

8.HTTP2.0数据传输示例

发起新流

在发送应用数据之前,必须创建一个新流并随之发送相应的元数据,比如优先级、HTTP首部等。HTTP2.0协议规定客户端和服务器都可以发起流,因此有两种可能:

  • 客户端通过发送HEADERS帧来发起流,这个帧里包含带有新流ID的公用首部,可以选的31位优先值,以及一组HTTP键值对首部;
  • 服务器端通过发送PUSH_PROMISE帧来发起推送流,这个帧与HEADERS等效,但它包含要约流ID,没有优先值。
    HEADERS帧示例如下:

    这两种帧的类型字段都只用于沟通新流的元数据,净荷会在DATA帧中单独发送。

    发送应用数据

    创建新流并发送HTTP首部之后,接下来就是利用DATA帧发送应用数据。应用数据可以分为多个DATA帧,最后一帧要翻转帧首部的END_STREAM字段。数据净荷不会被另行编码或压缩。编码方式取决于应用或服务器,纯文本,gzip压缩、图片或适配压缩格式都可以。DATA帧示例如下:

参考资料:

posted @ 2016-12-03 15:45  默语  阅读(3589)  评论(0编辑  收藏  举报