HTTP/2的前世今生

300 阅读12分钟

阅读了一篇HTTP相关的文章,内容写得很详细、全面,便对文章做了总结及翻译,以作记录。原文链接:cs.fyi/guide/http-…

总结

timeline
      HTTP/0.9 - 1991 : 没有HTTP头
                      : GET请求方法
                      : 响应的内容是HTML
      HTTP/1.0 - 1996 : 可处理更多的响应格式
                      : 增加POST、HEAD方法
                      : 请求和响应中添加HTTP头
                      : 添加了状态码
                      : 引入字符集
                      : 授权
                      : 缓存
                      : 内容编码
      HTTP/1.1 - 1997 : PUT、PATCH等方法
                      : 引入持久连接
                      : 支持管道
                      : 分块传输
                      : 摘要认证和代理身份验证
                      : 引入缓存、字节范围等
      HTTP/2 - 2015   : 二进制替代文本
                      : 多路复用
                      : 报头压缩
                      : 服务器推送
                      : 请求优先级
                      : 安全性
                      

文章内容

什么是HTTP

HTTP是一个基于TCP/IP协议的应用层通信协议,它用于为客户端和服务器端的通信提供了标准。 它定义了内容如何被请求并传播到网络上。通过应用层协议,它可视为一个抽象层,它标准化 了主机之间(客户端和服务器端)如何进行通信。HTTP本身依赖TPC/IP来获取客户端和服务 器端的请求和响应。通常的,TCP使用80端口,HTTPS使用443端口(当然,也可以使用 其它的端口)。

HTTP/0.9 - 1991

HTTP的第一个文档版本是1991年提出的HTTP/0.9,它是有史以来最简单的协议,只有一个 GET方法。如果客户端需要访问服务器上的某些网页,它会发出如下的请求:

GET /index.html

然后收到服务器的如下响应:

(请求体)
 关闭连接)

也就是说,服务器在接收到请求后,使用HTML进行应答,并且在内容被传输之后,关闭 连接。特点如下:

  • 没有HTTP头
  • GET是唯一允许的请求方法
  • 响应的内容是HTML

HTTP/1.0 - 1996

不同于仅为HTML响应而设计的HTTP/0.9,HTTP/1.0可以处理其它响应格式,如图像、 视频文件、纯文本或任何其它的内容类型。它增加了更多的方法,POSTHEAD, 更改了请求/响应的格式,在请求和响应中都添加了HTTP头,添加了状态码来识别响应, 引入了字符集支持、多部分类型数据、授权、缓存、内容编码等。

一个HTTP/1.0请求和响应的样例如下:

