HTTP
HTTP 基本概念
HTTP 是什么?
HTTP 是超文本传输协议,也就是HyperText Transfer Protocol。它可以拆成三个部分:
- 超文本
- 传输
- 协议
「协议」
- 协字,代表的意思是必须有两个以上的参与者
- 议字,代表的意思是对参与者的一种行为约定和规范
HTTP 是一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范(两个以上的参与者),以及相关的各种控制和错误处理方式(行为约定和规范)。
「传输」
传输就是把一堆东西从 A 点搬到 B 点,或者从 B 点 搬到 A 点 HTTP 协议是一个双向协议。是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。 数据虽然是在 A 和 B 之间传输,但允许中间有中转或接力。在 HTTP 里,需要中间人遵从 HTTP 协议,只要不打扰基本的数据传输,就可以添加任意额外的东西。
「超文本」
超文本就是超越了普通文本的文本,它是文字、图片、视频等的混合体,最关键有超链接,能从一个超文本跳转到另外一个超文本。 HTML 就是最常见的超文本了,它本身只是纯文字文件,但内部用很多标签定义了图片、视频等的链接,再经过浏览器的解释,呈现给我们的就是一个文字、有画面的网页了。 总结起来: HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。
HTTP 常见的状态码有哪些?
五大类状态码 1xx、2xx、3xx、4xx、5xx
1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
- 「200 OK」是最常见的成功状态码,表示一切正常。如果是非 HEAD 请求,服务器返回的响应头都会有 body 数据。
- 「204 No Content」与 200 OK 基本相同,但响应头没有 body 数据。
- 「206 Partial Content」是应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
「301 Moved Permanently」永久重定向,请求的资源已经不存在了,需用新的 URL 再次访问。
「302 Found」临时重定向,请求的资源还在,但暂时要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
「304 Not Modified」不具有跳转的含义,也称缓存重定向,告诉客户端可以继续使用缓存资源。
4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义
- 「400 Bad Request」客户端请求的报文有错误,但只是个笼统的错误。
- 「403 Forbidden」服务器禁止访问资源,并不是客户端的请求出错。
- 「404 Not Found」请求的资源在服务器上不存在或未找到,无法提供给客户端。
5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
- 「500 Internal Server Error」与 400 类似,是个笼统通用的错误码,服务器发生了什么错误。
- 「501 Not Implemented」表示客户端请求的功能还不支持。
- 「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
- 「503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。
HTTP 常见字段有哪些?
Host
客户端发送请求时,用来指定服务器的域名。 有了 Host 字段,就可以将请求发往「同一台」服务器上的不同网站。
Host: www.A.comContent-Length 字段
服务器在返回数据时,会有 Content-Length 字段,表明本次回应的数据长度.
Content-Length: 1000如上面则是告诉浏览器,本次服务器回应的数据长度是 1000 个字节,后面的字节就属于下一个回应了 HTTP 是基于 TCP 传输协议进行通信的,而使用了 TCP 传输协议,就会存在一个“粘包”的问题,HTTP 协议通过设置回车符、换行符作为 HTTP header 的边界,通过 Content-Length 字段作为 HTTP body 的边界,这两个方式都是为了解决“粘包”的问题。
Connection 字段
Connection 字段最常用于客户端要求服务器使用「HTTP 长连接」机制,以便其他请求复用。 HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。 HTTP/1.1 版本的默认连接都是长连接,但为了兼容老版本的 HTTP,需要指定 Connection 首部字段的值为 Keep-Alive。
Connection: Keep-Alive开启了 HTTP Keep-Alive 机制后, 连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接,一直持续到客户端或服务器端提出断开连接。
Content-Type 字段
Content-Type 字段用于服务器回应时,告诉客户端,本次数据是什么格式。
Content-Type: text/html; Charset=utf-8上面的类型表明,发送的是网页,而且编码是UTF-8。 客户端请求的时候,可以使用 Accept 字段声明自己可以接受哪些数据格式。
Accept: */* 上面代码中,客户端声明自己可以接受任何格式的数据。
Content-Encoding 字段
Content-Encoding 字段说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式
Content-Encoding: gzip表示服务器返回的数据采用了 gzip 方式压缩,告知客户端需要用此方式解压。 客户端在请求时,用 Accept-Encoding 字段说明自己可以接受哪些压缩方法。
Accept-Encoding: gzip, deflateGET 与 POST
GET 和 POST 有什么区别?
根据 RFC 规范,GET 的语义是从服务器获取指定的资源,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)。 根据 RFC 规范,POST 的语义是根据请求负荷(报文body)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body 中,body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。
GET 和 POST 方法都是安全和幂等的吗?
- 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
- 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。
RFC 规范定义的语义来看:
- GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签。
- POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。
实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。比如:
- 可以用 GET 方法实现新增或删除数据的请求,这样实现的 GET 方法自然就不是安全和幂等。
- 可以用 POST 方法实现查询数据的请求,这样实现的 POST 方法自然就是安全和幂等。 :::tips RFC 规范并没有规定 GET 请求不能带 body 的。理论上,任何请求都可以带 body 的。只是因为 RFC 规范定义的 GET 请求是获取资源,所以根据这个语义不需要用到 body。 ::: :::tips 另外,URL 中的查询参数也不是 GET 所独有的,POST 请求的 URL 中也可以有参数的。 :::
HTTP 缓存技术
HTTP 缓存有哪些实现方式?
HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。
什么是强制缓存?
只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。 利用下面这两个 HTTP 响应头部字段实现的,它们都表示资源在客户端缓存的有效期:
- Cache-Control, 是一个相对时间;
- Expires,是一个绝对时间;
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
具体的实现流程如下:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
- 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
- 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
什么是协商缓存?
协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。比如某些请求的响应码是 304,就是告诉浏览器可以使用本地缓存的资源 协商缓存可以基于两种头部来实现。
第一种:请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现
- 响应头部中的 Last-Modified:标示这个响应资源的最后修改时间;
- 请求头部中的 If-Modified-Since:当资源过期了,发现响应头中具有 Last-Modified 声明,则再次发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。
第二种:请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段:
- 响应头部中 Etag:唯一标识响应资源;
- 请求头部中的 If-None-Match:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。
Etag 的优先级更高 因为 ETag 主要能解决 Last-Modified 几个比较难以解决的问题
- 在没有修改文件内容情况下文件的最后修改时间可能也会改变
- 可能有些文件是在秒级以内修改的,If-Modified-Since 能检查到的粒度是秒级的
- 有些服务器不能精确获取文件的最后修改时间。 :::tips 协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。 :::
使用 ETag 字段实现的协商缓存的过程:
- 浏览器第一次请求访问服务器资源时,服务器会在返回同时,在 Response 头部加上 ETag 唯一标识,标识的值是根据当前请求的资源生成的;
- 当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:
- 如果没有过期,则直接使用本地缓存;
- 如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
- 服务器再次收到请求后,会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较:
- 如果值相等,则返回 304 Not Modified,不会返回资源;
- 如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的 ETag 唯一标识;
- 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。
HTTP1.1 特性
HTTP/1.1 的优点有哪些?
简单
HTTP 基本的报文格式就是 header + body,头部信息也是 key-value 简单文本的形式
灵活和易于扩展
HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充。 同时 HTTP 由于是工作在应用层( OSI 第七层),则它下层可以随意变化,比如:
- HTTPS 就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层;
- HTTP/1.1 和 HTTP/2.0 传输协议使用的是 TCP 协议,而到了 HTTP/3.0 传输协议改用了 UDP 协议。
应用广泛和跨平台
互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,同时天然具有跨平台的优越性。
HTTP/1.1 的缺点有哪些?
无状态双刃剑
好处,服务器不会去记忆 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担 坏处,既然服务器没有记忆能力,它在完成有关联性的操作时会非常麻烦。
例如登录->添加购物车->下单->结算->支付,这系列操作都要知道用户的身份才行。但服务器不知道这些请求是有关联的,每次都要问一遍身份信息。
无状态的问题,解法方案有很多种,其中比较简单的方式用 Cookie 技术。 Cookie 通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。
明文传输双刃剑
明文意味着在传输过程中的信息,是可方便阅读的,比如 Wireshark 抓包都可以直接肉眼查看,为我们调试工作带了极大的便利性。 但是这正是这样,HTTP 的所有信息都暴露在了光天化日下,相当于信息裸奔。
不安全
- 通信使用明文(不加密),内容可能会被窃听。
- 不验证通信方的身份,因此有可能遭遇伪装。
- 无法证明报文的完整性,所以有可能已遭篡改。
HTTP 的安全问题,可以用 HTTPS 的方式解决,也就是通过引入 SSL/TLS 层。
HTTP/1.1 的性能如何?
长连接
早期 HTTP/1.0 性能上的一个很大的问题,那就是每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无谓的 TCP 连接建立和断开,增加了通信开销。 为了解决上述 TCP 连接问题,HTTP/1.1 提出了长连接的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。
管道网络传输
HTTP/1.1 采用了长连接的方式,这使得管道(pipeline)网络传输成为了可能。 即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。 但是服务器必须按照接收请求的顺序发送对这些管道化请求的响应。 如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。
:::tips 所以,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。 :::
实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持 原因:
- 不可靠性:由于网络环境的不确定性,服务器或代理可能无法正确处理和回复管道中的请求。这样就会导致部分请求失败,进而影响整体的用户体验。
- 兼容性:一些网络设备、代理服务器和应用程序并不支持HTTP管道。当在这些环境中使用管道时,可能会导致意外的问题。
- 未全面实现:虽然HTTP/1.1支持管道,但并不是所有的服务器都完全实现了该功能。其中一些服务器只支持部分管道请求,或者在某些情况下禁用了管道。
队头阻塞
「请求 - 应答」的模式会造成 HTTP 的性能问题。 因为当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一同被阻塞了,会招致客户端一直请求不到数据,这也就是「队头阻塞」。
HTTP 与 HTTPS
HTTP 与 HTTPS 有哪些区别?
- HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
- HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
- 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。
HTTPS 解决了 HTTP 的哪些问题?
- 混合加密的方式实现信息的机密性,解决了窃听的风险。
- 摘要算法的方式来实现完整性,它能够为数据生成独一无二的「指纹」,指纹用于校验数据的完整性,解决了篡改的风险。
- 将服务器公钥放入到数字证书中,解决了冒充的风险。
混合加密
HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
- 在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密。
- 在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据。
摘要算法 + 数字签名
计算机里会用摘要算法(哈希函数)来计算出内容的哈希值,也就是内容的「指纹」,这个哈希值是唯一的,且无法通过哈希值推导出内容。 通过哈希算法可以确保内容不会被篡改,但是并不能保证「内容 + 哈希值」不会被中间人替换,因为这里缺少对客户端收到的消息是否来源于服务端的证明。 为了避免这种情况,计算机里会用非对称加密算法来解决,共有两个密钥:
- 一个是公钥,这个是可以公开给所有人的;
- 一个是私钥,这个必须由本人管理,不可泄露。
这两个密钥可以双向加解密的 一般不会用非对称加密来加密实际的传输内容,因为非对称加密的计算比较耗费性能的。 所以非对称加密的用途主要在于通过「私钥加密,公钥解密」的方式,来确认消息的身份,我们常说的数字签名算法,就是用的是这种方式,不过私钥加密内容不是内容本身,而是对内容的哈希值加密。 私钥是由服务端保管,然后服务端会向客户端颁发对应的公钥。如果客户端收到的信息,能被公钥解密,就说明该消息是由服务器发送的
数字证书
前面知道:
- 可以通过哈希算法来保证消息的完整性;
- 可以通过数字签名来保证消息的来源可靠性(能确认消息是由持有私钥的一方发送的);
但是这还远远不够,还缺少身份验证的环节,万一公钥是被伪造的呢? 所以需要通过了一个权威的机构来证明身份,这个权威的机构就是 CA (数字证书认证机构) 数字证书的工作流程: :::tips
- 服务器把自己的公钥注册到CA
- CA用自己的私钥将服务器的公钥进行数字签名,并颁发数字证书
- 客户端拿到了数字证书,通过CA的公钥进行解密验证真实性(CA公钥内置在系统或浏览器中)
- 客户端通过解密获得的服务端公钥加密报文
- 服务端通过自己的私钥对报文解密 :::
HTTPS 是如何建立连接的?其间交互了什么?
SSL/TLS 协议基本流程:
- 客户端向服务器索要并验证服务器的公钥。
- 双方协商生产「会话秘钥」。
- 双方采用「会话秘钥」进行加密通信。
前两步也就是 SSL/TLS 的建立过程,也就是 TLS 握手阶段。 TLS 的「握手阶段」涉及四次通信,使用不同的密钥交换算法,TLS 握手流程也会不一样的,现在常用的密钥交换算法有两种:RSA 算法(opens new window)和 ECDHE 算法(opens new window)。 TLS 协议建立的详细流程:
ClientHello
首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。向服务器发送以下信息: (1)客户端支持的 TLS 协议版本,如 TLS 1.2 版本。 (2)客户端生产的随机数(Client Random),后面用于生成「会话秘钥」条件之一。 (3)客户端支持的密码套件列表,如 RSA 加密算法。
SeverHello
服务器收到客户端请求后,向客户端发出响应,也就是 SeverHello。服务器回应的内容有如下内容: (1)确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。 (2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。 (3)确认的密码套件列表,如 RSA 加密算法。 (4)服务器的数字证书。
客户端回应
客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA 公钥,确认服务器的数字证书的真实性。 如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息: (1)一个随机数(pre-master key)。该随机数会被服务器公钥加密。 (2)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。 (3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。 上面第一项的随机数是整个握手阶段的第三个随机数,会发给服务端,所以这个随机数客户端和服务端都是一样的。 服务器和客户端有了这三个随机数(Client Random、Server Random、pre-master key),接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
服务器的最后回应
服务器收到客户端的第三个随机数(pre-master key)之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。 然后,向客户端发送最后的信息: (1)加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。 (2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。 至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。
HTTPS 的应用数据是如何保证完整性的?
TLS 在实现上分为握手协议和记录协议两层:
- TLS 握手协议就是 TLS 四次握手的过程,负责协商加密算法和生成对称密钥,后续用此密钥来保护应用程序数据(即 HTTP 数据);
- TLS 记录协议负责保护应用程序数据并验证其完整性和来源,所以对 HTTP 数据加密是使用记录协议;
具体过程:
- 首先,消息被分割成多个较短的片段,然后分别对每个片段进行压缩。
- 接下来,经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),为了保证完整性,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。
- 再接下来,经过压缩的片段再加上消息认证码会一起通过对称密码进行加密。
- 最后,上述经过加密的数据再加上由数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。
记录协议完成后,最终的报文数据将传递到传输控制协议 (TCP) 层进行传输。
HTTPS 一定安全可靠吗?
比如:客户端通过浏览器向服务端发起 HTTPS 请求时,被「假基站」转发到了一个「中间人服务器」,于是客户端是和「中间人服务器」完成了 TLS 握手,然后这个「中间人服务器」再与真正的服务端完成 TLS 握手。 要发生这种场景是有前提的,前提是用户点击接受了中间人服务器的证书。 中间人服务器与客户端在 TLS 握手过程中,实际上发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器(客户端)识别出是非法的,于是就会提醒用户该证书存在问题。
如何避免被中间人抓取数据?
可以通过 HTTPS 双向认证来避免这种问题。 一般我们的 HTTPS 是单向认证,客户端只会验证了服务端的身份,但是服务端并不会验证客户端的身份。如果用了双向认证方式,服务端也会验证客户端的身份。 服务端一旦验证到请求自己的客户端为不可信任的,服务端就拒绝继续通信,客户端如果发现服务端为不可信任的,那么也中止通信。
HTTP/1.1、HTTP/2、HTTP/3 演变
HTTP/1.1 相比 HTTP/1.0 提高了什么性能?
HTTP/1.1 相比 HTTP/1.0 性能上的改进:
- 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
- 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
但 HTTP/1.1 还是有性能瓶颈:
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
- 发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
- 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
HTTP/2 做了什么优化?
HTTP/2 相比 HTTP/1.1 性能上的改进:
- 头部压缩
- 二进制格式
- 并发传输
- 服务器主动推送资源
头部压缩
HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。 这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号
二进制格式
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)。
并发传输
HTTP/2 引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。 针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应。
服务器推送
HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。 客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。 :::info HTTP/2 有什么缺陷? ::: HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。 HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。 一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
HTTP/3 做了哪些优化?
HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP。 UDP 发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。但是UDP 是不可靠传输的,所以利用基于 UDP 的 QUIC 协议 实现类似 TCP 的可靠性传输。 QUIC 3 个特点:
- 无队头阻塞
- 更快的连接建立
- 连接迁移
无队头阻塞
QUIC 协议也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。 QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。
更快的连接建立
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。 HTTP/3 在传输数据前虽然需要 QUIC 协议握手,但是这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。 但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商. 甚至,在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。
连接迁移
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。 当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。 **