HTTP知识点整理

HTTP/2是HTTP协议自1999年HTTP 1.1发布后的首个更新,主要基于SPDY协议。于2015年正式发布,HTTP/2解决了HTTP1.1线头阻塞、重复建立TCP连接等问题,充分利用TCP连接的高效传输,使得网络延迟大幅下降,网络传输速度大幅提升。

HTTP/0.9 - 1991

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

1
GET /index.html

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

1
2
(response body)
(connection closed)

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

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

HTTP/1.0 - 1996

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

如下所示,这是一个通过 HTTP/1.0 请求和响应的例子:

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

正如你所见,客户端除了发送请求以外,它还发送了它的个人信息,要求响应类型等。而在 HTTP/0.9 中因为没有头信息,客户端是不会发送这些信息的。

上面的例子对应的服务器响应结果如下:

1
2
3
4
5
6
7
8
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)

在这个新版本中,请求和响应的头信息仍然采用 ASCII 编码方式,但具体的响应内容可以是任何类型的,例如:图像、视频、HTML、纯文本或任何其他的内容类型。因此,现在的服务器端可以向客户端发送任何内容类型的数据;

HTTP/1.0的主要缺点之一是,你不能在每个连接中发送多个请求。也就是说,每当客户端要向服务器端请求东西时,它都会打开一个新的TCP连接,并且在这个单独请求完成后,该连接就会被关闭。且对于下个需求时,它必须再创建一个新的连接。为什么会如此糟糕呢?好吧,来让我们做个假设,假设你需要访问一个包含10张图片、5个样式表和5个JS文件的网页,这是一个共20项内容要请求的网页。由于服务器会在每个请求完成后将连接关闭,所以,这将会有一系列的20个独立的连接,每个项目均有一个单独的连接。因为三次握手和其后的缓慢启动机制,若每次请求都创建一个新的TCP连接,这就会带来明显的性能损失,最终的结果就是,这些大量的连接会导致严重的性能下降。

HTTP/1.1 - 1999

1999年发布了HTTP/1.1,这是目前使用最广泛的一个版本,相对于HTTP/1.0,HTTP/1.1主要改进内容包含:

  • 新增的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对上一版协议进行了很多改进,但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/2 是专为低延迟传输的内容而设计。 关键特征或与 HTTP / 1.1 旧版本的差异,如下:

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

二进制协议

二进制分帧层

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

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

数据流、消息和帧

新的二进制分帧机制改变了客户端与服务器之间交换数据的方式。 为了说明这个过程,我们需要了解 HTTP/2 的三个概念:

  • 数据流(Stream):已建立的连接内的双向字节流,可以承载一条或多条消息。
  • 消息(Message):与逻辑请求或响应消息对应的完整的一系列帧。
  • 帧(Frame):HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流。

这些概念的关系总结如下:

  • 所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。
  • 每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。
  • 每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
  • 帧是最小的通信单位,承载着特定类型的数据,例如 HTTP 标头、消息负载,等等。 来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。

说通俗点就是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来停止接收指定的流而不关闭连接且还可以在此连接中接收其它流。

简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。

多路复用

在 HTTP/1.x 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接(请参阅使用多个 TCP 连接)。这是 HTTP/1.x 交付模型的直接结果,该模型可以保证每个连接每次只交付一个响应(响应排队)。更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。

HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。

