#http2 #http-header #response-headers #http #request-headers #tls #http-request

tokio-http2

HTTP/1.1 库(即将推出 HTTP/2)使用托里奥项目(核心、协议、服务)。与 https://github.com/lambdastackio/httpd 一起使用。

7 个版本

使用旧的 Rust 2015

0.1.9 2017 年 1 月 18 日
0.1.7 2017 年 1 月 16 日
0.1.2 2016 年 12 月 4 日

#13 in #request-headers

38 每月下载次数
用于 2 crates

Apache-2.0

1MB
4.5K SLoC

包含 (WOFF 字体,120KB) docs/Heuristica-Italic.woff,(WOFF 字体,90KB) docs/FiraSans-Medium.woff,(WOFF 字体,92KB) docs/FiraSans-Regular.woff,(WOFF 字体,56KB) docs/SourceCodePro-Regular.woff,(WOFF 字体,56KB) docs/SourceCodePro-Semibold.woff,(WOFF 字体,49KB) docs/SourceSerifPro-Bold.woff 和 1 个更多

HTTP/2 和 HPACK

Travis AppVeyor Badge Downloads License

状态

  • HPACK - 第一阶段工作正常
  • HTTP/2 - 进行中

注意:托里奥项目中的几个项目目前不是 crates,这意味着 Cargo.toml 中的依赖反映了直接的 git 拉取,因此该项目无法作为更新后的 crate 添加。这应该很快得到解决,或者我将创建每个 tokio 项目的镜像 crate,直到官方提供的 tokio 项目作为 crate 发布。

内容

  1. 内容

托里奥-HTTP2 设计目标

  • 用户未来、托里奥项目库,tokio-proto 被大量使用
  • 基于多路复用概念构建(HTTP/2 需要)
  • 允许构建代理、服务器和客户端
  • 库应该有足够的默认设置以形成有见地的设计,但默认设置应该能够被覆盖
  • 支持调用外部应用程序并接受输出以打包并返回给请求者的能力
  • 某些功能可能被拆分到另一个项目中以获得更大的灵活性

目录

托里奥-HTTP2 目标

  • 能够建立健壮的 HTTP/2 Web 服务器,允许自定义 Rust 端点
  • 服务器可用于企业环境,作为交付机制来管理大型复杂的基础设施,例如多拍字节对象存储等
  • 能够在企业中创建能够在所有主要平台上运行的高级智能代理的能力
  • 能够使用其他LambdaStack库和产品,如 s3lsioaws-sdk-rust,与公共云如AWS接口,这些库和产品已经可以将Ceph和AWS S3连接起来
  • 能够长期使用,以构建一个可靠的企业级CICD Pipeline服务
  • 一个可能的长期选项是采用VMWare中使用的mClock算法等QoS特性

注意:上述目标可以根据需求进行更改

目录

* HTTP/1.1 与 HTTP/2 的比较

HTTP/2 保留了与 HTTP/1.1 相同的语义。这包括 HTTP 方法(如 GET 和 POST)、状态代码(如 404(页面未找到))、URL 以及如何定义和使用头部字段。

在保留这些特性的同时,HTTP/2 添加了五个关键特性

  • 单次、持久连接 - 每个网页只使用一个连接,如图所示。只要网页打开,就使用相同的连接。
  • 多路复用 - 请求和回复被优先排序并多路复用到单个连接内的单独流中。当连接稳定时,消除了“头阻塞”(让每个传输等待所有之前的传输完成)。
  • 头部压缩和二进制编码 - 使用新的、独立的、安全的HPACK压缩标准压缩头部,减少了网络传输的数据量。头部信息以紧凑的二进制格式发送,而不是纯文本。
  • 优先级 - 请求被分配依赖级别,同一级别的请求被优先排序。服务器使用这些信息来排序和分配资源以满足请求。
  • SSL加密 - HTTP/2 允许你添加SSL支持,在某些情况下,没有性能损失,使你的网站更安全。

HTTP/2如何克服HTTP/1.x中SSL带来的性能开销?这里有四个关键技术

  1. 只有一个连接可以最小化SSL握手。
  2. 头部被压缩,减少了发送它们所需的时间。
  3. 多路复用意味着文件传输不需要等待其他请求。请参阅下文的多路复用部分以获取详细信息。
  4. 文件不需要内联、连接或精灵化,因此缓存可以最优地工作。

与非SSL实现相比,仍存在SSL性能开销,用于验证单个连接以及加密和解密数据,但这种剩余开销应该或多或少地被HTTP/2的性能改进所抵消。

* 来自NGINX白皮书

目录


HTTP/2 和 HPACK

通过允许我们在传输层本身解决HTTP/1.1之前在应用程序内执行的许多HTTP/1.1的解决方案,HTTP/2将使我们的应用程序更快、更简单、更稳健——这是一个罕见的组合!它还为我们提供了许多完全新的优化应用程序和改进性能的机会!

HTTP/2的主要目标是通过启用完全的请求和响应多路复用来减少延迟,通过高效压缩HTTP头部字段来最小化协议开销,并添加对请求优先级和服务器推送的支持。为了实现这些要求,有一大批其他协议增强功能,如新的流量控制、错误处理和升级机制,但这些是每个网络开发人员都应该理解和利用其应用程序的最重要特性。

HTTP/2以任何方式都不会修改HTTP的应用语义。所有核心概念,如HTTP方法、状态代码、URI和头部字段,都保持不变。相反,HTTP/2修改了客户端和服务器之间数据格式(框架)和传输的方式,双方都管理整个过程,并将所有复杂性隐藏在新的框架层中。因此,所有现有应用程序都可以不修改即可交付。这就是好消息。

