HTTP/2 探索第一篇——概念

阅读数:2190 2019 年 8 月 22 日 19:06

HTTP/2探索第一篇——概念

一、现状

1. 网络性能

现在网络优化的瓶颈是什么?你可能会说,带宽。也许在 2014 年前,决定性能的关键是带宽,但是在今天以及以后,瓶颈都不会是带宽,而是延迟;

HTTP/2探索第一篇——概念

从图中可以看出,随着带宽的增长,页面加载时间 (PLT Page Load Time) 在 1Mbps 到 3Mbps 的区间得到了很大的改善,但是再提高带宽,带来的提升就很小了,属于非线性改善;反观延迟,延迟(这里是指多个 RTT 时间相加的总和)的改善对于页面加载时间是属于线性改善;

HTTP/1.1 TCP 连接是需要三次握手的,同时,多个 TCP 连接也会给服务器带来资源的消耗,在 HTTP/1.1 中,每个请求回复都是一次 TCP 连接(未开启 Keep-Alive 的情况下),并且,同时传输多个资源时,会有队首阻塞的问题,造成网络资源无法有效利用;

2. 安全

对于大多数人来说,下图的情况几乎都有遇到过(电脑或手机里)。万恶的运营商或者网络接入 WiFi 提供商劫持我们的网络,修改网络的内容,给我们带来了很大的困扰;

HTTP/2探索第一篇——概念

二、HTTP/2.0

现在,HTTP/2.0 出现了。其实 HTTP/2.0 是支持 Clear Text 版和 Over TLS 版,由于现有支持 HTTP/2.0 的浏览器都是实现的 Over TLS 版,故本文的 HTTP/2.0 都是讲的是 HTTPS 版 HTTP/2.0;

1. Clear Text 版:

客户端向服务端请求(假设此时 scheme 是 HTTP),带有以下头:

Upgrade: h2c

HTTP2-Settings

服务器端返回:

101 状态码,转换协议;

Connection: Upgrade

Upgrade: h2c 或者 200/404

2. HTTP/2.0 Over TLS 版:

客户端向服务器端请求

TLS + ALPN(Application Layer Protocol Negotiation)/NPN

服务器端返回:

TLS 握手 并返回支持的 HTTP 协议;

a. TLS 握手详细过程

HTTP/2探索第一篇——概念

b. ALPN 协商过程
参考 TLS 握手过程图,下面是增加 ALPN 协商的具体过程:

客户端添加一个 ProtocolNameList 字段,包含支持的 HTTP 协议到 ClientHello 消息中;

服务器端查看 ProtocolNameList 字段后通过 ServerHello 消息返回 ProtocolName 字段,表明被选定的协议;

通过实现 ALPN,不再需要单独请求一次服务器带上 Upgrade: h2c;

c. False Start
通常情况下,使用 ALPN 会搭配使用 False Start,客户端在完成 TLS 握手前提前发送加密后的应用数据,将两次 RTT TLS 握手减少为一次;不过需要同时支持 ALPN(NPN 已经很少用啦)和前向安全性;

d. HSTS
HTTP Strict Transport Security(简称为 HSTS)是一个安全功能,告诉浏览器只能通过 HTTPS 访问当前资源,禁止 HTTP 方式。

如果用户输入域名 www.qq.com ,浏览器首先会去请求 http://www.qq.com,请求过程是明文非加密的,此时容易被中间人攻击,让网路恶意中间商直接接触到用户信息;而 HSTS 是用户请求时,服务器告诉客户端,下次来请求直接请求 https://,而不要再请求服务器来跳转到 https;

同时,开启 HSTS 后,如果证书认证不通过(比如遭到中间人攻击),浏览器此时强制无法打开该网站;

3. 名词解释

流 (Stream):一个 Stream 是包含一条或多条信息,ID 和优先级的双向通道;

消息 (Message):消息由帧组成;

帧 (Frame):帧有不同的类型,并且是混合的。他们通过 stream id 被重新组装进消息中;

4. 概念解释

a. 二进制帧

HTTP/2探索第一篇——概念

HTTP2 的二进制帧是 9 字节 (72 bit)

长度:24bit,也就是理论上可以携带 2^24 字节的数据。但通常由于 SETTINGS_MAX_FRAME_SIZE 的设置,不能发送超过 2^14(16384) 字节的数据;

类型:8bit,决定了该帧的类型;

DATA : 数据帧

HEADERS : 头部帧

PRIORITY : 设置流的优先级

RST_STREAM : 终止流

SETTINGS : 设置连接参数

PUSH_PROMISE : 服务器推送模拟请求帧

PING : 用来计算 RTT 时间和看是否服务器挂了的 Stream

GOAWAY : 告诉对方停止再向当前连接创建 stream

WINDOW_UPDATE : 流量控制

保留字段:1bit,一般为 0;

Stream ID:31bit,Stream 标识,理论上可以有 2147483648,超过这么多 stream 怎么办呢?

如果是客户端无法再创建新的 stream id,可以直接创建新的 TCP 连接,stream id 被重置;

如果是服务器端无法再创建新的 stream id,服务器将会给客户端发一个 GOAWAY 帧,客户端无法再向该服务器创建 stream,不得不新建 TCP 连接;

5. 新特性

多路复用

头部压缩

资源优先级 / 依赖关系

流量控制

Server Push(客户端请求一个路径时,服务器推送一个资源给客户端)

a. 多路服用

