HTTP飞翔
HTTP/2特性概览
在前面的HTTPS中,安全性已经达到了极致,但是在性能方提升方面还是比较欠缺的
而且只能依赖长连接这种落后的技术进行优化
所以现在我们推出了HTTP/2,对性能进行优化
为啥不是HTTP/2.0
因为之前的1.0和1.1等小版本造成了很多混乱,难以区分,所以HTTP协议不再使用小版本号,只使用大版本号
所以HTTP/2之后不会出现2.0这种情况了
这样能让我们明确的辨别出版本协议的跃进程度,让协议在较长一段时间内保持稳定
兼容HTTP/1
由于HTTP/1是目前主流的协议,所以HTTP/2做改动时必须兼容HTTP/1,否则会破坏互联网上的无数现有资产
为了保持兼容性,所以HTTP/2分解成了语义和语法两个部分
- 语义:不做改动,与HTTP/1完全一致,包括请求方法、URI、状态码、头字段等,并且,仍然使用http协议名标识明文协议,这样也可以让浏览器或服务器去自动升级或降级协议
- 语法:语法则会发生巨大的变化,完全变更了HTTP报文的传输格式
头部压缩
HTTP/1中可以使用头字段Content-Encoding指定body编码方式,比如gzip
但是,报文的另外一个组成部分Header却被无视了
由于Header部分可能会携带许许多多的头字段,可能达到几百字节,而body可能只有几十字节,所以数据会变得头重脚轻
这种情况也叫做长尾效应,导致大量带宽消耗在这些冗余度极高的数据上
所以现在HTTP/2针对这种情况,将头部压缩作为性能改进的一个重点,开发了HPACK算法,在客户端和服务器两端建立字典,用索引号表示重复的字符串,还用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率
二进制格式
HTTP/1中使用的是纯文本形式的报文
但是HTTP/2使用了二进制格式,向下层的TCP/IP协议靠拢
这样能方便计算机解析,使用纯文本还容易出现多义性,如大小写、空白字符、回车换行等,而二进制则只有0和1,可以严格规定字段大小、顺序、标志位等格式,解析起来没有歧义,实现简单,体积小、速度快
以二进制格式为基础,HTTP/2将TCP协议的部分特性挪到了应用层,把原来的Header+body的消息打散为数个小片的二进制帧,用HEADERS帧存放头数据,DATA帧存放实体数据
将数据分帧后,原来的报文结构就消失了,现在协议看到的只是一个一个的碎片
虚拟的流
前面说过,现在消息已经变成了一个一个的碎片,所以我们应该如何接收呢
HTTP/2为此定义了流,他是二进制帧的双向传输序列,同一个消息往返的帧会分配一个唯一的流ID
这样HTTP/2就可以在一个TCP连接上用流同时发送多个碎片化信息,这就是常说的多路复用
以这种方式传输,就不会出现队头阻塞的问题了,降低了延迟,大幅度提高了连接的利用率
并且,HTTP/2还在一定程度上改变了传统的请求应答模式,现在服务器也可以主动新建流向客户端发送信息,比如可以在浏览器请求HTML的时候就先把JS、CSS等文件发送给客户端,这就是服务器推送
强化安全
出于兼容的考虑,HTTP/2延续了HTTP/1的明文特征,还是一样使用明文传输数据,不加密,不过格式是二进制
但是由于HTTPS大势所趋,所以互联网上的HTTP/2通常是使用https协议名,并且跑在TLS上的
为了区分加密和明文这两个不同的版本,HTTP/2协议定义了两个字符串表示:h2表示加密的HTTP/2,h2c表示明文的HTTP/2
协议栈
HTTP/2是建立在HPack、Stream、TLS之上的,如图:
HTTP/2内核剖析
这一节,我们对上述的特性做进一步的深入了解
连接前言
在TLS握手成功之后,客户端要发送一个连接前言,用来确认建立HTTP/2连接
这个连接前言是标准的HTTP/1请求报文,使用纯文本的ASCII码格式,请求方法是特别注册的一个关键字PRI:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
在Wireshark中,HTTP/2连接前言也被称为Magic
头部压缩
前面我们说过,使用HPACK算法来压缩头部数据,现在我们详细来了解一下HPACK算法
这是一个有状态的算法,需要在客户端和服务器各自维护一份索引表,也就是字段,压缩和解压缩就是查表和更新表的操作
为了方便管理和压缩,HTTP/2把起始行废除了,请求方法、URI、状态码等统一转化成头字段的形式,并且给这些字段起了个名字——伪头字段
而版本号和错误原因由于没有啥作用,就被废除了
伪头字段会在名字前面加上一个: ,比如:authority、:method、:status
HTTP报文头就简单了,全都是Key-Value形式的字段,所以HTTP/2就为最常用的头字段定义了一个只读的静态表
如果静态表中只有key没有value或者自定义字段找不到,那么就要使用动态表了,他添加在静态表后, 结构相同,在编码解码时会随时更新
比如第一次发送user-agent的时候,字长是100多字节,采用哈夫曼压缩编码发送之后,客户端和服务器都更新自己的动态表,添加一个新的索引号65,下一次发送只需要发送一个字节的编号就可以了
这种方式发送头部字段都会极大的节省资源,压缩效果比gzip好很多
二进制帧
HTTP/2中的二进制帧类似TCP的段或者TLS中的记录,但是报头很小,只有9字节,非常节省
帧开头是3个字节的长度,默认上限是214,最大是224,也就是说通常不超过16k,最大是16M
接下来1个字节是帧类型,可以分为数据帧和控制帧,HEADERS帧和DATA帧属于数据帧,存放HTTP报文,SETTINGS、PING、PRIORITY等则用管理流的控制帧
HTTP/2定义了10中类型的帧,但是一个字节最多可以表示256种,也就是说允许我们自己扩展
第5个字节是帧标志信息,可以保持8个标志位,携带简单的控制信息
报文头的最后4个字节是流标识符,也就是帧所属的流,接收方使用它就可以从乱序种找出具有相同流ID的帧序列,按顺序组装起来就实现了虚拟的流
流与多路复用
流与多路复用是HTTP/2中最核心的部分
流是二进制帧的双向传输序列,关键是帧头中的流ID
虽然说HTTP/2上的帧是乱序收发的,但是只要他们拥有相同的流ID,就都属于一个流
一个HTTP/2的流就等同于一个HTTP/1中的请求应答
其中流的特点有:
- 流是可并发的,即多路复用
- 客户端和服务器都可以创建流
- 流是双向的,一个流里面客户端和服务器都可以发送或接收数据帧
- 流之间没有固定关系,互相独立,但是流内部的帧有严格顺序
- 流可以设置优先级
- 流ID不能重用,客户端发起的ID是基数,服务器发起的ID是偶数
- 在流上发送RST_STREAM帧可以随时终止流,取消接收或发送
- 第0号流不能关闭,也不能发送数据帧,只能发送控制帧,用于流量控制
在这些特点中,我们还可以推出深层次的知识点:
- HTTP/2使用多个流收发数据,那么本身就是长连接,所以不需要
Connection头字段 - 下载大文件想取消接收,则可以在HTTP/2中发送一个
RST_STREAM断流,而长连接会继续保持 - 客户端在一个连接中只能最多发出230个请求
- ID用完之后可以再发一个控制帧
GOAWAY关闭TCP连接
应该迁移到HTTP/2吗
HTTP/2的优点
完全保持了与HTTP/1的兼容,由于兼容HTTP/1,所以具有HTTP/1的所有优点,并且基本解决了HTTP/1的所有缺点,安全与性能兼顾
可以认为是更安全的HTTP、更快的HTTPS
在安全上,HTTP/2对HTTPS在各方面都做了强化
影响网络速度的两个关键因素是带宽和延迟,HTTP/2的头部压缩、多路复用、流优先级、服务器推送手段其实都是针对这两个要点的
- HTTP/2的多路复用特性要求对一个域名只用一个TCP连接,这样能让所有数据都在一个连接上传输,这样可以节约客户端、服务器和网络的资源,还可以把带宽跑满
- 由于流可能会有依赖关系,可能存在等待导致的阻塞,所以优先级就派上用场了,可以告诉服务器哪个文件更重要,更需要优先传输,就可以调高流的优先级,合理的分配优先的带宽资源
- 服务器推送也是降低延迟的有效手段,不需要客户端预先请求,服务器可以直接发给客户端,省去客户端解析HTML再请求的时间
HTTP/2的缺点
HTTP/2在TCP级别还是存在队头阻塞的问题,所以网络质量差的话,发生丢包,那么TCP就会等待重传,传输速度降低
并且,HTTP/2对一个域名只开一个连接,所以只要这个链接出现问题,那么整个网站的体验也会变差
而对于HTTP/1,则不会受到较大的影响,因为其本来就慢,并且一个域名还能开多个连接,所以顶多其中一两个连接变慢,其他不会受影响
应不应该迁移HTTP/2
HTTP/2处在一个比较尴尬的位置,前有HTTP/1,后有HTTP/3
但是,由于其性能的大幅提升,还是有很多大网站使用的
如果网站流量很大,就可以使用HTTP/2
如果网站流量比较小,就不需要使用HTTP/2,用现有的HTTP就足够了
如果是新建网站,则直接可以使用HTTP/2,可以得到性能的提升,又可以丢掉历史包袱
配置HTTP/2
如果我们已经迁移到HTTPS了,那么在Nginx中只需要在server配置中多加一个参数就可以了
server {
listen 443 ssl http2;
}
只需要在ssl后面多加一个http2即可