然而,我们并不仅仅满足于交付一个能正常工作的应用;我们的目标是提供最佳性能!HTTP/2 为我们的应用提供了一系列新的优化,这些优化在之前是不可能实现的,我们的任务就是充分利用它们。让我们深入了解一下这些优化的细节。

目录

为什么不使用 HTTP/1.2?

为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制帧层,该层与之前的 HTTP/1.x 服务器和客户端不兼容——这就是为什么协议版本号从 HTTP/1.x 增加到 HTTP/2 的主要原因。

话虽如此,除非你正在通过操作原始 TCP 套接字来实现一个 Web 服务器(或自定义客户端),否则你不会看到任何区别:所有新的低级别帧操作都是由客户端和服务器代表你完成的。唯一可观察到的区别将是性能的提升和新的功能,如请求优先级、流控制和服务器推送!

目录

SPDY 和 HTTP/2 的简要历史

SPDY 是一个实验性协议,由 Google 开发并于 2009 年中宣布,其主要目标是尝试通过解决 HTTP/1.1 已知的性能限制来减少网页的加载延迟。具体的项目目标如下

  • 目标是将页面加载时间(PLT)减少 50%。避免需要网站作者对内容进行任何修改。

  • 最小化部署复杂性,避免网络基础设施的改变。

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

  • 收集真实性能数据来(不)验证实验性协议。

注意

为了实现 50% 的 PLT 提升,SPDY 旨在通过引入一个新的二进制帧层来更有效地利用底层 TCP 连接,从而实现请求和响应的复用、优先级排序和头部压缩;请参阅“延迟作为性能瓶颈”。

在最初宣布不久后,Google 的软件工程师 Mike Belshe 和 Roberto Peon 分享了他们对新的 SPDY 协议实验性实现的第一次结果、文档和源代码

"到目前为止,我们只在实验室条件下测试了 SPDY。初步结果非常令人鼓舞:当我们通过模拟的家庭网络连接下载前 25 个网站时,我们看到了性能的显著提升——页面加载速度提高了高达 55%。" ——《两倍快的网络》,Chromium 博客

快进到 2012 年,新的实验性协议得到了 Chrome、Firefox 和 Opera 的支持,越来越多的网站(无论是大型的如 Google、Twitter、Facebook 还是小型的)都在其基础设施中部署了 SPDY。实际上,SPDY 正在通过行业采用的增长成为事实标准。

观察到上述趋势后,HTTP 工作组(HTTP-WG)开始了一个新的工作,目的是从 SPDY 中吸取经验教训,在此基础上进行改进,并交付官方的“HTTP/2”标准:起草了一份新的章程,公开征集 HTTP/2 建议,并在工作组内进行了大量讨论后,将 SPDY 规范作为新 HTTP/2 协议的起点。

在接下来的几年里,SPDY 和 HTTP/2 会继续并行发展,SPDY 作为实验性分支被用来测试 HTTP/2 标准的新功能和建议:纸面上看起来好的可能在实际中行不通,反之亦然,SPDY 为测试和评估每个建议提供了一个路线,以便在将其纳入 HTTP/2 标准时使用。最终,这个过程历时三年,产生了十几个中间草案

  • 2012 年 3 月:对 HTTP/2 的建议征集
  • 2012 年 11 月:HTTP/2 的第一个草案(基于 SPDY)
  • 2014 年 8 月:发布 HTTP/2 草案-17 和 HPACK 草案-12
  • 2014 年 8 月:工作组对 HTTP/2 的最后呼吁
  • 2015 年 2 月:IESG 批准 HTTP/2 和 HPACK 草案
  • 2015 年 5 月:发布 RFC 7540(HTTP/2)和 RFC 7541(HPACK)

2015年初,IESG审查并批准了新的HTTP/2标准以供发布。随后,谷歌Chrome团队宣布了他们的计划,将废弃SPDY和NPN扩展,以支持TLS

"HTTP/2相较于HTTP/1.1的主要改进集中在性能提升上。一些关键特性,如多路复用、头部压缩、优先级排序和协议协商,都是源于一个早期开放的但非标准的协议SPDY。Chrome从Chrome 6开始支持SPDY,但由于HTTP/2中已经包含了大部分好处,是时候说再见了。我们计划在2016年初移除对SPDY的支持,并同时在Chrome中移除对NPN扩展的支持,转而使用ALPN。强烈建议服务器开发者迁移到HTTP/2和ALPN。

我们很高兴为导致HTTP/2开放标准的进程做出了贡献,并希望看到广泛的采用,因为标准化和实施得到了整个行业的参与。" -- Hello HTTP/2, Goodbye SPDY, Chromium博客

SPDY和HTTP/2的共同发展使服务器、浏览器和网站开发者能够在协议开发过程中获得实际经验。因此,HTTP/2标准是起点最好且测试最全面的几个标准之一。到IESG批准HTTP/2时,已有数十个经过彻底测试和生产的客户端和服务器实现。事实上,在最终协议批准后的几周内,许多用户已经享受到了它的好处,因为几个流行的浏览器(和许多网站)已经部署了完整的HTTP/2支持。

目录

HTTP/2的设计和技术目标

HTTP协议的最初版本是为了实现实现的简单性:HTTP/0.9是一个单行协议,用于启动万维网;HTTP/1.0在信息标准中记录了HTTP/0.9的流行扩展;HTTP/1.1引入了官方的IETF标准;参见HTTP简史。因此,HTTP/0.9-1.x正好做到了它想要做到的事情:HTTP是互联网上最普遍和广泛采用的应用协议之一。