将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP 2 最重要的一项增强。事实上,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升,让我们可以:

  • 并行交错地发送多个请求,请求之间互不影响。
  • 并行交错地发送多个响应,响应之间互不干扰。
  • 使用一个连接并行发送多个请求和响应。
  • 不必再为绕过 HTTP/1.x 限制而做很多工作(请参阅针对 HTTP/1.x 进行优化,例如级联文件、image sprites 和域名分片。
  • 消除不必要的延迟和提高现有网络容量的利用率,从而减少页面加载时间。

HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,应用速度更快、开发更简单、部署成本更低。

头部压缩(Hpack)

每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。 在 HTTP/1.x 中,此元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:

  • 这种格式支持通过静态 Huffman 代码对传输的标头字段进行编码,从而减小了各个传输的大小。
  • 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的标头字段的索引列表(换句话说,它可以建立一个共享的压缩上下文),此列表随后会用作参考,对之前传输的值进行有效编码。

利用 Huffman 编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。

头部压缩的过程如下图所示:

  • 连接会维持一个查找表,该查找表中给出了62个常用字段及其默认值(来自对多个常用网站中请求的统计)。
  • 在第一次请求时,会首先查看查找表中对应字段的值是否为本次请求携带的值,如果不是则更改这个查找表,同时增加查找表中没有的字段。在查找表中将所有头部添加完成后才根据查找表对头部进行压缩并发送请求。
  • 在接下来的每次请求中(当前连接),报文中的压缩数据仅包含查找表中被修改的字段。鉴于一个连接中很多请求中大部分头部字段携带的值都是不常变化的,此举可以大大降低头部传输

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

注:在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个 :method、:scheme、:authority 和 :path 伪标头字段。

数据流优先级

在HTTP/2中,请求和响应是可以乱序传输的,因此我们需要一个机制可以确保哪些被其他响应数据所依赖的或者关键资源被优先传输,以使网页的呈现和使用具有最好的体验。HTTP/2中在流的层面,采用了“优先级树”的形式确保响应数据能够按照依赖关系和优先级顺序来传输。

“优先级树”可以表示成如上图所示的样子。其中子节点所表示的流响应依赖于父节点流,因此父节点流应该被优先传输。在兄弟节点中,被分配权重较大的点应该被分配更多的网络资源,被优先传输。 如下图中(3)所示的“优先级树”中,A,B依赖于C,C依赖于D,因此D应该被优先传输,D传输完后才应该传输C,同理C传输完成后才应该传输A,B,A,B的数据在传输过程中所占用的网络资源应遵循3:1的关系。

服务端推送

HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。

为什么在浏览器中需要一种此类机制呢?一个典型的网络应用包含多种资源,客户端需要检查服务器提供的文档才能逐个找到它们。那为什么不让服务器提前推送这些资源,从而减少额外的延迟时间呢?服务器已经知道客户端下一步要请求什么资源,这时候服务器推送即可派上用场。

事实上,如果您在网页中内联过 CSS、JavaScript,或者通过数据 URI 内联过其他资产(请参阅资源内联),那么您就已经亲身体验过服务器推送了。对于将资源手动内联到文档中的过程,我们实际上是在将资源推送给客户端,而不是等待客户端请求。使用 HTTP/2,我们不仅可以实现相同结果,还会获得其他性能优势。 推送资源可以进行以下处理:

  • 由客户端缓存
  • 在不同页面之间重用
  • 与其他资源一起复用
  • 由服务器设定优先级
  • 被客户端拒绝

PUSH_PROMISE 101

所有服务器推送数据流都由 PUSH_PROMISE 帧发起,表明了服务器向客户端推送所述资源的意图,并且需要先于请求推送资源的响应数据传输。这种传输顺序非常重要:客户端需要了解服务器打算推送哪些资源,以免为这些资源创建重复请求。满足此要求的最简单策略是先于父响应(即,DATA 帧)发送所有 PUSH_PROMISE 帧,其中包含所承诺资源的 HTTP 标头。

在客户端接收到 PUSH_PROMISE 帧后,它可以根据自身情况选择拒绝数据流(通过 RST_STREAM 帧)。 (如果资源已经位于缓存中,可能会发生这种情况。) 这是一个相对于 HTTP/1.x 的重要提升。 相比之下,使用资源内联(一种受欢迎的 HTTP/1.x“优化”)等同于“强制推送”:客户端无法选择拒绝、取消或单独处理内联的资源。

使用 HTTP/2,客户端仍然完全掌控服务器推送的使用方式。客户端可以限制并行推送的数据流数量;调整初始的流控制窗口以控制在数据流首次打开时推送的数据量;或完全停用服务器推送。这些优先级在 HTTP/2 连接开始时通过 SETTINGS 帧传输,可能随时更新。

推送的每个资源都是一个数据流,与内嵌资源不同,客户端可以对推送的资源逐一复用、设定优先级和处理。 浏览器强制执行的唯一安全限制是,推送的资源必须符合原点相同这一政策:服务器对所提供内容必须具有权威性。

去除某些针对HTTP/1.x的优化

在HTTP/1.x中存在一些用于Web性能提升的“奇技淫巧”,但这些技术可能在HTTP/2中起到相反的作用,因此我们第一步就是先去除这些可能会妨碍性能的优化。

###域名分片(domain sharding)
HTTP/1.x中,通信两端最多只有六个连接,且是通过区分不同域名来维持管理的。为了突破这个限制,通常会把请求资源至于不同的域名下(如 shard1.example.org, shard2.example.org)。而在HTTP/2中,因为不需要新开连接来解决头阻塞问题,所以不需要通过这种方式来增加通信的连接数。相反的在HTTP/2中采用域名分片会造成以下两个问题:

  • 增加DNS域名解析时间
  • 增加传输中压缩头部的大小。前文已经提到,头部压缩中的数据复用是在一个连接上维持的,域名分片后新开的连接无法复用之前已经发送过的头部数据,造成一些不必要地数据在网络上的传输。

雪碧图

HTTP/1.x中,为了减少请求多个图片带来的头阻塞问题,通常采用把多个图片拼接成一个大图,然后一个请求将所有图片加载在浏览器中,然后使用CSS技术将所需要的部分按需展示出来。显然的,雪碧图会带来如下问题:

  • 增加代码量,需要写一些本不必要地CSS代码
  • 在浏览器渲染过程中,内存中需要加载更多的图片内容

###拼接JavaScript CSS
同雪碧图的原理一样,拼接JavaScript、CSS的做法同样是为了减少请求数,以避免潜在的头阻塞问题。在HTTP/2中,因为不存在头阻塞问题,因此应该避免这种优化方式带来的一些不必要地资源的加载。


参考链接:
https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn
https://zh.wikipedia.org/wiki/HTTP/2
https://juejin.im/post/59e46b2651882578b8185a05

© 2019 GOYTH All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero