HTTP的迭代

64 阅读9分钟

起源

1991年发布的http/0.9http协议的最早的版本,其仅支持get方法,且只用于客户端获取html文档

这和浏览器最开始的样子:在地址栏输入url,通过get方法获得目标页面相吻合,这也代表了浏览器的最基本的用途。

我们可以考虑一下HTTPHyperText transfer protocol超文本传输协议的命名与HTTP诞生的关系

超文本HyperText

最初的HTTP协议只为获取传输HTML文档,而HTML的全称是HyperText Markup Language

HyperText的构想,是文本中有一系列超链接,可以指向其他文本、图像、视频,通过点击可以在不同的信息之间跳转;而html就是这个思想的载体和实际产物

显然在今天,html已超脱出HyperText的最初构想,衍生出众多远比超文本更丰富的强大功能,或许html这个名称已经不能概括他的作用,更像是说明了其起源

而我们今天所说的HTTP同样如此

HTTP是乳名

在最初的构想中,HTTP只用于客户端和服务器之间通过get请求和相应传输HTML文档,因此其被简单的概括为超文本传输协议

HTML相同,显然今天的HTTP协议远比只能传输HTML文档的雏形更加强大,其不仅能传输css,js,图片,视频,更在XHR/Fetch的加入下,能有更新、添加、删除等用途,详见:通过Content-Type,理解多样的HTTP协议

所以和HTML相同,HTTP这个名字也更像是说明其起源的乳名,而远远不能概括其全部

借助理解其起源,我们能理解HTTPHTML的命名,大概率是人们习惯性的称呼中保留下来的乳名

HTTP相同,URL也有相似的情况:URL的全称Uniform Resource Locator统一资源标识符,与其常见的用途:请求获取网络上的资源相同。比如在地址栏输入url后获取到html文件并通过浏览器加载。但是在今天,随着更多HTTP方法的加入,如post,一个URL还可能代表着更新、删除服务器上的资源,这也超脱了其网络上文件标识这个最基础的概念,因此URL同样也像是一个乳名

HTTP/1.0和HTTP1.1

显然,只有get方法的单行协议的http/0.9应用场景十分有限,因此1.0作为一个对新版本http的探索的方式诞生了,但是1.0版本仅仅作为一种尝试,其并未正式加入到标准中。

单行协议指0.9版本的http协议是以唯一的get方法开头,其后跟着url的单行指令构成,其响应也是单独的文件本身,非常简单

HTTP1.1最终作为一个标准化的协议发布,其发布时间仅仅比1.0版本晚了几个月

相比于HTTP0.9HTTP1.1进行了诸多方面的完善:

  1. 拓展了更多的HTTP方法,如post,head
  2. 加入请求头和响应头,这一改动大幅拓展了HTTP的适用性
  3. 加入了响应码
  4. 可以传递html文档以外的文件,其mime类型标注在headercontent-type
  5. 复用连接
  6. 管线化
  7. 响应分块
  8. HTTP缓存机制

HTTPS

最初的http应用于信任度较高的学术网络,因此是明文传输,直至后来http的应用范围急剧扩张,http的加密传输也迫在眉睫

在这种历史情境下,ssl/tls作为加密协议的https协议面世了

https/tls详解

不仅仅是传输文件

最初的http协议仅仅用作html文档的传输,后来header加入后,则允许传输除html之外的其他文件

后来为了让http可以用作远程操作,再2000年,一种叫做RESThttp的设计模式被设计出来,其全称是representational state transfer,实现了这个设计模式的Api叫做RESTful Api

RESTful Api规定了通过某些特定的http方法发送的请求,符合其指定的目的,如get用于获取,post用于创建新资源,put用于更新

注意!restful Api只是约定俗成的规范,而非硬性标准,例如开发者完全可以使用post方法来创建资源而不是更新资源,也完全可以在get方法中带有请求体去更新资源,这都是允许的。

连接的复用

tcp的连接建立需要三次握手,断开需要四次挥手,代价很大,如果一次http请求使用一次连接,效率很低;为什么是三次握手和四次挥手?todo

http1.1标准化了长连接技术,通过Connection:Keep-Alivehttp头部来控制多次请求复用同一个连接

可以通过Keep-Alive: timeout=30, max=100头部管理空闲超时和最大请求数

管线化

