HTTP

176 阅读13分钟

HTTP/1.1的优化

如何避免发送HTTP请求?

缓存

image.png

万一缓存的响应不是最新的?

image.png

Etag: 上一次请求的响应资源的摘要,即响应资源的唯一标识。

304: 不含有包体,Not Modified

如何减少HTTP请求次数?

减少重定向次数

重定向请求

image.png

重定向的工作交由代理服务器完成:

image.png

若代理服务器知道了重定向规则:

image.png


重定向响应码

image.png

其中,301308 是告诉客户端可以将重定向响应缓存到本地磁盘;之后客户端就自动用url2替代url1访问服务器资源。

合并请求

把多个访问小资源的请求合并成一个大资源的请求,减少了 重复发送的HTTP头部;

HTTP/1.1是请求响应模型,若上一个发送的请求未收到响应,后续的请求就不会发送。所以浏览器会同时发起5-6个请求,每一个请求都是不同的TCP连接。合并了请求,就会减少TCP连接的数量,省去了TCP握手和慢启动耗费的时间。

  • 多个小图片合并成大图片
  • webpack将js,css等资源合并打包
  • 图片用base64编码,并以url形式嵌入html文件

缺点:当大资源中的某个小资源发生变化后,客户端必须重新下载整个完整的大资源文件。

延迟发送请求

一般HTML里含有很多HTTP的URL,可以按需获取,来减少同一时间的HTTP请求次数。

如何减少HTTP响应的数据大小?

无损压缩

客户端支持的压缩算法,会在HTTP请求中,通过头部的 Accept-Encoding 字段告诉服务器:

Accept-Encoding: gzip, deflate, br

服务器会通过响应头部中的 content-encoding 字段告诉客户端该资源使用的压缩算法:

content-encoding: gzip

有损压缩

可以通过HTTP请求头部的 Accept 字段里的 q质量因子,告诉服务器期望的资源质量。