不幸的是,实现简单性也付出了应用性能的代价:HTTP/1.x客户端需要使用多个连接来实现并发性和减少延迟;HTTP/1.x不压缩请求和响应头部,导致不必要的网络流量;HTTP/1.x不允许有效的资源优先级排序,导致对底层TCP连接的利用率低下;等等。

这些限制并非致命,但随着网络应用程序在范围、复杂性和在日常生活中的重要性不断增长,它们对网络开发者和用户都施加了越来越大的负担,这正是HTTP/2设计要解决的具体问题。

"HTTP/2通过引入头部字段压缩和允许在同一连接上执行多个并发交换,提高了网络资源的使用效率并降低了延迟感……具体来说,它允许在同一连接上交错请求和响应消息,并使用高效的编码来处理HTTP头部字段。它还允许请求优先级排序,使更重要的请求更快完成,从而进一步提高性能。

由此产生的协议对网络更友好,因为与HTTP/1.x相比,可以使用的TCP连接更少。这意味着与其他流量的竞争更少,连接寿命更长,这反过来又导致了对可用网络容量的更好利用。最后,HTTP/2还通过使用二进制消息帧来提高消息处理的效率。" -- 超文本传输协议版本2,草案17

重要的是要注意,HTTP/2是在扩展而不是取代之前的HTTP标准。HTTP的应用语义是相同的,对提供的功能或核心概念(如HTTP方法、状态码、URI和头部字段)没有做出任何改变——这些改变明确超出了HTTP/2的努力范围。话虽如此,尽管高层API保持不变,但了解底层变化如何解决之前协议的性能限制是很重要的。让我们简要地了解一下二进制帧层及其特性。

目录

二进制帧层

HTTP/2所有性能提升的核心是新的二进制帧层(图12-1),它规定了客户端和服务器之间如何封装和传输HTTP消息。

帧 图12-1. HTTP/2二进制帧层

“层”指的是在设计上引入一种新的优化编码机制,在套接字接口和向我们的应用程序公开的更高HTTP API之间:HTTP语义,如动词、方法和头部,不受影响,但它们在传输过程中的编码方式是不同的。与以换行符分隔的明文HTTP/1.x协议不同,所有HTTP/2通信都被分成更小的消息和帧,每个都使用二进制格式编码。

因此,客户端和服务器都必须使用新的二进制编码机制来相互理解:HTTP/1.x客户端不会理解只支持HTTP/2的服务器,反之亦然。幸运的是,我们的应用程序对所有的这些变化都一无所知,因为客户端和服务器代表我们完成了所有的必要帧工作。

二进制协议的优缺点

ASCII协议易于检查和入门。然而,它们不太高效,通常更难正确实现:可选空白、不同的终止序列和其他怪癖使得区分协议和有效负载变得困难,从而导致解析和安全错误。相比之下,虽然二进制协议可能需要更多的努力来入门,但它们往往导致更高效、更健壮和可证明正确的实现。

HTTP/2使用二进制帧。因此,您需要一款了解它的工具来检查和调试协议——例如,Wireshark或类似工具。实际上,这比看起来要少得多的问题,因为您需要使用相同的工具来检查携带HTTP/1.x和HTTP/2数据的加密TLS流——这些流也依赖于二进制帧(参见TLS记录协议)。

目录

流、消息和帧

新的二进制帧机制的出现改变了客户端和服务器之间数据交换的方式(图12-2)。为了描述这个过程,让我们熟悉一下HTTP/2的术语

`Stream`
A bidirectional flow of bytes within an established connection, which may carry one or more messages.

`Message`
A complete sequence of frames that map to a logical request or response message.

`Frame`
The smallest unit of communication in HTTP/2, each containing a frame header, which at a minimum identifies the stream to which the frame belongs.
  • 所有通信都是在单个TCP连接上完成的,该连接可以承载任意数量的双向流。
  • 每个流都有一个唯一的标识符和可选的优先级信息,用于携带双向消息。
  • 每个消息都是一个逻辑HTTP消息,如请求或响应,它由一个或多个帧组成。
  • 帧是携带特定类型数据的通信的最小单位——例如,HTTP头部、消息有效负载等。来自不同流的帧可以交错,然后通过每个帧头中的嵌入式流标识符重新组装。

HTTP/2帧 图12-2. HTTP/2流、消息和帧

简而言之,HTTP/2将HTTP协议通信分解为二进制编码帧的交换,然后映射到属于特定流的消息,所有这些都包含在单个TCP连接中。这是HTTP/2协议提供所有其他功能和性能优化的基础。

目录

请求和响应多路复用

在HTTP/1.x中,如果客户端想要发送多个并行请求来提高性能,那么必须使用多个TCP连接;请参阅使用多个TCP连接。这种行为是HTTP/1.x交付模型的直接后果,该模型确保每次连接只能发送一个响应(响应排队)。更糟糕的是,这也导致首部阻塞和底层TCP连接的低效使用。

HTTP/2中的新二进制帧层消除了这些限制,并允许通过允许客户端和服务器将HTTP消息分解为独立的帧(图12-3)、交错它们,然后在另一端重新组装它们,来实现完整的请求和响应多路复用。

HTTP/2 Connection

图12-3. 在共享连接内HTTP/2请求和响应的多路复用