尽管复用了tcp连接来传输多个http请求,但是tcp请求的发送是按照请求->响应->继续发送请求的顺序进行的,如果前一个请求的响应返回慢,会阻碍下一个请求的发送;这个问题被称作是“队头堵塞”

管线化技术是处理这个问题的一次尝试,管线化允许下一个请求不需要等待上一个请求的响应返回就可以发送,但是响应此时仍需要按顺序处理,上一个请求的响应如果返回较慢,仍会影响后续响应的处理

因此管线化技术意义不大,已经被弃用

队头堵塞问题在http2的二进制分帧中被彻底解决

响应分块

响应分块允许服务器无需缓冲完整数据,无需通过Content-Length头部说明数据大小,动态将数据分成多个块传输给客户端

通过Transfer-Encoding: chunked头部表示使用分块技术

通过特定的数据指示数据结尾

但是由于http1.1中普遍存在的队头堵塞问题,响应分块技术在http2版本中有更好的多路复用来替代

存在请求分块,但是很少被使用,大文件上传常用multipart/form-data来做数据层面而非协议层面的分块

分片上传比multipart/form-data更加高效,todo上传实现的方案

http缓存机制

http缓存机制

host头部

HTTP请求头中的Host字段用于指定请求的目标服务器域名和端口号,是HTTP/1.1协议中必须包含的头部字段。

当多个域名共享同一IP地址时,服务器通过Host字段区分客户端请求的具体域名

这允许一个ip托管多个域名

HTTP2

http2.0相对于http1.1有诸多改进,如下:

  • 二进制分帧(核心优化)
  • 多路复用
  • 头部压缩
  • 服务器推送
  • 流优先级
  • 流量控制
  • 安全性增强

二进制分帧

将传输信息分成二进制帧进行传输,是实现多路复用等多个特性的基础

一个请求会被分成多个帧:

  1. HEADERS帧,包含头部信息,过大时,会通过CONTINUATION帧传输
  2. DATA帧,实际负载的数据,过大会被分成多个
  3. PRIORITYd帧,指定优先级
  4. RST_STREAM帧,强制结束stream
  5. SETTINGS帧,协商链接参数
  6. PUSH_PROMISE,实现服务器推送

每个帧都有通用的结构

  • 长度(3字节):帧负载的字节数(不含头部)。
  • 类型(1字节):标识帧类型(如0x1表示HEADERS)。
  • 标志位(1字节):控制标记(如END_HEADERS表示头部结束)。
  • 流ID(4字节):标识所属的流。

id标志该帧属于哪个stream

一个请求对应一个stream

一个http连接默认允许100个流并行

多路复用

http2.0的核心之一,解决了http1.1队头堵塞问题

由于二进制分帧的加入,不同的帧允许在TCP连接上交错传输,接收方根据streamId重新组装获得完整的请求

此时请求之间不会相互阻塞,因此解决了http队头堵塞问题

tcp队头堵塞仍然存在,这是超时重传机制决定的,但是http3quic协议解决了这个问题

头部压缩

针对二进制分帧中的HEADERS帧使和用于传输头部的CONTINUATION帧,使用HPACK算法进行压缩

压缩机制

  • 静态字典:预定义了61个常见头部字段(如method: GETstatus: 200),通过索引号直接引

  • 动态字典:动态维护连接中新增的头部字段(如自定义User-Agent),后续请求复用索引

  • 霍夫曼编码:对字符串值(如长URL)进一步压缩

因此后续只需要传递索引号即可,大小更小

服务器推送

http协议请求-响应的模式,无法实现服务器主动推送内容到浏览器,针对这一特性,实现的一种服务器推送机制

但是由于一些争议,现在主流的浏览器都禁止了服务器推送

流优先级

  • 每个流可以依赖其他流,作为其父流,并可以配置权重
  • 新流默认依赖根流,优先级默认是16
  • 根流是一个虚拟的流
  • 子流需要等待父流完成后才能进行
  • 同级流之间根据分配的权重分配资源
  • 同级流之间是并发进行的,只不过其分得的带宽资源不同

优先级控制主要由浏览器进行配置,往往不需要开发者手动配置

安全控制

HTTP2强制要求使用TLS1.2或者更高的版本进行加密

详见: https/tls详解

HTTP3

主要是传输层协议更新,传输层不再基于TCP协议,而是基于谷歌开发的新传输层协议QUIC

TODO:QUIC详解