当前位置 博文首页 > huansky:深入浅出:HTTP/2

    huansky:深入浅出:HTTP/2

    作者:huansky 时间:2021-02-19 00:33

    概述

    HTTP/2 的目的就是通过支持请求与响应的多路复用来减少延迟,通过压缩HTTP首部字段将协议开销降至最低,同时增加对请求优先级和服务器端推送的支持。为达成这些目标,HTTP/2 还会给我们带来大量其他协议层面的辅助实现,比如新的流量控制、错误处理和更新机制。上述几种机制虽然不是全部,但却是最重要的,所有Web开发者都应该理解并在自己的应用中利用它们。

    HTTP/2 不会改动HTTP的语义。HTTP方法、状态码、URI及首部字段,等等这些核心概念一如往常。但是,HTTP/2 修改了格式化数据(分帧)的方式,以及客户端与服务器间传输这些数据的方式。这两点统帅全局,通过新的组帧机制向我们的应用隐藏了所有复杂性。换句话说,所有原来的应用都可以不必修改而在新协议运行。这当然是好事。

    下面我们就来详细介绍一下这些新的机制。

    历史及其与SPDY的渊源

    SPDY是谷歌开发的一个实验性协议,于2009年年中发布,其主要目标是通过解决HTTP 1.1中广为人知的一些性能限制,来减少网页的加载延迟。大致上,这个项目设定的目标如下:

    • 页面加载时间(PLT,Page Load Time)降低50%;

    • 无需网站作者修改任何内容;

    • 把部署复杂性降至最低,无需变更网络基础设施;

    • 与开源社区合作开发这个新协议;

    • 收集真实性能数据,验证这个实验性协议是否有效。

    2012年,这个新的实验性协议得到了Chrome、Firefox和Opera的支持,很多大型网站(如谷歌、Twitter、Facebook)都对兼容客户端提供SPDY会话。换句话说,SPDY在被行业采用并证明能够大幅提升性能之后,已经具备了成为一个标准的条件。最终,HTTP-WG(HTTP Working Group)在2012年初把HTTP/2 提到了议事日程,吸取SPDY的经验教训,并在此基础上制定官方标准。

    走向HTTP/2

    从那时起,SPDY 已经经过了很多变化和改进,而且在 HTTP/2 官方标准公布之前,还将有很多变化和改进。在此,有必要回顾一下HTTP/2 宣言草稿,因为这份宣言明确了该协议的范围和关键设计要求:

    HTTP/2 应该满足如下条件:

    • 相对于使用TCP的HTTP 1.1,用户在大多数情况下的感知延迟要有实质上、可度量的改进;

    • 解决HTTP中的“队首阻塞”问题;

    • 并行操作无需与服务器建立多个连接,从而改进TCP的利用率,特别是拥塞控制方面;

    • 保持HTTP 1.1的语义,利用现有文档,包括(但不限于)HTTP方法、状态码、URI,以及首部字段;

    • 明确规定HTTP/2 如何与HTTP 1.x互操作,特别是在中间介质上;

    • 明确指出所有新的可扩展机制以及适当的扩展策略;

    HTTP/2 特征

    二进制分帧层

    HTTP/2 性能增强的核心,全在于新增的二进制分帧层(如下图所示),它定义了如何封装HTTP消息并在客户端与服务器之间传输。

    这里所谓的“层”,指的是位于套接字接口与应用可见的高层HTTP API之间的一个新机制:HTTP的语义,包括各种动词、方法、首部,都不受影响,不同的是传输期间对它们的编码方式变了。HTTP 1.x以换行符作为纯文本的分隔符,而HTTP/2 将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。

    这样一来,客户端和服务器为了相互理解,必须都使用新的二进制编码机制:HTTP 1.x客户端无法理解只支持HTTP/2 的服务器,反之亦然。不过不要紧,现有的应用不必担心这些变化,因为客户端和服务器会替它们完成必要的分帧工作。

    首部压缩

    HTTP的每一次通信都会携带一组首部,用于描述传输的资源及其属性。在HTTP 1.x中,这些元数据都是以纯文本形式发送的,通常会给每个请求增加500~800字节的负荷。如果算上HTTP cookie,增加的负荷通常会达到上千字节。为减少这些开销并提升性能,HTTP/2会用 “HPACK” 算法来压缩头部数据。

    “HPACK”算法是专门为压缩 HTTP 头部定制的算法,与 gzip、zlib 等压缩算法不同,它是一个“有状态”的算法,需要客户端和服务器各自维护一份“索引表”,也可以说是“字典”(这有点类似 brotli),压缩和解压缩就是查表和更新表的操作。

    • HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;

    • 首部表在HTTP/2 的连接存续期内始终存在,由客户端和服务器共同渐进地更新;

    • 每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值。

    于是,HTTP/2 连接的两端都知道已经发送了哪些首部,这些首部的值是什么,从而可以针对之前的数据只编码发送差异数据,具体如下图所示。

    请求与响应首部的定义在HTTP/2 中基本没有改变,只是所有首部键必须全部小写,而且请求行要独立为:method 、:scheme 、:host 和:path 这些键-值对。

    在前面的例子中,第二个请求只需要发送变化了的路径首部(:path ),其他首部没有变化,不用再发送了。这样就可以避免传输冗余的首部,从而显著减少每个请求的开销。

    通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次。事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部!

    二进制帧

    头部数据压缩之后,HTTP/2 就要把报文拆成二进制的帧准备发送。

    HTTP/2 的帧结构有点类似 TCP 的段或者 TLS 里的记录,但报头很小,只有 9 字节,非常地节省(可以对比一下 TCP 头,它最少是 20 个字节)。

    二进制的格式也保证了不会有歧义,而且使用位运算能够非常简单高效地解析。

    下一篇:没有了