图12-3的快照捕捉了同一连接内多个正在运行的流:客户端正在向服务器传输一个DATA帧(流5),而服务器正在向客户端传输流1和3的交错帧序列。因此,有三个并行流正在运行!

将HTTP消息分解成独立的帧、交错它们,然后在另一端重新组装的能力是HTTP/2最重要的改进。事实上,它为整个网络技术堆栈带来了许多性能收益,使我们能够

  • 并行交错多个请求,而不会阻塞任何一个
  • 并行交错多个响应,而不会阻塞任何一个
  • 使用单个连接并行传输多个请求和响应
  • 移除不必要的HTTP/1.x解决方案(参见针对HTTP/1.x的优化),例如连接文件、图像精灵和域名分片
  • 通过消除不必要的延迟和提高可用网络容量利用率来降低页面加载时间
  • 等等...

HTTP/2中的新二进制帧层解决了HTTP/1.x中的头阻塞问题,消除了启用并行处理和请求/响应传输所需多个连接的需要。因此,这使得我们的应用程序更快、更简单、部署成本更低。

目录

流优先级

一旦HTTP消息可以拆分成多个单独的帧,并且我们允许来自多个流的帧进行多路复用,客户端和服务器交错和发送帧的顺序就变成了一个关键的性能考虑因素。为了便于实现这一点,HTTP/2标准允许每个流都有一个相关的权重和依赖性

  • 每个流可以分配一个介于1和256之间的整数权重
  • 每个流可以明确地依赖于另一个流

流依赖性和权重的组合允许客户端构建和传达一个“优先级树”(图12-4),表示它希望如何接收响应。反过来,服务器可以使用这些信息通过控制CPU、内存和其他资源的分配来优先处理流,一旦响应数据可用,分配带宽以确保将高优先级的响应最优地传送给客户端。

Stream weight 图12-4. HTTP/2流依赖性和权重

HTTP/2中的流依赖性通过引用另一个流的唯一标识符作为其父流来声明;如果省略,则流被认为是依赖于“根流”。声明流依赖性表示,如果可能的话,父流应该在依赖项之前分配资源——例如,请在传输响应C之前处理和传输响应D。

具有相同父流(即兄弟流)的流应该按其权重比例分配资源。例如,如果流A的权重为12,而它的一个兄弟流B的权重为4,那么要确定这些流应获得的资源比例

  1. 求所有权重的和:4 + 12 = 16
  2. 将每个流权重除以总权重:A = 12/16, B = 4/16

因此,流A应获得四分之三的资源,而流B应获得四分之一的资源;流B应获得分配给流A资源的三分之一。让我们通过图12-4中的几个更多实际示例来工作。从左到右

  1. 流A和流B都没有指定父依赖关系,因此被称为依赖于隐含的“根流”;A的权重为12,B的权重为4。因此,基于比例权重:流B应获得分配给流A的资源的三分之一。
  2. D依赖于根流;C依赖于D。因此,D应在C之前获得全部资源分配。权重无关紧要,因为C的依赖关系传达了更强的偏好。
  3. D应在C之前获得全部资源分配;C应在A和B之前获得全部资源分配;流B应获得分配给流A的资源的三分之一。
  4. D应在E和C之前获得全部资源分配;E和C应在A和B之前获得等量分配;A和B应根据它们的权重进行成比例分配。

如上述示例所示,流依赖关系和权重的组合为资源优先级提供了一个表达性语言,这对于改善浏览性能至关重要,因为我们有具有不同依赖关系和权重的许多资源类型。更好的是,HTTP/2协议还允许客户端在任何时候更新这些偏好,这使浏览器能够进行进一步的优化——例如,我们可以根据用户交互和其他信号更改依赖关系和重新分配权重。

注意

流依赖关系和权重表达了一种传输偏好,而不是要求,因此不能保证特定的处理或传输顺序。也就是说,客户端不能通过流优先级强制服务器按特定顺序处理流。虽然这似乎有些令人费解,但实际上是期望的行为:我们不希望当更高优先级的资源受阻时,阻止服务器在较低优先级的资源上取得进展。

浏览器请求优先级和HTTP/2

在浏览器中渲染页面时,并非所有资源都具有相同的优先级:HTML文档本身对于构建DOM至关重要;CSS对于构建CSSOM是必需的;DOM和CSSOM的构建都可以在JavaScript资源上阻塞(请参阅DOM、CSSOM和JavaScript);其余资源,如图像,通常以较低优先级获取。

为了加速页面加载时间,所有现代浏览器都会根据资产的类型、它们在页面上的位置,甚至从以前的访问中学到的优先级来优先处理请求——例如,如果在前一次访问中渲染被某个资产阻塞,则该资产在未来可能会被优先考虑。

在HTTP/1.x中,浏览器在利用上述优先级数据方面能力有限:该协议不支持多路复用,没有方法将请求优先级通知服务器。相反,它必须依赖并行连接的使用,这可以实现最多每个源六个请求的有限并行性。因此,请求会在客户端排队,直到有可用连接,这增加了不必要的网络延迟。理论上,HTTP管道试图部分解决这个问题,但在实践中它未能得到采用。

HTTP/2解决了这些低效性:请求排队和行首阻塞被消除,因为浏览器可以在发现请求的瞬间发出所有请求,并且浏览器可以通过流依赖关系和权重来传达其流优先级偏好,允许服务器进一步优化响应交付。

目录

每个来源一个连接

由于新二进制帧机制的实施,HTTP/2不再需要多个TCP连接来并行复用流;每个流被分成许多帧,这些帧可以交错和优先处理。因此,所有HTTP/2连接都是持久的,并且每个源只需要一个连接,这提供了许多性能优势。

