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 头部,所以除了文本类型无法区分和传输其他类型
- 没有状态码和错误码,一旦出现问题,只能返回一个固定的错误页面
一个典型的请求:
代码语言:javascript复制
GET /mypage.html
一个典型的响应:
代码语言:javascript复制
<html>
这是一个非常简单的 HTML 页面
</html>
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 连接而不是每次请求响应后都关闭连接。不过由于不是标准字段,不同实现的行为可能不一致,因此没有从根本上解决。
一个典型的文本类型的请求和响应
代码语言:javascript复制
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>
典型的图片类型的请求和响应
代码语言:javascript复制
GET /myimage.gif HTTP/1.0
User-Agent: NCSA_Mosaic/2.0 (Windows 3.1)
200 OK
Date: Tue, 15 Nov 1994 08:12:32 GMT
Server: CERN/3.0 libwww/2.17
Content-Type: text/gif
(这里是图片内容)
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 队头阻塞问题,即同一个连接中有一个请求阻塞了,后续所有请求都将被阻塞。
通过同一个连接实现的请求响应:
代码语言:javascript复制
GET /zh-CN/docs/Glossary/Simple_header HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/zh-CN/docs/Glossary/Simple_header
200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 20 Jul 2016 10:55:30 GMT
Etag: "547fa7e369ef56031dd3bff2ace9fc0832eb251a"
Keep-Alive: timeout=5, max=1000
Last-Modified: Tue, 19 Jul 2016 00:59:33 GMT
Server: Apache
Transfer-Encoding: chunked
Vary: Cookie, Accept-Encoding
(content)
GET /static/img/header-background.png HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/zh-CN/docs/Glossary/Simple_header
200 OK
Age: 9578461
Cache-Control: public, max-age=315360000
Connection: keep-alive
Content-Length: 3077
Content-Type: image/png
Date: Thu, 31 Mar 2016 13:34:46 GMT
Last-Modified: Wed, 21 Oct 2015 18:27:50 GMT
Server: Apache
(image content of 3077 bytes)
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 的改动相对来说是最大的,目前看推广的覆盖率并没有之前的速度快。