[译]HTTP/2 发展历程

自从上一篇博客到现在,已经很久没有更新文章了,主要原因还是没有太多的时间投入其中。今天我终于有点时间了,那么来写一些HTTP相关的内容吧。 每个Web开发者都应该了解HTTP协议以及它在整个网络中所起的作用,知道了这些,能对你开发更好的Web应用起到帮助作用。 在这篇文章中,我们将讨论:HTTP是什么,它的由来,现在的发展如何以及它是如何走到这一步的。

HTTP是什么?

首先,HTTP是什么?HTTP是基于
TCP/IP的应用层通信协议,它是客户端和服务器之间相互通信的标准。它规定了如何在互联网上请求和传输内容。通过应用层协议,我的意思是,它只是一个规范了主机(客户端和服务器)如何通信的抽象层,并且它本身依赖于
TCP/IP来获取客户端和服务器之间的请求和响应。默认的TCP端口是80端口,当然,使用其他端口也是可以的。然而,HTTPS使用的端口是443端口。

HTTP/0.9 – 极简版(1991)

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

GET /index.html

并且来自服务器的响应内容如下:

(response body)
(connection closed)

也就是说,服务器会得到这个请求,然后通过HTML格式回复响应内容,且一旦响应内容发送完毕,就会关闭这个连接。归纳一下:

  • 没有header数据块
  • GET方法是唯一允许的方法
  • 必须以HTML格式响应

正如你所见,这个协议只不过是未来版本的一个垫脚石罢了。

HTTP/1.0 – 1996

1996年,HTTP的下一个版本,即
HTTP/1.0 诞生了,它在原版本上做出了极大的改善。不像
HTTP/0.9 仅能以HTML格式响应,
HTTP/1.1 现在可以处理其他的响应格式了,例如:图像,视频文件,纯文本或其他任何的内容类型。它增加了更多的方法(即
POST
HEAD),请求/响应的格式也发生了改变,请求和响应中均加入了HTTP头信息,响应数据还增加了状态码标识,还介绍了字符集的支持、多部分发送、权限、缓存、内容编码等很多内容。 如下所示,这是一个通过
HTTP/1.0 请求和响应的例子:


GET / HTTP/1.0
Host: kamranahmed.info
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*

正如你所见,客户端除了发送请求以外,它还发送了它的个人信息,要求响应类型等。而在
HTTP/0.9 中因为没有头信息,客户端是不会发送这些信息的。 上面的例子对应的服务器响应结果如下:


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

(response body)
(connection closed)
最开始的响应内容是
HTTP/1.0(HTTP后面的是版本号),然后是状态码
200,再往后是原因短语(或状态码的描述)。 在这个新版本中,请求和响应的头信息仍然采用
ASCII 编码方式,但具体的响应内容可以是任何类型的,例如:图像、视频、HTML、纯文本或任何其他的内容类型。因此,现在的服务器端可以向客户端发送任何内容类型的数据;不久之后,
HTTP介绍中的“超文本”术语就变得名不副实了。也许用
HMTP或超媒体传输协议可能会更有意义一些,但是,我想,我们还是会一直用
HTTP这个名字的。
HTTP/1.0的主要缺点之一是,你不能在每个连接中发送多个请求。也就是说,每当客户端要向服务器端请求东西时,它都会打开一个新的TCP连接,并且在这个单独请求完成后,该连接就会被关闭。且对于下个需求时,它必须再创建一个新的连接。为什么会如此糟糕呢?好吧,来让我们做个假设,假设你需要访问一个包含10张图片、5个样式表和5个JS文件的网页,这是一个共20项内容要请求的网页。由于服务器会在每个请求完成后将连接关闭,所以,这将会有一系列的20个独立的连接,每个项目均有一个单独的连接。因为三次握手和其后的缓慢启动机制,若每次请求都创建一个新的
TCP连接,这就会带来明显的性能损失,最终的结果就是,这些大量的连接会导致严重的性能下降。
三次握手协议 三次握手的简单新式是,所有的TCP连接开始于客户端和服务器的三次握手,并在开始共享应用数据之前会先共享一系列的数据包。

  • SYN – 客户端创建一个随机数(称为x),并且将x发送给服务器。
  • SYN ACK – 服务器确认请求后,当确认x是从客户端发来时会发送一个ACK数据包(称为y,y=x+1)返回给客户端。
  • ACK – 客户端收到从服务器发来的增量y后,向服务器发送ACK(x+1)确认包