对于SPDY和HTTP/2来说,其杀手级特性是在一个良好的拥塞控制通道上进行任意的多路复用。这让我惊讶其重要性以及其工作效果之好。围绕这个特性,我最喜欢的指标是创建的连接中仅携带单一HTTP事务的比例(因此该事务承担了所有开销)。对于HTTP/1,74%的活跃连接仅携带单一事务——持久连接并没有像我们希望的那样有帮助。但在HTTP/2中,这个数字骤降至25%。这是降低开销的巨大胜利。” —— HTTP/2已在Firefox中上线,帕特里克·麦克曼斯

大多数HTTP传输都是短暂且突发的,而TCP优化的是长期存在的批量数据传输。通过重用相同的连接,HTTP/2既能更有效地使用每个TCP连接,也能显著减少整体协议开销。此外,使用更少的连接可以减少整个连接路径(即客户端、中间件和源服务器)的内存和处理占用,从而降低整体运营成本并提高网络利用率和容量。因此,转向HTTP/2不仅应该减少网络延迟,还应该帮助提高吞吐量并降低运营成本。

注意

减少连接数量对于提高HTTPS部署的性能尤为重要:这相当于减少了昂贵的TLS握手、提高了会话重用,并整体减少了客户端和服务器所需资源。

等等,我听到你说,我们列举了使用单个TCP连接的好处,但是有没有潜在的不利因素?是的,有。

  • 我们已经从HTTP中消除了头部阻塞,但在TCP级别仍然存在头部阻塞(参见头部阻塞)。
  • 带宽延迟积的影响可能会限制连接吞吐量,如果禁用了TCP窗口缩放。
  • 当发生数据包丢失时,TCP拥塞窗口大小会减小(参见拥塞避免),这会降低整个连接的最大吞吐量。

列表中的每一项都可能对HTTP/2连接的吞吐量和延迟性能产生不利影响。然而,尽管存在这些限制,转向多个连接也会带来其自身的性能权衡。

  • 由于不同的压缩上下文,头部压缩效果较差。
  • 由于不同的TCP流,请求优先级设置效果较差。
  • 由于更多竞争流,每个TCP流的有效利用率较低,并且拥塞的可能性更高。
  • 由于更多的TCP流,资源开销增加。

上述优缺点并非详尽无遗,并且总是可以构造出特定的场景,其中一个或多个连接可能是有益的。然而,在野外部署HTTP/2的实验证据表明,单连接是首选的部署策略。

"到目前为止的测试中,头部阻塞的负面影响(尤其是在存在数据包丢失的情况下)被压缩和优先级的优势所抵消。” —— 超文本传输协议版本2,草案2

与所有性能优化过程一样,当你移除一个性能瓶颈时,你会解锁下一个瓶颈。在HTTP/2的情况下,TCP可能是它。这就是为什么,再次强调,服务器上经过良好调优的TCP堆栈对于HTTP/2来说是一个如此关键的优化标准。

正在进行的研究旨在解决这些担忧并提高TCP的整体性能:TCP快速打开、比例速率减少、增加初始拥塞窗口等。话虽如此,重要的是要承认,与前辈一样,HTTP/2并不强制使用TCP。随着我们展望未来,其他传输方式,如UDP,也不在可能性之外。

目录

流量控制

流控制是一种防止发送方用接收方可能不希望或无法处理的数据压倒接收方的机制:接收方可能正忙、负载过重,或者可能只为特定流分配固定数量的资源。例如,客户端可能请求了一个高优先级的大视频流,但用户已暂停视频,客户端现在希望暂停或限制从服务器到客户端的传输,以避免获取和缓冲不必要的资料。或者,代理服务器可能具有快速的下行链路和缓慢的上行链路连接,并希望调节下行链路向上游传输数据的速度,以控制其资源使用;等等。

上述要求让你想起了TCP流控制吗?应该会的,因为问题实际上是相同的——参见流控制。然而,由于HTTP/2流是在单个TCP连接中复用的,TCP流控制既不够精细,也不提供必要的应用级API来调节单个流的传输。为了解决这个问题,HTTP/2提供了一套简单的构建块,允许客户端和服务器实现它们自己的流和连接级流控制。

  • 流控制是方向性的。每个接收方都可以为每个流和整个连接选择任何它希望的窗口大小。
  • 流控制是基于信用的。每个接收方会宣传其初始连接和流流控制窗口(以字节为单位),每当发送方发出一个DATA帧时,这个窗口都会减少,通过接收方发送的WINDOW_UPDATE帧来增加。
  • 流控制不能被禁用。当HTTP/2连接建立时,客户端和服务器交换SETTINGS帧,设置双向的流控制窗口大小。流控制窗口的默认值设置为65,535字节,但接收方可以设置一个很大的最大窗口大小(字节)并通过发送WINDOW_UPDATE帧来维持它,每次收到数据时都会发送。
  • 流控制是逐跳的,而不是端到端的。也就是说,中间件可以使用它来控制资源使用并根据自己的标准和启发式方法实现资源分配机制。

HTTP/2没有指定实现流控制的任何特定算法。相反,它提供了简单的构建块,并将实现推迟到客户端和服务器,它们可以使用它来实施自定义策略以调节资源使用和分配,并实现可能有助于提高我们网络应用的实际和感知性能的新传输功能。

例如,应用层流控制允许浏览器只获取特定资源的部分,通过将流流控制窗口减少到零来暂停获取,然后在以后恢复——例如,获取图片的预览或第一扫描,显示它,并允许其他高优先级获取进行,一旦更关键的资源加载完成,再继续获取。

