Skip to content

HTTP 版本

HTTP/0.9

最初版本的 HTTP 协议并没有版本号,后来它的版本号被定位在 0.9 以区分后来的版本。HTTP/0.9 极其简单:请求由单行指令构成,以唯一可用方法 GET 开头,其后跟目标资源的路径(一旦连接到服务器,协议、服务器、端口号这些都不是必须的)。

http
GET /mypage.html

响应也极其简单的:只包含响应文档本身。

html
<html>
  这是一个非常简单的 HTML 页面
</html>

跟后来的版本不同,HTTP/0.9 的响应内容并不包含 HTTP 头。这意味着只有 HTML 文件可以传送,无法传输其他类型的文件。也没有状态码或错误代码。

HTTP/1.0

和 HTTP/0.9 区别如下

  • 协议版本信息现在会随着每个请求发送
  • 状态码会在响应开始时发送,使浏览器能了解请求执行成功或失败,并相应调整行为(如更新或使用本地缓存)。
  • 引入了 HTTP 标头的概念,无论是对于请求还是响应,允许传输元数据,使协议变得非常灵活,更具扩展性。
  • 在新 HTTP 标头的帮助下,具备了传输除纯文本 HTML 文件以外其他类型文档的能力(凭借 Content-Type 标头)。
http
GET /mypage.html HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)

200 OK
Date: Tue, 15 Nov 1994 08:12:31 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/html
<HTML>
  一个包含图片的页面
  <IMG SRC="/myimage.gif">
</HTML>

HTTP/1.1

HTTP/1.1 消除了大量歧义内容并引入了多项改进:

  • 引入了持久连接,即 TCP 连接默认不关闭,可以被多个请求复用

  • 引入了管线化机制,即在同一个 TCP 连接里面,客户端可以同时发送多个请求,而不用等待响应,但是 http response 的顺序必须要与 http request 的顺序一致,这是 http/1.1 的队头阻塞问题,并且由于实现比较复杂,并不是所有的服务器都支持正确的管线化

    firefox 对同一域名最多启用 6 条 TCP 连接(Chrome 似乎也是)。现在很多网站将静态资源部署到多个 CDN 服务器上,就是为了加上不同的域名来加快静态资源加载。

  • 采用分块传输编码,对于一些很耗时的动态操作,服务器需要等到所有操作完成,才能发送数据,显然这样的效率不高。更好的处理方法是,产生一块数据,就发送一块,采用"流模式"(stream)取代"缓存模式"(buffer)

HTTP/2.0

HTTP2.0 的特点是:在不改动 HTTP 语义、方法、状态码、URI 及首部字段的情况下,大幅度提高了 web 性能。

实现上面所说的特点依赖于下面的措施:

  • 二进制分帧:HTTP/2 将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。这是 HTTP/2 实现多路复用的基础。
  • 多路复用:即在一个 TCP 连接中存在多个流,可以同时发送多个请求。在一端这些帧乱序发送,到对端后再根据每个帧首部的流标识符重新组装。通过该技术,可以避免 HTTP 旧版本的队头阻塞问题,极大提高传输性能。
  • 首部压缩:HTTP/2 使用 HPACK 算法对首部字段进行压缩,减少了首部字段的大小,降低了开销。
  • 服务器推送:某些资源客户端是一定会请求的,这时可以采取服务端 push 的技术,提前给客户端推送必要的资源,就可以相对减少一点延迟时间。不过……似乎很少有服务器这么干,所以这项功能被有些浏览器 ban 掉了,比如 Firefox

TIP

HTTP/2 的首部压缩主要通过 HPACK 算法实现,该算法旨在减少 HTTP/2 请求和响应中首部(Header)的大小,从而提高网络传输效率和减少延迟。HPACK 算法的实现主要依赖于三个核心机制:静态表(Static Table)、动态表(Dynamic Table)和霍夫曼编码(Huffman Encoding)。下面详细介绍这三个机制是如何工作的。

  1. 静态表

    静态表是 HPACK 算法预定义的一组常用 HTTP 首部字段及其值的列表。这个表包含了 61 个常见的 HTTP 首部字段,一些字段还预定义了值。 当编码和解码 HTTP 首部时,如果首部字段在静态表中存在,就可以直接使用静态表中的索引值来代替整个字段名和值,从而减少了传输的数据量。

  2. 动态表

    动态表在通信开始时是空的,随着通信的进行,动态表会根据传输的首部字段动态更新。当有新的首部字段出现时,它们会被添加到动态表中,以便后续引用。 动态表有一个大小限制,当表满时,最早添加的条目会被新条目替换(先进先出原则)。这样,动态表始终保持了最近使用的首部字段,以便高效引用。

  3. 霍夫曼编码

    霍夫曼编码是一种变长编码技术,通过使用不同长度的编码来代替原始字符,从而达到压缩数据的目的。在 HPACK 中,霍夫曼编码用于对首部字段的名称和值进行编码。 使用静态霍夫曼码表,可以将首部字段的名称和值编码为更短的二进制序列,进一步减少了传输的数据量。

NOTE

  1. 创建一个含有所字符及其频率的集合或队列。
  2. 根据字符的频率,将集合或队列中的元素建立一个最小堆(优先队列)数据结构。
  3. 从堆中取出频率最低的两个元素(即堆顶两个元素),然后生成新的节点作为这两个元素的父节点,新的节点的频率是这两个元素的频率之和。然后将新的父节点放回堆中。
  4. 重复第三步直到堆中只剩下一个元素,这个元素即为哈夫曼树的根节点。

HTTP/3.0

HTTP/3 推出是为了解决 HTTP/2 的一些问题

  1. 队头阻塞,HTTP/2 采用了多路复用技术,但是由于 TCP 的特性,导致了队头阻塞问题

    NOTE

    HTTP/2.0 也部分解决了队头阻塞的问题,而 HTTP/3.0 也解决了,这是不是很奇怪,但其实只是因为他们解决的层面不同

    HTTP/2 解决了 HTTP/1.1 中因为 HTTP 请求序列化造成的队头阻塞问题。在 HTTP/1.1 中,浏览器必须等待前一个请求处理完毕并返回响应后,才能发送下一个请求,这就导致了所谓的队头阻塞问题。HTTP/2 引入了帧、消息和数据流的概念,通过多路复用技术,允许单个连接上可以并行交错的请求和响应,从而解决了这个问题。

    然而,HTTP/2 虽然解决了 HTTP 层面的队头阻塞问题,但是并没有解决 TCP 层面的队头阻塞问题。因为 TCP 是一个按序传输的协议,如果其中一个数据包丢失了,那么后续的数据包就必须等待丢失的数据包被重新传输后才能继续传输,这就导致了所谓的 TCP 层面的队头阻塞问题。

    HTTP/3 解决的就是这个问题。HTTP/3 放弃了 TCP 协议,转而使用 QUIC 协议(基于 UDP 的传输层协议)作为传输层协议,这使得即使有数据包丢失,也不会影响后续数据包的传输,从而解决了 TCP 层面的队头阻塞问题。

  2. TCP 与 TLS 的握手时延迟,TCP 三次握手需要 1.5 个 RTT,TLS 根据版本不同有不同的 RTT,TLS/1.3 甚至可以做到 1/0 RTT 的效果,但是这里放一个 TODO 吧,以后再补充

  3. 网络迁移需要重新连接,面对移动设备的网络切换,TCP 连接需要重新建立(因为 TCP 自身四元组的限制),而 QUIC 协议可以在不同网络之间无缝切换

参考