Accept: audio/*; q=0.2, audio/basic

HTTPS RSA握手

HTTP采用明文传输,所以安全上存在以下三个风险:

  • 窃听风险
  • 篡改风险
  • 冒充风险

image.png

TLS协议如何解决HTTP的风险:

  • 信息加密
  • 校验机制
  • 身份证书

在HTTP通信前,先进行TLS握手。不同的密钥交换算法,TLS握手过程可能会有一些区别。

密钥交换算法: 考虑到性能问题,双方通信时使用的是对称加密密钥;为了保证对称加密密钥的安全性,使用非对称加密的方式来保护对称加密密钥的协商;这个工作就是由密钥交换算法负责的。

RSA是最简单的密钥交换算法。

RSA握手过程

image.png

第一次握手

客户端首先会发一个 Client Hello 消息。 消息里面有客户端使用的TLS版本号,支持的密码套件,生成的客户端随机数。

第二次握手

确认TLS版本号,选择一个密码套件,生成随机数,返回 Server Hello 消息。

密码套件的基本形式是:

密钥交换算法+签名算法+对称加密算法+摘要算法

然后,服务端为了证明自己的身份,会发送 Server Certificate给客户端,这个消息里含有数字证书。

最后,发送 Sever Hello Done

数字证书签发和验证流程

image.png

CA签发证书的过程,如左图:

  • CA将持有者的公钥等信息打包,并计算得到Hash值
  • CA使用自己的私钥对Hash值加密,生成Certificate Signature,即CA对证书签名。
  • 将Certificate Signature添加在证书上。

客户端校验服务端的数字证书的过程,如右图:

  • 客户端使用同样的Hash算法获取该证书的Hash值H1
  • 浏览器和操作系统中集成了CA的公钥,浏览器使用CA公钥解密Certificate Signature内容,得到H2
  • 比较H1和H2,如果值相同,则为可信赖证书。

证书链

我们向CA申请的证书,一般不是根证书签发,而是由中间证书签发的。

image.png

第三次握手

客户端验证完证书后,生成一个新的随机数 pre-master,用服务端的RSA公钥加密该随机数,返回 Change Cipher Key Exchange

服务端收到后,用RSA私钥解密后,得到客户端发来的随机数。

至此,双方已经得到三个随机数,Client Random,ServerRandom,pre-master,生成会话密钥。

生成完会话密钥后,客户端发送 Change Cipher Spec,开始使用加密方式发送消息。

然后,客户端再发送 Encrypted Handshake Message(Finished),把之前所有发送的数据做个摘要,并用会话密钥加密,让服务器做个验证。

Change Cipher Spec 之前传输的TLS握手数据都是明文,之后都是对称加密的密文。

第四次握手

服务器也是同样操作,发 Change Cipher SpecEncrypted Handshake Message(Finished);若双方都验证加密和解密没问题,那么握手正式完成。

RSA算法缺陷

不支持前向保密。一旦服务器的私钥泄露,所有TLS通讯密文都会被破解。

为了解决这个问题,于是就有了DH算法。

image.png

但由于DH算法的效率问题,之后出现了ECDHE算法,也正是现在大多数网站所使用的。

HTTPS ECDHE算法

DH算法

image.png

其中,A,B为公钥;a,b为私钥。

DHE算法

根据私钥生成的方式,DH算法分为两种:

  • static DH算法:服务端私钥不变,(则公钥也不变;因此服务端私钥易被破解,不具备前向安全性)
  • DHE算法:双方的私钥都是临时生成的

ECDHE算法

  • 双方事先确定使用那种椭圆曲线及曲线上的基点G
  • 双方各自生成随机数作为私钥d,并得到公钥Q(Q=dG)
  • 双方各自交换公钥,(x1,y1)=d1Q2=d1d2G=d2d1G=d2Q1=(x2,y2),因此双方的x一样,作为会话密钥。

ECDHE握手

  • 若使用RSA算法,TLS完成四次握手后才能数据传输;ECDHE算法,客户端可以不用等服务端的最后一次TLS握手而提前发送,节省了一个消息的往返时间。
  • 使用ECDHE,在TLS第二次握手时,服务端会发送 Server Key Exchange

TLS第一次握手

客户端发送 Client Hello,包含使用的TLS版本号,支持的密码套件列表,以及随机数Client Random。

TLS第二次握手

服务端返回 Server Hello,包含确认的TLS版本号,一个合适的密码套件,以及随机数Server Random。

接着,发送 Certificate,及证书。

在发送完证书后,服务端会发送 Server Key Exchange。这个过程服务端做了三件事:

  • 选择曲线及基点G;
  • 生成随机数作为私钥;
  • 根据G和私钥计算出椭圆曲线公钥;

为了保证公钥不被篡改,服务端会用签名算法给公钥签名;

最后,发送 Server Hello Done

TLS第三次握手

客户端校验证书合法; 生成一个随机数作为椭圆曲线的私钥;根据服务端前面给的信息,生成客户端的公钥;返回Client Key Exchange。

双方计算出(x,y)。

最终的会话密钥:

客户端随机数+服务端随机数+x

算好后,客户端发送 Change Cipher Spec,告诉服务端后续改用对称算法加密。

最后,发送 Encrypted Handshake Message,把之前发送的数据做一个摘要。

TLS第四次握手

服务端也发送 Change Cipher SpecEncrypted HandShake Message;若双方验证加密和解密没问题,握手完成。

HTTPS如何优化?

分析性能损耗

  • 第一个环节:TLS协议握手过程
  • 第二个环节:握手后的对称加密报文传输

主要针对第一个环节。TLS协议握手过程不仅增加了网络延时(最长可以花费2RTT),而且握手过程中的一些步骤也会产生性能损耗。

image.png

硬件优化

HTTPS协议是计算密集型而不是I/O密集型,所以应该提高CPU。

软件优化

协议优化

密钥交换算法优化

密钥交换算法:选用ECDHE替换RSA。 对称加密算法:选用AES。

TLS升级

image.png

TLS1.3中, 客户端在Client Hello带上了支持的椭圆曲线以及对应的公钥; 服务端选定一个椭圆曲线等参数,在返回消息时带上服务端这边的公钥。

经过这1个RTT,双方已经有生成会话密钥的材料了;于是客户端计算出会话密钥,并进行数据加密传输。

证书优化

证书传输优化

减小证书的大小。用ECDSA证书代替RSA证书; ECDSA密钥长度更短。

证书验证优化

CRL称为证书吊销列表。存在两个问题:

  • 定期更新,实时性差。
  • 列表越来越大,下载,遍历速度变慢。

因此,现在基本使用 OCSP:客户端向CA发送查询请求,让 CA返回证书的有效状态。但是存在网络开销。

因此,出现了OCSP Stapling,服务器周期查询证书状态,获得一个带有时间戳和签名的响应结果并缓存起来。

当客户端发起连接请求时,服务器会把这个响应结果发给客户端;客户端不需要再去查询。

会话复用

Session ID

客户端和服务器首次TLS握手连接后,双方缓存会话密钥,并用唯一的Session ID来标识;相当于 key-value 的关系。

当客户端再次连接时,Hello消息会带上Session ID;找到了则直接恢复会话;当然,内存中的会话密钥会定期失效。

缺点:

  • 随着客户端增多,内存压力变大;
  • 现在网站服务一般由多台服务器提供,客户端连接未必会命中上次的服务器。

Session Ticket

为了解决Session ID的问题,就会出现Session Ticket,服务器不再缓存会话密钥。

客户端与服务器首次连接时,服务器会加密会话密钥,作为Ticket发给客户端,由客户端缓存。

客户端再次连接服务器时,会发送Ticket;服务器解密后获取上一次的会话密钥,然后验证有效期;若没问题,则恢复会话。

对于集群服务器,要保证每台服务器加密 会话密钥 的密钥是一致的。

Session ID和Session Ticket都不具备前向安全性。 同时,应对 重放攻击 也很困难。

image.png

即中间人可以截获客户端的Session ID或Session Ticket以及Post报文,并不断向服务器发送该报文。

因此,需要对会话密钥设定一个合理的过期时间。

Pre-shared Key

Session ID和Session Ticket需要在1 RTT才能恢复会话。 而对于TLS1.3,重连TLS1.3只需要0 RTT。

原理和Ticket类似,只不过重连时,客户端会把Ticket和HTTP请求一同发给服务端。

Pre-shared Key也有重放攻击的危险。 中间人可以截取客户端的会话重用的Post请求。

因此,应对重放攻击,可以给会话密钥设定一个合理的过期时间; 以及,只针对安全的HTTP请求如GET/HEAD使用会话重用。

HTTP/2牛逼在哪?

HTTP/1.1协议的性能问题

  • 并发连接有限。且每一个连接都经过TCP和TLS握手耗时,以及TCP慢启动。
  • 队头阻塞问题。同一个连接只能在完成一个HTTP事务后,才能处理下一个。
  • HTTP头部巨大且重复
  • 不支持服务端推送消息

头部压缩

HPACK算法压缩头部。主要包含三部分:

  • 静态字典
  • 动态字典
  • Huffman编码

静态表编码

HTTP/2为高频出现在头部的字符串和字段建立一张静态表。

image.png

若头部字段属于静态表范围,且Value变化,则头部前2位固定为01。

动态表编码

客户端和服务端在编码解码的时候随时更新,Index从62起步。

动态表生效前提:必须同一个连接上,重复传输完全相同的头部。

二进制帧

image.png

HTTP/2把响应报文划分成两个二进制帧(Frame)。

image.png

流标识符 用于标识该Frame属于哪个Stream。

帧数据 通过HPACK算法压缩过的HTTP头部或包体。

并发传输

多个Stream复用一个TCP连接,解决了HTTP/1.1的队头阻塞问题。

image.png

  • 1个TCP可包含多个Stream
  • 1个Stream可包含多个Message,Message对应HTTP/1中的请求或响应。
  • 1个Message可包含多个Frame。

HTTP消息可由多个Frame构成,1个Frame可由多个TCP报文组成。

不同Stream的帧可以乱序发送,因为每个帧头部都携带Stream ID信息; 同一Stream内部的帧必须严格有序。

image.png

客户端和服务端双方都可以建立Stream。 同一个连接中的Stream ID不能复用,顺序递增。 客户端建立的必须为奇数号;服务端建立的为偶数号。

HTTP/2实现100个并发Stream时,只需要建立一个TCP连接;并且可以对每个Stream设置优先级。

服务器主动推送资源

image.png

通过PUSH_PROMISE帧传输HTTP头部;帧中的Promise Stream ID字段告诉客户端,接下来会在哪个偶数号Stream中发送包体。

HTTP/3 强势来袭

美中不足的HTTP/2

队头阻塞

TCP是字节流协议,必须保证收到的字节数据完整有序,才会将内核缓冲区中的数据发送给HTTP应用。

TCP与TLS的握手时延迟

HTTP请求时的TCP三次握手及TLS1.2的四次握手,需要3个RTT时延; TCP拥塞控制的慢启动过程。

网络迁移需要重新连接

一个TCP连接是由四元组(源IP/端口,目标IP/端口)确定。 若IP/端口变动,将导致TCP和TLS重新握手。

QUIC协议的特点

UDP不需要连接,也就不需要握手和挥手的过程。 UDP在应用层实现了QUIC协议,使其可靠。

无队头阻塞

QUIC连接上的多个Stream相互独立,互不影响; 但某个流的数据包丢失了,即使该流其他数据包到达,数据也无法被读取;要直到丢失数据包重传。

更快的连接建立

HTTP/3的QUIC内部包含了TLS1.3;因此仅需要1个RTT就可以同时完成建立连接与密钥协商。

第二次连接时,应用数据包可以和QUIC握手信息(连接信息+TLS信息)一起发送,达到0-RTT。

连接迁移

QUIC协议没有用四元组方式来绑定连接,而是通过连接ID来标记通信的两个端点。

因此,即使移动设备网络变化,主要客户端和服务端仍保有上下文信息(连接ID,TLS密钥等),就可以无缝复用原链接,消除重连的成本。

HTTP/3协议

HTTP/3直接使用QUIC里的Stream。

image.png

头部压缩算法升级成了QPACK:也采用了静态表、动态表、Huffman编码。

HPACK队头阻塞:若首次出现的请求发生了丢包,后续的收到请求,对方无法解码出HPACK头部。

QPACK通过两个特殊的单向流同步双方的动态表;编码方收到解码方更新确认的通知后,才使用动态表编码HTTP头部。