目录

服务器推送

HTTP/2的另一个强大新特性是服务器能够为单个客户端请求发送多个响应。也就是说,除了对原始请求的响应外,服务器还可以向客户端推送额外的资源(图12-5),而无需客户端显式请求每个资源!

服务器推送 图12-5. 服务器为推送资源发起新的流(承诺)

注意

HTTP/2摆脱了严格的请求-响应语义,并启用了一对多和服务器触发的推送工作流程,这为浏览器内和浏览器外的新交互可能性打开了大门。这是一个使能特性,它将对我们的协议思考方式以及它被使用的地方和方式产生重要的长期影响。

为什么在浏览器中需要一个这样的机制呢?一个典型的Web应用程序由数十个资源组成,所有这些资源都是由客户端通过检查服务器提供的文档来发现的。因此,为什么不消除额外的延迟,让服务器提前推送相关的资源呢?服务器已经知道客户端需要哪些资源;这就是服务器推送。

事实上,如果您曾经通过数据URI内联CSS、JavaScript或其他资产(参见资源内联),那么您已经有了服务器推送的实际经验!通过手动将资源内联到文档中,我们实际上是将该资源推送到客户端,而无需等待客户端请求它。使用HTTP/2,我们可以实现相同的结果,但还有额外的性能优势。

  • 推送的资源可以被客户端缓存。
  • 推送的资源可以在不同的页面之间重用。
  • 推送的资源可以与其他资源一起复用。
  • 服务器可以对推送的资源进行优先级排序。
  • 客户端可以拒绝推送的资源。

每个推送的资源都是一个流,与内联资源不同,它允许客户端对其进行单独的复用、优先级排序和处理。唯一的限制是浏览器强制执行的同源策略:服务器必须是对提供内容的权威机构。

PUSH_PROMISE 101

所有服务器推送流都是通过PUSH_PROMISE帧启动的,这些帧表示服务器将推送描述的资源到客户端,并且需要在请求推送资源的响应数据之前发送。这种交付顺序至关重要:客户端需要知道服务器打算推送哪些资源,以避免对这些资源创建自己的和重复的请求。满足这一要求的最简单策略是,在父响应(即DATA帧)之前发送包含所承诺的资源HTTP头部的所有PUSH_PROMISE帧。

一旦客户端接收到PUSH_PROMISE帧,它就有选择拒绝流(通过RST_STREAM帧)的选项,如果它想要的话(例如,资源已经在缓存中),这是对HTTP/1.x的重要改进。相比之下,资源内联的使用是HTTP/1.x中流行的“优化”,相当于“强制推送”:客户端无法选择退出、取消或单独处理内联资源。

使用HTTP/2,客户端始终完全控制服务器推送的使用方式。客户端可以限制并发推送流的数量;调整流首次打开时的初始流量控制窗口以控制推送多少数据;完全禁用服务器推送。这些偏好通过HTTP/2连接开始时的SETTINGS帧进行通信,并且可以在任何时候更新。

目录

头部压缩 - HPACK

每次HTTP传输都携带一组描述传输资源及其属性的头部。在HTTP/1.x中,此元数据始终以纯文本形式发送,每传输增加500-800字节的开销,如果使用HTTP cookie,则有时更多;请参阅测量和控制协议开销。为了减少这种开销并提高性能,HTTP/2使用HPACK压缩格式压缩请求和响应头部元数据,该格式使用两种简单但强大的技术。

  1. 它允许通过静态Huffman代码对传输的头部字段进行编码,从而减少它们的单个传输大小。
  2. 它要求客户端和服务器维护和更新一个先前看到的头部字段的索引列表(即建立一个共享的压缩上下文),然后使用它作为参考来高效地编码先前传输的值。

Huffman编码允许在传输时压缩单个值,先前传输的值的索引列表允许我们通过传输索引值来编码重复的值(图12-6),这些索引值可以用来高效地查找和重建完整的头部键和值。

HPACK 图12-6. HPACK:HTTP/2的头部压缩

作为进一步的优化,HPACK压缩上下文由静态表和动态表组成:静态表在规范中定义,提供了所有连接可能使用的常见HTTP头部字段列表(例如,有效的头部名称);动态表最初为空,并根据特定连接中交换的值进行更新。因此,通过使用静态Huffman编码对之前未见过的值进行压缩,并替换每侧静态或动态表中已存在的值,从而减小每个请求的大小。

注意

HTTP/2中请求和响应头部字段的定义保持不变,仅有少数小的例外:所有头部字段名称均为小写,请求行现在分为独立的:method、:scheme、:authority和:path伪头部字段。

HPACK的安全性和性能

HTTP/2和SPDY的早期版本使用了带有自定义字典的zlib来压缩所有HTTP头部,这可以将传输的头部数据大小减少85%–88%,并显著提高页面加载时间的延迟。

"在低带宽的DSL链路中,其中上传链路仅为375 Kbps,特别是在请求头部压缩方面,对于某些网站(即发出大量资源请求的网站)来说,页面加载时间得到了显著改善。我们发现页面加载时间仅因为头部压缩就减少了45–1142毫秒。" ——SPDY白皮书,chromium.org

然而,在2012年夏季,针对TLS和SPDY压缩算法的“CRIME”安全攻击被公布,这可能导致会话劫持。因此,将zlib压缩算法替换为HPACK,HPACK是专门设计来:解决发现的安全问题,高效且易于正确实现,当然,还能很好地压缩HTTP头部元数据。

有关HPACK压缩算法的详细信息,请参阅https://tools.ietf.org/html/draft-ietf-httpbis-header-compression

目录

升级到HTTP/2

切换到HTTP/2不会一夜之间发生:数百万台服务器必须更新以使用新的二进制帧,数十亿台客户端也必须相应地更新他们的网络库、浏览器和其他应用程序。

好消息是,所有现代浏览器都承诺支持HTTP/2,并且大多数现代浏览器使用高效的背景更新机制,这已经使大量现有用户无需干预就支持了HTTP/2。话虽如此,一些用户将卡在旧版浏览器上,服务器和中间件也必须更新以支持HTTP/2,这是一个更长(且劳动力和资本密集型)的过程。

HTTP/1.x还将存在至少另一个十年,大多数服务器和客户端将必须同时支持HTTP/1.x和HTTP/2标准。因此,HTTP/2客户端和服务器必须能够在交换应用程序数据之前发现和协商将使用哪种协议。为此,HTTP/2协议定义了以下机制

  1. 通过TLS和ALPN的安全连接协商HTTP/2
  2. 在不了解之前升级明文连接到HTTP/2
  3. 在有了解的情况下启动明文HTTP/2连接

HTTP/2标准不要求使用TLS,但在实践中,它是在存在大量现有中间代理的情况下部署新协议最可靠的方式;请参阅《Web中的代理、中间代理、TLS和新协议》。因此,使用TLS和ALPN是部署和协商HTTP/2的推荐机制:客户端和服务器在TLS握手过程中协商所需的协议,而不增加任何额外的延迟或往返;请参阅TLS握手和应用层协议协商(ALPN)。此外,作为附加约束,尽管所有流行的浏览器都承诺支持TLS上的HTTP/2,但其中一些也表明他们只会在TLS上启用HTTP/2——例如,Firefox和Google Chrome。因此,TLS与ALPN协商是实现浏览器中HTTP/2的事实要求。

在常规的非加密通道上建立HTTP/2连接仍然可能,尽管可能不是通过流行的浏览器,并且有一些额外的复杂性。因为HTTP/1.x和HTTP/2都运行在同一个端口(80)上,在没有其他关于服务器对HTTP/2支持的信息的情况下,客户端必须使用HTTP升级机制来协商适当的协议。

GET /page HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c ((1.))
HTTP2-Settings: (SETTINGS payload) ((2.))

HTTP/1.1 200 OK ((3.))
Content-length: 243
Content-type: text/html

(...HTTP/1.1 response...)

     OR

HTTP/1.1 101 Switching Protocols ((4.))
Connection: Upgrade
Upgrade: h2c

(... HTTP/2 response ...)

     ((1.)) Initial HTTP/1.1 request with HTTP/2 upgrade header
     ((2.)) Base64 URL encoding of HTTP/2 SETTINGS payload
     ((3.)) Server declines upgrade, returns response via HTTP/1.1
     ((4.)) Server accepts HTTP/2 upgrade, switches to new framing

使用前面的升级流程,如果服务器不支持HTTP/2,则它可以立即以HTTP/1.1响应响应请求。或者,它可以通过返回HTTP/1.1格式的101切换协议响应来确认HTTP/2升级,然后立即切换到HTTP/2,并使用新的二进制封装协议返回响应。在两种情况下,都不会产生额外的往返。

最后,如果客户端选择的话,它也可能通过其他方式记住或获得有关HTTP/2支持的信息——例如,DNS记录、手动配置等——而不是依赖于升级工作流程。有了这些知识,它可以选择从开始就发送HTTP/2帧,通过未加密的通道,并寄希望于最好的结果。在最坏的情况下,连接将失败,客户端将回退到升级工作流程或切换到具有ALPN协商的TLS隧道。

注意:客户端和服务器之间、服务器之间以及所有其他排列的加密通信是安全最佳实践:所有传输中的数据都应该被加密、验证并检查篡改。简而言之,使用带有ALPN协商的TLS来部署HTTP/2。

目录

二进制帧简介

所有HTTP/2改进的核心是新的二进制、长度前缀的封装层。与换行符分隔的明文HTTP/1.x协议相比,二进制封装提供了更紧凑的表示,既易于处理,也易于正确实现。

一旦建立HTTP/2连接,客户端和服务器通过交换帧来进行通信,这些帧是协议中最小的通信单元。所有帧都共享一个共同的9字节头部(图12-7),其中包含帧的长度、类型、一个用于标志的位字段和一个31位的流标识符。

Buffer frame 图12-7. 9字节帧头部

  • 24位长度字段允许单个帧携带多达字节的数据。
  • 8位类型字段确定帧的格式和语义。
  • 8位标志字段用于传递帧类型特定的布尔标志。
  • 1位保留字段始终设置为0。
  • 31位流标识符唯一标识HTTP/2流。

注意:技术上,长度字段允许每个帧的负载多达字节(约16MB)。然而,HTTP/2标准将DATA帧的默认最大负载大小设置为字节(约16KB)每个帧,并允许客户端和服务器协商更高的值。更大的不一定是更好的:较小的帧大小可以启用有效的多路复用并最小化头阻塞。

鉴于我们了解共享的HTTP/2帧头信息,现在我们可以编写一个简单的解析器来检查任何HTTP/2字节流,并识别不同的帧类型,报告它们的标志,并检查每个帧的前九个字节以报告它们的长度。此外,由于每个帧都是长度前缀的,因此解析器可以快速且高效地跳转到下一个帧的开始——这比HTTP/1.x有显著的性能提升。