当三次握手完成后,客户端和服务器之间的数据共享就可以开始了。需要注意的是,客户端可能会在处理完最后一个
ACK数据包之后,就开始发送应用数据了。但服务器为了完成请求,仍然需要等待
ACK数据包收到才行。
《[译]HTTP/2 发展历程》 然而,一些
HTTP/1.0 的实现试图通过引入一个新的头信息
Connection: keep-alive 来解决这个问题,这是为了告诉服务器:“嘿,服务器,请不要关闭这个连接,我还要用它呢”。但这并没有得到广泛的支持,所以问题仍然存在。 除了是无连接的,
HTTP 也是一个无状态的协议,例如:服务器不会维护客户端的信息,因此,在它自己与任何旧的请求没有任何关联的情况下,服务器为了能完成请求,就需要每一个请求都必须带有服务器所需要的信息才行。所以,这简直就是“火上浇油”啊:客户端除了要打开大量的连接,它还必须要发送一些冗余的数据,这就导致了需要使用更多的带宽。

HTTP/1.1 – 1999

HTTP/1.0仅发布了3年之后,它的下一个版本,即
HTTP/1.1便在1999年问世了,它在之前的基础上做了很多的改进。基于
HTTP/1.0的主要改进内容包含:

  • 新增的HTTP方法PUT、PATCH、HEAD、OPTIONS、DELETE
  • 主机名标识HTTP/1.0 中,Host头信息不是必须项,但 HTTP/1.1 中要求必须要有Host头信息。
  • 持久性连接 正如前面所说,在 HTTP/1.0 中每个连接只有一个请求 ,且在这个请求完成后该连接就会被关闭,从而会导致严重的性能下降及延迟问题。HTTP/1.1 引入了对持久性连接的支持,例如: 默认情况下连接不会被关闭,在多个连续的请求下它会保存连接的打开状态。想要关闭这些连接,需要将 Connection: close 加入到请求的头信息中。客户端通常会在最后一次请求中发送这个头信息用来安全的关闭连接。
  • 管道机制 HTTP/1.1也引入了对管道机制的支持,客户端可以向服务器发送多个请求,而无需等待来自同一连接上的服务器响应,并且当收到请求时服务器必须以相同的顺序来响应。但你可能会问:客户端是怎么知道第一个响应下载完成和下一个响应内容开始的?要解决这个问题,必须要有Content-Length头信息,客户端可以用它来确定响应结束,然后开始等待下一个响应。
应该注意的是,为了从持久性连接或管道机制中获益,
Content-Length头信息必须在可用的响应中,因为这会让客户端知道当传输完成后,它可以发送下一个请求(用正常顺序发送请求的方式)或者开始等待下一个响应(当启用了管道机制时)。
但这种方法仍然存在一个问题:如果数据是动态的,且服务器找不到之前的内容长度时怎么办?那么,在这种情况下,你就真的不能从持久性连接中收益了,不是吗?!为了解决这个问题,在
HTTP/1.1 中引入了分块编码的支持。在这种情况下,服务器可能会忽略
Content-Length 并支持分块编码。然而,如果他们没有可用的数据,那么连接必须在请求结束时关闭。

  • 分块传输 在动态内容的情况下,当传输开始时服务器无法找到 Content-Length 头信息的话,它也可以开始以块的方式发送内容(一块一块的发),并且当每个小块发送后,它会给每个块添加一个 Content-Length 头信息。当所有的块发送完成后(即整个传输已经完成),它会发送一个空的块(即 Content-Length 为零的块)以确定客户端的传输已经完成。为了通知客户端采用分块传输的方式,服务器需要在头信息中包含Transfer-Encoding: chunked
  • 不像HTTP/1.0 只有基本的身份验证,HTTP/1.1 还包含摘要和代理验证
  • 高速缓存
  • 字节范围
  • 字符集
  • 谈判语言
  • 客户端cookie
  • 加强对压缩的支持
  • 新的状态代码
  • 以及更多。。。