GET / HTTP/1.0
Host: cs.fyi
User-Agent: Mozilla/5.0 (Machintosh; Intel Mac OS X 10_10_5)
Accept: */*

除了请求之外,客户端还发送了它的个人信息,所需的响应类型等信息。而在HTTP/0.9 中,客户端永远不能发送这样的信息,因为它没有HTTP头。

其对应的响应体样例如下:

HTTP/1.0 200 OK 
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

(响应体)
(连接关闭)

在这个版本中,请求和响应头仍然保持为ASCII编码,但响应体可以是任何类型的,即图像、 视频、HTML、纯文本或任何其它类型的内容。在HTTP引入这些内容之后,超文本一词 或许没有HMTP或者超媒体传输协议更加贴切。

HTTP/1.0的主要缺点之一是一个连接对应一个请求。也就是说,客户端从服务器获取内容时, 它都必须打开一个新的TCP连接,并且在该请求完成后,连接将 关闭。 对于下一个需求,它需要建立一个新的连接。这样子为什么不好呢?让我们假设 你访问的网页有10张图片,5个样式表和5个javascript文件,总共有20个项目需要在对 该网页进行请求时获取。由于服务器在请求完成后立即关闭连接,因此将有20个单独 的连接,其中每个项目将在其单独的连接上逐一提供服务。这种大量的连接会导致严重的性能下降, 因为建立新的TCP连接需要进行三次握手,然后还要进行慢启动,这会带来严重的性能开销。

三次握手

三次握手可简要概括为下图:

1.png

在HTTP/1.0的一些实现中,人们试图通过引入一个名为Connection: keep-alive的新标签 来解决这个问题,它的意思是告诉服务器”嘿,服务器,不要关闭这个连接,我还需要它”。但是 ,它仍然没有得到广泛的支持,问题仍然存在。

除了无连接之外,HTTP也是一个无状态协议,即服务器不维护客户端的信息,所以每个请求都必 须有服务器自己完成请求所需的信息,而不与任何旧请求相关联。这使问题更严重了,客户端除了打 开的大量连接之外,它还会在网络上发送一些冗余的数据,从而增加带宽负担。

HTTP/1.1 - 1997

这个版本对之前做了很多改进,如下:

  • 增加了新的HTTP方法,包括PUTPATCHOPTIONSDELETE
  • 需要提供主机名标识中的主机头
  • 引入了持久连接,即连接在默认情况下不会关闭,而是保持打开状态,从而允许多个顺序请求。 要关闭连接,请求头Connection: close必须是可用的。客户端通常在最后一个请求中发送此头以安全关闭连接。
  • 它还引入了对管道的支持,在同一个连接上,客户端可以向服务器发送多个请求,而无需等待 服务器的响应,并且服务器必须按照接收请求的顺序发送响应。但是你可能会问,客户端怎么知 道这是第一个响应下载完成和下一个响应内容开始的点呢?好吧,为了解决这个问题,必 须存在Content-Length头,客户端可以使用它来标识响应的结束位置,并开始等待下一个响应。
  • 分块传输。对于动态内容,当服务器在开始传输时无法真正知道内容长度时,可以开始分块发 送内容,并在发送时为每个分块添加内容长度。当所有的块被发送时,即整个传输已经完成, 它会发送一个空块,即Content-Length设置为0的块,以便识别传输已经完成的客户端。为了 通知客户端有分块传输,服务器包含了首部Transfer-Encoding: chunked
  • 提供了摘要认证和代理身份验证
  • 缓存、字节范围、字符集、语言协商、客户端cookies、增强的压缩支持、新的状态码等

更详细的HTTP/1.0和HTTP/1.1的内容可查阅:www.ra.ethz.ch/cdstore/www…

HTTP/1.1于1999年推出,多年来一直是一项标准。尽管它比其前身有了很大的改进,但随着网 络的日新月异,它开始显示出其局限性。如今加载网页所需的资源比以往任何时候都要多。一个 简单的网页现在需要打开30多个连接。那么,为什么HTTP/1.1需要这么多连接呢?原因在于 ,HTTP/1.1在任何时刻只能有一个活跃的连接。HTTP/1.1通过引入管道化试图解决这个问题 ,但它并没有完全解决问题,因为头部阻塞会导致慢速或繁重的请求阻塞其后的请求,一旦请求 被困在管道中,它将不得不等待下一个请求完成。为了克服HTTP/1.1的这些局限性,开发人员 开始实施各种方案解决,例如使用spritesheets、CSS中的编码图像、单个庞大的CSS/Java Script文件、域名分片等。

SPDY - 2009

谷歌开始尝试采用其他协议来加快网络速度,提供网络安全性,并减少网页加载时间。在2009年, 他们宣布了SPDY协议。

如果我们不断增加带宽,网络性能在开始时会有所提高,但到了一个点,性能就没有多少提高了。 但是如果不断降低延迟,会有一个恒定的性能增益。这是SPDY背后性能提升的核心思想:减少 延迟以提高网络性能。(延迟是数据在源和目的地之间传输所需的时间,带宽是每秒传输的数据量。

SPDY的特性包括多路复用、压缩、优先级、安全性等,它是存在于HTTP应用层上的转换层,在 将请求发送到网络之前对其进行修改。2015年,谷歌不想有两个相互竞争的标准,所以决定将 SPDY合并到HTTP中,诞生了HTTP/2,并弃用了SPDY。

HTTP/2 - 2015

HTTP/2是为低延迟的内容传输而设计的,它与HTTP/1.1的主要特性或区别如下:

  • 二进制替代文本
  • 多路复用——在单个连接上允许多个异步的HTTP请求
  • 使用HPACK压缩报头
  • 服务器推送——对单个请求进行多个响应
  • 请求优先级
  • 安全性

1 二进制协议

HTTP/2倾向于通过将HTTP/1.x变成二进制协议来解决存在于HTTP/1.x中的延迟增加问题。 作为二进制协议,它更容易解析,但与HTTP/1.x不同的是,它不再人眼可读。HTTP/2的 主要组成部分是帧和流。

帧和流

HTTP消息由一个或多个帧组成:有一个HEADERS帧用于元数据,DATA帧用于有效负载, 以及其他类型的帧。

每个HTTP/2的请求和响应都有一个唯一的流ID,并被划分为帧。帧是二进制数据块。帧的集合 称为流。每个帧都有一个流id来标识它所属的流,每个帧都有一个公共的首部。此外,除了流 ID是唯一的,客户端发起的任何请求都使用奇数,服务器响应的流ID是偶数。

除了报头和数据,另一个帧类型是RST_STREAM,它是一种特殊的帧类型,用于中止一些流 ,即客户端可以发送此帧来让服务器知道我不再需要此流。在HTTP/1.1中,让服务器停止向 客户端发送响应的唯一方法是关闭连接,这会导致延迟增加,因为必须为任何连续的请求打开 一个新连接。而在HTTP/2中,客户端可以使用RST_STREAM,并在连接仍然打开且其他流仍 在使用时停止接收特定的流。

2 多路复用

由于HTTP/2现在是二进制协议,它使用帧和流进行请求和响应,一旦TCP连接打开,所有 流都会通过同一个连接异步发送,而不会打开任何额外的连接。反过来,服务器以相同的异 步方式响应,即响应没有顺序,客户端使用分配的流id来识别特定分组所属的流。这也解决 了HTTP/1中存在的队首阻塞问题。例如,客户端不必等待正在花费时间的请求,而其他请 求仍将被处理。

3 首部压缩

它是一个单独的RFC的一部分,该RFC专门用于优化发送的headers。它的本质是,当我们 不断地从同一个客户端访问服务器时,我们会不断地发送大量冗余数据报头,有时可能会有 cookie增加报头大小,从而导致带宽占用和延迟增加。为了解决这个问题,HTTP/2引入 了首部压缩。

与请求和响应不同的是,报头不压缩为gzip或compress等格式,但报头压缩有一种不同 的机制,即文字值使用Huffman编码,并由客户端和服务器维护报头表,客户端和服务器 在后续请求中省略任何重复的报头(例如用户代理等),并使用双方维护的报头表引用它们。

4 服务器推送

服务器推送是HTTP/2的另一个重要特性,服务器知道客户端将请求某个资源,就可以在客户 端没有请求的情况下将资源推送给客户端。例如,假设浏览器加载一个web页面,它解析整个 页面以找出它必须从服务器加载的远程内容,然后向服务器发送相应的请求以获取该内容。

允许服务器推送它知道的客户端需要的数据,从而减少往返次数。具体做法是,服务器发 送一个名为PUSH_PROMISE的特殊帧通知客户端:“嘿,我要把这个资源发给你!不 要向我要。”PUSH_PROMISE帧与导致推送发生的流相关联,它包含被承诺的流ID,即 服务器将在该流上发送要推送的资源。

5 请求优先级

客户端可以在打开流的HEADERS帧中包含优先级信息,从而为流指定优先级。在其他 任何时候,客户端可以发送一个优先级帧来改变流的优先级。

在没有任何优先级信息的情况下,服务器异步处理请求,即没有任何顺序。如果给 流分配了优先级,那么服务器会根据这个优先级信息决定处理哪个请求需要多少资源。

6 安全

关于HTTP/2是否应该强制要求安全性(通过TLS),有过广泛的讨论。最后,决定不强制 执行。然而,大多数供应商表示,他们只支持通过TLS使用的HTTP/2。所以,虽然HTTP/2 在规范上不需要加密,但它在默认情况下是强制性的。除此之外,通过TLS实现的HTTP/2也有 一些要求。必须使用TLS 1.2或更高版本,必须有一定的最小密钥长度,需要临时密钥等。