一旦确定帧类型,解析器就可以解释帧的其余部分。HTTP/2标准定义了以下类型

`DATA`
Used to transport HTTP message bodies

`HEADERS`
Used to communicate header fields for a stream

`PRIORITY`
Used to communicate sender-advised priority of a stream

`RST_STREAM`
Used to signal termination of a stream

`SETTINGS`
Used to communicate configuration parameters for the connection

`PUSH_PROMISE`
Used to signal a promise to serve the referenced resource

`PING`
Used to measure the roundtrip time and perform "liveness" checks

`GOAWAY`
Used to inform the peer to stop creating streams for current connection

`WINDOW_UPDATE`
Used to implement flow stream and connection flow control

`CONTINUATION`
Used to continue a sequence of header block fragments

注意:您需要一些工具来检查低级别的HTTP/2帧交换。您最喜欢的十六进制查看器当然是一个选择。或者,为了获得更人性化的表示,您可以使用像Wireshark这样的工具,它理解HTTP/2协议,并可以捕获、解码和分析交换。

好消息是,前面分类的帧的确切语义主要只与服务器和客户端实现者相关,他们需要关注流量控制、错误处理、连接终止等细节的语义。HTTP协议的应用层特性和语义保持不变:客户端和服务器处理帧、多路复用和其他细节,而应用层可以享受更快速和更高效交付的好处。

话虽如此,尽管帧层对应用程序来说是隐藏的,但对我们来说,更进一步看看两个最常见的流程是有用的:启动新的流和交换应用程序数据。对请求或响应如何转换为单个帧的直觉将为您提供调试和优化HTTP/2部署所需的知识。让我们更深入地探讨。

固定长度字段与可变长度字段和HTTP/2

HTTP/2只使用固定长度的字段。HTTP/2帧的开销低(数据帧的头部为9个字节),可变长度编码的节省并不能抵消解析器所需的复杂性,也不会对交换的带宽或延迟产生重大影响。

例如,如果可变长度编码可以将开销减少50%,对于1,400字节的网络数据包,这将仅为单个帧节省4个字节(0.3%)。

目录

启动新的流

在发送任何应用程序数据之前,必须创建一个新的流并发送适当的请求元数据:可选的流依赖性和权重,可选的标志,以及描述请求的HPACK编码的HTTP请求头。客户端通过发送包含所有上述内容的HEADERS帧(图12-8)来启动此过程。

Wireshark HTTP/2 1 图12-8. Wireshark中解码的HEADERS帧

注意:Wireshark以与在线编码相同的顺序解码和显示帧字段——例如,比较通用帧头中的字段与图12-7中的帧布局。

HEADERS帧用于声明和传达关于新请求的元数据。如果有的话,应用数据负载在DATA帧中独立传递。这种分离允许协议将“控制流量”的处理与应用数据的传递分开——例如,流量控制仅应用于DATA帧,而非DATA帧总是以高优先级处理。

PUSH_PROMISE启动的服务器端流

HTTP/2允许客户端和服务器端都可以启动新的流。在服务器端启动流的情况下,使用PUSH_PROMISE帧来声明承诺并传达HPACK编码的响应头。帧的格式与HEADERS类似,只不过省略了可选的流依赖性和权重,因为服务器完全控制承诺数据的传递。

为了消除客户端和服务器发起的流之间的ID冲突,计数器进行了偏移:客户端发起的流具有奇数ID,而服务器发起的流具有偶数ID。因此,由于图12-8中的流ID设置为"1",我们可以推断它是一个客户端发起的流。

目录

发送应用程序数据

一旦创建了一个新的流,并发送了HTTP头部后,如果存在应用程序负载,则使用DATA帧(图12-9)来发送。负载可以分布在多个DATA帧之间,最后一个帧通过在帧的头部切换END_STREAM标志来指示消息的结束。

Wireshark HTTP/2 2 图12-9. DATA帧

注意:图12-9中的"End Stream"标志设置为"false",表示客户端尚未完成传输应用程序负载;还有更多的DATA帧即将到来。

除了长度和标志字段外,关于DATA帧没有太多可说的。应用程序负载可能分布在多个DATA帧之间,以实现高效的多路复用,但除此之外,它将与应用程序提供的完全一致——即,编码机制(纯文本、gzip或其他编码格式)的选择推迟到应用程序。

目录

分析 HTTP/2 帧数据流

掌握了不同帧类型的知识,我们现在可以重新审视我们之前在请求和响应多路复用中遇到的图(图12-10),并分析HTTP/2交换。

图12-10. 在共享连接内的HTTP/2请求和响应多路复用

图12-10. 在共享连接内的HTTP/2请求和响应多路复用

  • 有三个流,ID设置为1、3和5。
  • 所有三个流ID都是奇数;所有三个都是客户端发起的流。
  • 在这个交换中没有服务器发起的("推送")流。
  • 服务器正在为流1发送交错的数据帧,这些数据帧携带了客户端先前请求的应用程序响应。
  • 服务器在流1的数据帧之间交错地将流3的HEADERS和DATA帧——这是响应多路复用的实际应用!
  • 客户端正在传输流5的DATA帧,这表明之前已传输了一个HEADERS帧。

上述分析当然基于对实际HTTP/2交换的简化表示,但它仍然说明了新协议的许多优点和特性。到这一点,你应该已经具备了成功记录和分析实际HTTP/2跟踪所必需的知识——尝试一下吧!

目录

依赖关系

~19-30MB
~455K SLoC