HTTP/2探索第一篇——概念

HTTP/2.0 中,数据在发送端被切分为更小的数据帧用以高效利用链接;

HTTP 1.1 时代,再不开启 Keep-alive 的情况下,每一个请求会占用一个 TCP 连接,而 HTTP/2 将请求和响应消息拆分为各自独立的帧,交错的发送,然后再在接收端重新装配组合。有什么好处呢?

交错的多个请求 / 响应之间互现不会被阻塞

HTTP/1.1 时代的 Keep-alive 也是保持同一个 TCP 连接,但是由于请求 / 接收有先后,后面的请求资源会被前面的资源阻塞(没收到响应时不会发新的请求),如下图最左和最右边所示,即便是相比 HTTP 管道,优化也是巨大的:

HTTP/2探索第一篇——概念

减少了不必要的延时,改善了网路的利用率 (多路复用和资源优先级 / 依赖关系搭配使用,使得页面重度依赖的资源优先传输);

b. 头部压缩
HTTP/2.0 使用 HPACK 来给头部压缩;

值通过霍夫曼编码;

之前发送的值都被索引起来,之后使用时发现之前发送过该 Header 字段,并且值相同,就会沿用之前的索引来指代那个 Header 值;

Cookies:在 HTTP/2.0 中,Cookie 也将会变为键值对索引起来,而不是一长串字符串;

可以看看我们组 dream 同学的 HTTP/2.0 之特性科普篇——头部压缩,里面有截图部分的数据讲述压缩后的效果;

HTTP/2探索第一篇——概念

这里需要讲解一下伪头部字段:

请求:

:authority

:method

:path

:scheme

响应:

:status

所有的伪头部字段都是在所有 Header 的前部;

c. 资源优先级 / 依赖关系
资源优先级 / 依赖关系通过 stream 权重和 dependency 来设置;

HTTP/2探索第一篇——概念

通过上图可以看到,有一列是叫作 Priority,初始设置是根据 Content-type 来设置优先级的,比如 HTML 是 Highest,CSS 是 High,然后 JS 是 Medium;

Stream 权重值可以设置为 1 到 256 之间;

Stream 可以明确的表示依赖关系;

注意,一定要理解权重和依赖,权重值和依赖关系是作为带宽资源 / 服务器 / 客户端处理资源的建议值,但并不能保证他们有特定的传输顺序。让我们来看一张 HTTP/2.0 的依赖关系和权重图:

HTTP/2探索第一篇——概念

HTTP/2.0 中的 stream 都默认是依赖于一个根 stream(其实不存在)。权重值是针对同级来计算的,不同级是不用来计算的;

d. 流量控制
与 TCP 的流量控制类似,不过 HTTP/2.0 的流量控制可以到具体帧,而 TCP 是 TCP 连接层面上的。注意:流量控制目前只对 DATA 帧有效!流量控制的算法没有具体要求使用哪一种,但是大概实现的功能是这样的:

两端收发保有一个流量控制 (window) 窗口;

发送端每发送一个 DATA 帧,就把窗口的大小递减,递减量为这个帧的大小,要是窗口大小小于该帧的大小,那么这个帧就必须被拆分。如果窗口值等于 0,就不能发送任何帧。流量控制的初始默认窗口值大小为 65535 字节 (理论上可以设置 2^31-1 字节也就是 2147483647 字节大小的窗口值)。

接收端可以通过发送 WINDOW_UPDATE 帧给发送端,发送端以帧内指定的窗口大小增量加到窗口大小限制上。

e. Server Push
Server Push 的资源同样需要遵守同源策略,通过:authority 来判断;

HTTP/2探索第一篇——概念

如 Demo 里所示,如果在服务器端设置当请求 Path/examples/dashboard 时就推送 /examples/dashboard/d3.js,现在我们来看抓包:

HTTP/2探索第一篇——概念

说明:

当客户端请求服务器时(此时的请求路径已经设置好推送),服务器发回一个 PUSH_PROMISE 和两个 HEADERS Frame,从 Stream Identifier 可以看出,第一个 HEADERS 的 Stream ID 是 1,也就是复用请求的 Stream 来返回(这是 HTML 文件的返回响应 Header)。第二个 HEADERS 就是推送文件的响应 Header。

根据定义,由客户端初始化发起的 Stream 的标识符是奇数,由服务器端初始化发起的 Stream 是偶数,图中可以体现;

那么 Stream 1 和 Stream 2 的顺序如何保证呢?说明文档里有这样一句话:

Pushed streams initially depend on their associated stream.

也就是说,服务器将要推送的资源依赖于触发推送的请求,根据 Stream 依赖的功能,只有被依赖的 stream 加载完后才会去加载接下来的 stream;

Server Push 有什么好处呢:

推送的资源可以被客户端缓存;

推送的资源可以被不同的页面复用;

推送资源也是支持多路复用的;

推送资源可以被客户端拒绝掉 (客户端接收到 PUSH_PROMISE 后,可以选择发送 RST_PROMISE 来拒绝接收,告诉服务器端不要再发送了,当然,此时可能已经有部分内容已经发送过来了);

同时,Server Push 配合流量控制,可以实现很多很神奇的功能,这里卖个关子,然后会在下一篇讲解 :)

本文转载自公众号小时光茶舍(ID:gh_7322a0f167b5)。

原文链接:

https://mp.weixin.qq.com/s/Y1x0GUkfRbWEz6YsJrMTAQ

评论

发布