虽然HTTP/3规范还在起草阶段,但最新版本的Chrome浏览器已经默认支持它。Chrome占据了大约70%的浏览器市场份额,可以说HTTP/3已经成为主流。
这一基本协议的新修订旨在使网络更高效、更安全,并缩短内容交付延迟。在某些方面,这是对HTTP2的大胆尝试:通过用新的、专门构建的协议Quic替换底层TCP协议来实现类似的目标。解释Quic好处的最好方法是说明TCP作为HTTP请求传输的不足之处。要做到这一点,我们将从头开始。
1991年,当Tim Berners-Lee爵士正式设计了一个简单的单行超文本交换协议时,TCP已经是一个古老、可靠的协议。后来被称为HTTP 0.9的原始定义文档特别提到TCP是首选的(尽管不是唯一的)传输协议:
当然,这个概念验证版本的HTTP与我们今天所了解和喜爱的HTTP几乎没有相似之处。没有标头,也没有状态代码。典型的请求就像GET/PATH一样简单。响应只包含HTML,并以关闭TCP连接结束,因为浏览器还没有出现,用户应该直接阅读HTML。可以链接到其他资源,但是这个早期版本的HTML中没有一个标记异步请求额外的资源。单个HTTP请求提供了一个完整的、自给自足的页面。
在接下来的几年里,互联网呈爆炸式增长,HTTP发展成为一种可扩展和灵活的通用协议,尽管传输HTML仍然是它的主要专长。HTTP有三个关键更新实现了这一演变:
方法的引入允许客户端识别它想要执行的操作类型。例如,创建POST是为了允许客户端将数据发送到服务器进行处理和存储。
状态代码为客户端提供了一种方式,用于确认服务器已成功处理请求,如果未成功,则可了解发生了哪种错误。
Header添加了将结构化文本元数据附加到请求和响应的功能,这些请求和响应可能会修改客户端或服务器的行为。例如,编码和内容类型的报头不仅允许HTTP传输HTML,还允许传输任何类型的有效负载。“Compression”标头允许客户端和服务器协商支持的压缩格式,从而减少通过连接传输的数据量
同时,HTML高级支持图像、样式和其他链接资源。浏览器现在被迫执行多个请求才能显示单个网页,而最初的按请求连接架构不能处理这一点。建立和结束TCP连接涉及大量来回分组交换,因此延迟开销相对较高。当网页由单个文本文件组成时,这并不重要,但是随着每页请求数量的增加,延迟也随之增加。
下图说明了为每个请求建立新的TCP连接所涉及的开销。
创建了一个“Connection”标头来解决此问题。客户端发送带有“Connection:Keep-Alive”报头的请求,以表示有意保持TCP连接打开以供后续请求使用。如果服务器理解此标头并同意尊重它,则其响应也将包含“Connection:Keep-Alive”标头。这样,双方都保持TCP通道打开,并将其用于后续通信,直到任何一方决定关闭它。随着SSL/TLS加密的普及,这一点变得更加重要,因为协商加密算法和交换密钥需要在每个连接上进行额外的请求/响应周期。
当时,许多HTTP改进都是自发出现的。当流行的浏览器或服务器应用程序发现需要新的HTTP功能时,他们会简单地自己实现,并希望其他方也能效仿。具有讽刺意味的是,一个分散的网络需要一个集中的管理机构,以避免分裂成不兼容的部分。该协议的最初创建者Tim Berners-Lee认识到了这种危险,并在1994年成立了万维网联盟(W3C),该联盟与互联网工程任务组(IETF)一起致力于使互联网技术栈正规化。作为给现有环境带来更多结构的第一步,他们记录了当时在HTTP中使用的最常见的特性,并将产生的协议命名为HTTP/1.0。然而,因为这个“规范”描述了各种各样的、经常不一致的技术,就像“在野外”看到的那样,它从未获得过标准的地位。相反,新版本的HTTP协议的工作已经开始。
HTTP/1.1修复了HTTP/1.0的不一致性,并调整了协议,使其在新的Web生态系统中性能更佳。引入的两个最关键的更改是默认情况下使用持久TCP连接(Keep-Alive)和HTTP管道。
HTTP管道只是意味着客户端在发送后续HTTP请求之前不需要等待服务器响应请求。此功能可以更高效地使用带宽并减少延迟,但还可以进一步改进。HTTP流水线仍然要求服务器按照接收到的请求顺序进行响应,因此如果流水线中的单个请求完成速度较慢,则对客户端的所有后续响应都将相应延迟。此问题称为队头阻塞。
在这个时间点上,网络获得了越来越多的交互功能。Web2.0即将到来,一些网页包含数十甚至数百个外部资源。为了避免队头阻塞并降低页面加载速度,客户端需要在每个主机上建立多个TCP连接。当然,连接开销从未发挥作用。实际上,情况变得更糟了,因为越来越多的应用程序使用SSL/TLS加密HTTP流量。因此,大多数浏览器都设置了最大可能同时连接的限制,试图达到微妙的平衡。
许多较大的网络服务已经认识到,现有的限制对于它们异常繁重的交互式网络应用程序来说太有限了,所以他们通过通过多个域名分发应用程序来“玩弄系统”。不知何故,这一切都奏效了,但解决方案远非完美。
尽管有一些缺点,但HTTP/1.0和HTTP/1.1的简单性使它们获得了广泛的成功,十多年来没有人认真尝试改变它们。
2008年,谷歌发布了Chrome浏览器,这款浏览器因其快速和创新而迅速流行起来。它让谷歌在互联网技术问题上投下了强有力的一票。在2010年代初,谷歌在Chrome上增加了对其网络协议SPDY的支持。
HTTP/2标准是在SPDY的基础上做了一些改进。HTTP/2通过在单个开放的TCP连接上多路复用HTTP请求,解决了队头阻塞问题。这允许服务器以任何顺序响应请求,然后客户端可以在接收到响应时重新组装响应,从而在单个连接中使整个交换速度更快。
事实上,使用HTTP/2,服务器甚至可以在客户端请求资源之前就将资源提供给客户端!举个例子,如果服务器知道客户端很可能需要样式表来显示HTML页面,它可以将CSS“推送”到客户端,而无需等待相应的请求。虽然在理论上是有益的,但这一特性在实践中很少见,因为它需要服务器理解它所服务的HTML的结构,而这种情况很少发生。
除了请求正文之外,HTTP/2还允许压缩请求头,这进一步减少了通过线路传输的数据量。
HTTP/2为Web解决了很多问题,但不是全部。TCP协议层仍然存在类似类型的队头问题,它仍然是Web的基础构建块。当TCP数据包在传输过程中丢失时,在服务器重新发送丢失的数据包之前,接收方无法确认传入的数据包。由于TCP在设计上不受更高级别的协议(如HTTP)的影响,因此单个丢失的数据包将阻塞所有正在传输的HTTP请求的流,直到丢失的数据被重新发送。这个问题在不可靠的连接上尤为突出,在移动设备无处不在的时代,这并不罕见。
由于HTTP/2的问题不能完全在应用层解决,因此协议的新迭代必须更新传输层。但是,创建新的传输层协议并非易事。传输协议需要得到硬件供应商的支持,并由大多数网络运营商部署,但由于涉及的成本和努力,这些运营商不愿更新。以IPv6为例:它是24年前推出的,还远远没有得到普遍支持。
幸运的是,还有另一个选择。UDP协议与TCP一样受到广泛支持,但它足够简单,可以作为在其上运行的自定义协议的构建块。UDP数据包是即发即忘:没有握手、持久连接或纠错。HTTP3背后的主要思想是放弃TCP,转而采用基于UDP的Quic协议。Quic以一种对Web环境有意义的方式添加了必要的功能(那些以前由TCP提供的功能,以及更多)。
与HTTP2不同,HTTP2在技术上允许未加密的通信,而Quic严格要求加密才能建立连接。此外,加密应用于流经该连接的所有数据,而不仅仅是HTTP有效负载,从而避免了整个类别的安全问题。在Quic中,建立持久连接、协商加密协议,甚至发送第一批数据都合并到单个请求/响应周期中,从而显著降低了连接延迟。如果客户端具有本地缓存的加密参数,则可以使用简化的握手(0-RTT)重新建立到已知主机的连接。
为了解决传输级的队头阻塞问题,通过Quic连接传输的数据被分成多个流。流是持久的Quic连接中短暂的、独立的“子连接”。每个流处理其自己的纠错和传送保证,但使用连接全局压缩和加密属性。每个客户端发起的HTTP请求都在单独的流上操作,因此丢失一个数据包不会影响其他流/请求的数据传输。
UDP是一种无状态协议(持久连接只是其上的抽象),使Quic能够支持在很大程度上忽略了数据包传递复杂性的功能。例如,客户端在连接过程中更改其IP地址(就像智能手机从移动网络跳到家庭WiFi一样)理论上不应该中断连接,因为该协议允许在不同IP地址之间迁移,而无需重新连接。
目前,Quic协议的所有现有实现都运行在用户空间中,而不是操作系统内核中。由于客户端(例如浏览器)和服务器的更新通常都比操作系统内核更频繁,因此这有望更快地采用新功能。
虽然HTTP/3标准,在我看来,是迈向更快更安全的互联网的一大步,但它并不完美。它的一些问题是由其新颖性引起的,而另一些问题似乎是该议定书固有的。
TCP协议由来已久,路由器很容易理解。它具有清晰的未加密标记,用于建立和关闭连接,可用于跟踪和控制现有会话。在网络硬件学会理解新协议之前,它将把Quic流量简单地视为独立的UDP数据包流,这将使网络配置变得复杂得多。
从客户端缓存“恢复”连接的能力使协议容易受到重播攻击:在某些情况下,恶意攻击者可以重新发送以前捕获的数据包,这些数据包将被服务器解释为有效的,并且来自受害者。许多Web服务器,就像那些提供静态内容的服务器一样,不会受到这样的攻击的损害。攻击场景有效的应用程序必须记住禁用0-RTT功能。
到目前为止,这就是HTTP的故事。我认为HTTP/3是向前迈出的一大步,当然希望HTTP/3能在不久的将来得到广泛采用。