本文我不打算介绍 
HTTP/1.1的所有功能 ,因为它本身就是一个大的话题同时你也可以找到很多的相关资料。这里有一个相关的文档,我建议你优先阅读 
Key differences between HTTP/1.0 and HTTP/1.1(HTTP/1.0与1.1的重要差异) 和 
original RFC (RFC规范)。
HTTP/1.1是在1999年发布的,成为标准有很多年了。 虽然它对上一版协议进行了很多改进,但web世界每天都在改变,它开始显现出了它的不足。现在访问的网页与以前相比包含的资源更多。一个简单的网页都会至少打开30个连接。 我们知道 
HTTP/1.1 是持久连接,那为什么还需要这么多连接?你会说这是由于HTTP/1.1在任何时刻都只有一个有效连接。 
HTTP/1.1尝试通过pipeline来解决这个问题,但是它并没有完全的解决,因为在pipeline中有一个请求被卡住后面的请求都会被阻塞的队头阻塞问题。它将不得不等待下一个请求。 为了克服
HTTP/1.1的这些缺点, 开发人员开始尝试一些解决方案,如:在CSS中使用雪碧图、图像编码 ,合并CSS或Javascript文件, 
域分片(将多个资源分别放入不同的子域名下)等等。

SPDY – 2009

Google走在前面,它开始试验一种可替换的协议来减少网页的延迟,使得网页加载更快、提升web安全性 。 2009年, 他们称这种协议为
SPDY。
SPDY是谷歌的一个商标,不是一个缩写词。 它们意识到如果继续增加带宽来提升网络性能的过程中,必然在到达某一个点后不会再带来更多提升。在有延迟的情况下如果我们不断减少延迟,那么性能建会是一个常数。这是 SPDY性能提升背后的核心理概念,减少延迟来提升网络性能。

对于那些不知道这两个概念的人, 延迟即数据从源传输到目标的耗时(以毫秒为单位),带宽就是每秒传输的数据量(比特/秒).
SPDY的功能包含: 多路复用, 压缩, 优先级, 安全等。我不打算进入SPDY的细节,在进入下一节HTTP / 2后就会明白,HTTP / 2主要受SPDY的启发。
SPDY 没有真正试图替换HTTP,它任然是存在于应用层的基于HTTP的传输层,它在请求发送前进行一些修改。 它开始成为一个事实上的标准,大多数的浏览器开始实现它。 2015年,谷歌不想存在两个相互竞争的标准,因此他们决定把它合并到HTTP中成为HTTP/2,同时放弃SPDY。

HTTP/2 – 2015

现在,你已经知道我们为什么需要一个HTTP协议的修订版了。 
HTTP/2 是专为低延迟传输的内容而设计。 关键特征或与 HTTP / 1.1 旧版本的差异,如下:

  • 二进制,而不是文本
  • 多路复用- 在单个连接中多个异步HTTP请求
  • 使用HPACK报头压缩
  • 服务器推送 – 单请求多个响应
  • 请求优先级
  • 安全

查看图片

1. 二进制协议

HTTP/2 倾向于使用二进制协议来减少HTTP/1.x中的延迟。二进制协议更容易解析,而不具有像
HTTP/1.x 中那样对人的可读性。HTTP/2中的数据块是: 帧和流。

帧和流

HTTP 消息是由一个或多个帧组成的。有一个叫做 
HEADERS 的帧存放元数据,真正的数据是放在
DATA 帧中的,帧类型定义在
the HTTP/2 specs(HTTP/2规范),如(
HEADERS
DATA
RST_STREAM
SETTINGS
PRIORITY 等)。

