HTTP 演进史

date
Jul 31, 2023
slug
http-history-of-evolution
status
Published
tags
系统和网络
summary
梳理 HTTP 的发展演进的过程及每个版本的变化
type
Post
HTTP 的发展要追溯到万维网的发明,1989 年,当时在 CERN 工作的 Tim Berners-Lee 博士写了一份关于建立一个通过网络传输超文本系统的报告。这个系统起初被命名为 Mesh,在随后的 1990 年项目实施期间被更名为万维网(World Wide Web)。
万维网在现有的 TCP 和 IP 协议基础之上建立,由四个部分组成:
  • 一个用来表示超文本文档的文本格式,超文本标记语言(HTML)。
  • 一个用来交换超文本文档的简单协议,超文本传输协议(HTTP)。
  • 一个显示(以及编辑)超文本文档的客户端,即网络浏览器。第一个网络浏览器被称为 WorldWideWeb
  • 一个服务器用于提供可访问的文档,即 httpd 的前身。
 

HTTP/0.9 1991年

最初的 HTTP 协议并没有版本号,0.9 实际上是为了跟后续的 1.0 版本作区分。总的来说 0.9 版本十分简陋,功能单一。
特点:
  • 只支持 GET 请求,在其后面跟上目标资源的路径
  • 没有 HTTP 头部
不足:
  • 因为没有 HTTP 头部,所以除了文本类型无法区分和传输其他类型
  • 没有状态码和错误码,一旦出现问题,只能返回一个固定的错误页面
一个典型的请求:
一个典型的响应:
 

HTTP/1.0 1996年

在 0.9 基础上做了扩展,支持传输更多类型的内容。
特点:
  • 在请求中明确了版本号
  • 增加响应状态码
  • 增加 HTTP 头,使传输资源更加灵活,如:
    • Content-type:通过指定不同的 MIME type,表明资源的格式,如 text/html、text/css、image/png、application/javascript、application/octet-stream 等
    • Accept-Encoding / Content-Encoding:表明客户端支持的压缩类型和响应中使用的压缩类型
不足:
  • 每个 TCP 连接只能发送一个请求,造成了连接效率低下。后续在请求和响应头中增加了一个非标准的 Connection: keep-alive,告知双方请求可以复用同一条 TCP 连接而不是每次请求响应后都关闭连接。不过由于不是标准字段,不同实现的行为可能不一致,因此没有从根本上解决。
一个典型的文本类型的请求和响应
典型的图片类型的请求和响应
 

HTTP/1.1 1997年

HTTP/1.1 消除了大量歧义内容并引入了多项改进
特点:
  • 持久连接复用成为默认,不需要声明 Connection: keep-alive,想要关闭可以在响应中增加 Connection: close 声明
  • pipelining 管道,可以一次性发送多个请求,避免了此前一次只能发送一个请求的情况,不过响应需要按照发送的顺序来回复。
  • 增加几种 HTTP 方法
  • 增加分块传输的流模式,响应头携带 Transfer-Encoding:chunked 并在每一个分块增加 Content-Length 表明当前块长度,并在所有内容传输完成的最后追加一个 Content-Length:0 表明传输完成。
  • 增加 range 、Content-Range 相关头,用来支持续传和分段请求。
不足:
  • 存在 Head of line blocking 队头阻塞问题,即同一个连接中有一个请求阻塞了,后续所有请求都将被阻塞。
通过同一个连接实现的请求响应:
 

HTTP/2 2015年

说到 HTTP/2 就不得不提 SPDY,SPDY 是 Google 开发的一个开放网络协议,旨在通过减少延迟来加快网页加载速度。SPDY 在许多方面是 HTTP/2 的先驱,并直接影响了 HTTP/2 的设计和实现。
特点:
  • 兼容 HTTP/1.1
  • 全部使用二进制传输,传输效率更高
  • 引入了多路复用技术,结合 stream 流和 frame 帧的概念,解决了队头阻塞问题。
    • HTTP/1.1 并发请求数量针对每个域名都有限制,而 HTTP/2 可以使用一个连接发送不同业务请求,每个请求通过 streamID 区分,每个 stream 中可以传输多个 message,每个 message 由多个 frame 组成,frame 就是传输的最小单位。
    • 发送时不同请求通过同一个连接并发发送,服务端接到后处理一个就可以返回一个,响应时根据唯一的 streamID 组装响应内容即可。如此避免了 HTTP/1.1 的队头阻塞问题。
  • 支持首部压缩,在同一个连接的不同请求中大部分 header 都是重复的,HTTP/2 使用 HPACK 算法对 header 进行压缩。压缩涉及到很多细节方法
    • 静态表将常见的首部字段都编了号,发送这些首部时,其 key 名直接发送对应的编号即可。
    • 动态表一开始是空的,将随着请求过程中出现的一些不在静态表中的首部填充进去,获得新的编号。
    • 不论静态表还是动态表,其 value 如果是变化的,则使用哈夫曼编码压缩。
  • 支持服务器推送,请求一个网页的同时,还可能需要网页中依赖的静态资源,此时服务端就可以推送这些资源,无须等待客户端主动请求。
不足:
  • HTTP/2 作为应用层协议实际上已经比较完美了,但由于基于 TCP,所以避免不了受 TCP 的特性所影响导致的性能瓶颈,例如三次握手和四次挥手,慢启动和拥塞控制等等,严格来说这些不能算是问题,是设计理念不同的必然结果。
  • 要求 TLS 加密,也增加了建立连接的耗时
  • 移动设备可能频繁切换信号源,导致 socket 四元组发生变化,连接被迫失效,需要重连。
首部压缩的例子:
静态表包括了一些常用的首部字段名称和值。例如:
  • 索引 1::authority
  • 索引 2::method: GET
  • ...
  • 索引 40:content-type: application/json
动态表最初是空的,但会随着连接的使用而填充。假设我们有一个请求,其中包括一个不常见的首部字段:
  • custom-header: example-value
这个字段可以添加到动态表中,并分配一个索引,例如索引 41。
我们可以使用静态表和动态表中的索引来表示这些字段:
  • :method: GET 在静态表中,索引为 2
  • :authority: www.example.com 可以使用索引 1 和哈夫曼编码来压缩 www.example.com
  • custom-header: example-value 在动态表中,索引为 41
最终的首部压缩结果:
  • 索引 2
  • 索引 1 + 哈夫曼编码的 www.example.com
  • 索引 41 + 哈夫曼编码的 www.example.com
 

HTTP/3 2018年提案 2020年正式

HTTP/3 是基于 QUIC 协议的 HTTP 版本,致力于进一步提高性能。
特点:
  • 在 UDP 基础上构建,满足可靠数据传输的同时,相较 TCP 有很大性能提升。
  • 基于 TLS 1.3 快速建立安全连接
  • 连接迁移,不使用 TCP 四元组而是使用一个 64位 ID来标识连接,当发生网络环境的变化时,可以不需要重新建立连接。
不过 HTTP/3 的改动相对来说是最大的,目前看推广的覆盖率并没有之前的速度快。
 
参考:
HTTP协议演进与各版本特性:https://www.biaodianfu.com/http.html
 

© 菜皮 2020 - 2024