每个HTTP / 2请求和响应都被赋予一个唯一的流ID且放入了帧中。帧就是一块二进制数据。 一系列帧的集合就称为流。 每个帧都有一个流id,用于标识它属于哪一个流,每一个帧都有相同的头。同时,除了流标识是唯一的,值得一提的是,客户端发起的任何请求都使用奇数和服务器的响应是偶数的流id。 除了 
HEADERS和 
DATA, 另外一个值得说一说帧类型是
RST_STREAM,它是一个特殊的帧类型用于中止流,如:客户端发送这儿帧来告诉服务器我不再需要这个流了。在 
HTTP/1.1 中只有一种方式来实现服务器停止发送响应给客户端,那就是关闭连接引起延迟增加,因为后续的请求就需要打开一个新的连接。 在HTTP/2中,客户端可以使用
RST_FRAME来停止接收指定的流而不关闭连接且还可以在此连接中接收其它流。

2. 多路复用

由于HTTP / 2现在是一个二进制协议,且是使用帧和流来实现请求和响应,一旦TCP 连接打开了, 所有的流都通过这一连接来进行异步的发送而不需要打开额外的连接。反过来,服务器的响应也是异步的方式,如:响应是无序的、客户端使用流id来标识属于流的包。 这就解决了存在于HTTP/1.x 中
head-of-line 阻塞问题,如: 客户端将不必耗时等待请求,而其他请求将被处理。

3. HPACK 头部压缩

它是一个单独的用于明确优化发送header RFC的一部分。它的本质是,当我们同一个客户端不断的访问服务器时,在header中发送很多冗余的数据,有时cookie 就增大header,且消耗带宽和增加了延迟。为了解决这个问题, 
HTTP/2 引入了头部压缩。
查看图片 与请求和响应不同,header不是使用的 
gzip 或 
compress等压缩格式,它有不同的机制,它使用了霍夫曼编码和在客户端和服务器维护的头部表来消除重复的 headers (如:user agent),在后续的请求中就只使用头部表中引用。 既然谈到了header,那就再多说一点,它与HTTP/1.1中的一样,不过增加了伪header,如: 
:method
:scheme,
:host 和
:path

4. 服务器推送

在服务器段,Server push是HTTTP/2的另外一个重要功能,我们知道,客户端是通过请求来获取资源的,它可以通过推送资源给客户端而不需客户端主动请求。 例如,浏览器载入了一个页面,浏览器解析页面时发现了需要从服务器端载入的内容,接着它就发送一个请求来获取这些内容。 Server push允许服务器推送数据来减少客户端请求。 它是如何实现的呢,服务器在一个新的流中发送一个特殊的帧 
PUSH_PROMISE,来通知客户端:“嘿,我要把这个资源发给你!你就不要请求了。”

5. 请求优先级

客户端可以在一个打开的流中在流的
HEADERS 帧中放入优先级信息。 在任何时间,客户端都可以发送一个
PRIORITY 的帧来改变流的优先级。 如果没有优先级信息,服务器就会异步的处理请求,比如:无序处理。如果流被赋予了优先级,它就会基于这个优先级来处理,由服务器决定需要多少资源来处理该请求。

6. 安全

大家对
HTTP/2是否强制使用安全连接(通过TLS)进行了充分的讨论。最后的决定是不强制使用。 然而,大多数厂商表示,他们将只支持基于TLS的 HTTP / 2 。 所以,尽管HTTP / 2规范不需要加密,但它已经成为默认的强制执行的。 在这种情况下,基于TLS实现的 
HTTP/2需要的TLS版本最低要求是1.2。 因此必须有最低限度的密钥长度,临时密钥等。

HTTP/2超越了SPDY的变动也对它进行了增强。HTTP/2有很多性能提升,我们是时候开始使用它。

对它的细节感兴趣的请查阅: 
link to specs 和
demonstrating the performance benefits of HTTP/2.。对于任何问题或意见,在下面的评论区直接提出。 此外,在阅读时,如果你发现任何明显的错误,请指出来。 后续内容!敬请关注。

    原文作者:HTTP
    原文地址: https://juejin.im/entry/57e0e11fc4c9710061386240
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