一、大话HTTP协议-HTTP的前世今生

HTTP协议是我们的老朋友,它自身也是一路升级打怪走过来,挺不容易的,本文来说说HTTP的前世今生,加油!

一、Web的起源
在学习大名鼎鼎的HTTP协议之前,有必要先说下Web。在 IT 领域,Web 是 World Wide Web(万维网,一般简写为 WWW)的简称。WWW 可以让 Web 客户端(例如我们常用的浏览器,如 Chrome)访问 Web 服务器上的页面。 是一个由许多互相链接的超文本组成的系统,通过互联网访问。

在这个系统中,每个有用的事物,称为一样“资源”,并且由一个 URI(Uniform Resource Identifier 的缩写,表示“统一资源标识符”)标识。这些资源通过 HTTP(HyperText Transfer Protocol 的缩写,表示“超文本传输协议”)协议传送给用户,用户通过点击链接来获得资源。

注意,Internet 不等于 Web ,Web 是 Internet 的一部分,除了 Web,Internet 还包含其他服务:E-mail、FTP等服务。

我们在本系列第一篇文章中就提到了阿帕网,这是互联网的前身。后来,E-mail 等 Internet 的服务开始出现:1972年,e-mail 服务出现。最后,1991年终于诞生了Web。

Web 是谁发明的呢?那就是大名鼎鼎的万维网之父Tim Berbers Lee(蒂姆·伯纳斯·李)。

7月27日,在伦敦奥林匹克体育场举行的2012年伦敦奥运会开幕式上,一位英国科学家隆重登场,接受全场掌声,这个“感谢蒂姆”的场面惊动全球,成为开幕式的一个亮点。他就是互联网的发明者、被业界公认为“互联网之父”的英国人蒂姆•伯纳斯•李(Tim Berners-Lee)。在全世界的注目下,他在一台电脑前象征性地打出了一句话:“This is for Everyone”,含义是:互联网献给所有人。蒂姆•伯纳斯•李不仅被视为英国人的骄傲,他同样无可争辩地赢得了全世界的尊重。

在他之前,没有浏览器,没有 WWW,网络世界一片空白。1994 年,Tim Berners Lee 创立了著名的 W3C 组织,因为他觉得 Web 发展迅猛,需要有一个类似基金会或委员会的机构来规范,以达成全球统一标准。W3C 后来发明了一系列的语言和规范:HTML,CSS,XML 等。现在的 HTML5 也是他们规定的。W3C 最重要的工作是发展 Web 规范(被称为 recommendations,表示“推荐”),这些规范描述了 Web 的通信协议(比如 HTML 和 XHTML)和其他的构建模块。

二、浏览器背后的故事
我们知道,依靠Web服务,我们才有了如今丰富多彩的网页,Web 的操作主要基于 HTTP 协议,它也是 OSI 第 7 层(应用层)协议。HTTP 是 HyperText Transfer Protocol 的缩写,表示“超文本传输协议”,所有的 Web 文件都必须遵守这个标准。

设计 HTTP 最初的目的是为了提供一种发布和接收 HTML(超文本标记语言) 页面的方法。我们一般浏览网页,看到的网页地址都是 http:// 这样开头的,后面接域名。例如:http://www.google.com 。

HTTP:超文本传输协议,是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端浏览器。

简单来说,就是传输超文本的一种协议,什么是超文本呢?

超文本大概就是,不仅仅是文字,还有多媒体:视频、图片、动画等。还有就是有超链接,点击一个按钮可以调到另一个页面去,每个页面都可以这样跳来跳去,就形成一个网络。

HTTP 基于 TCP 协议。当我们在浏览器的地址栏中输入一个 URL(Uniform Resource Locator 的缩写,表示“统一资源定位符”),按下回车后浏览器会分析出 URL 上面的域名,然后通过 DNS 服务器查询出域名映射的 IP 地址,浏览器根据查询到的 IP 地址与 Web 服务器进行通信,而通信的协议就是 HTTP 协议。大致流程可以用下图来表示:

我们知道,网页是由 HTML 标签组成的。浏览器拿到响应数据后按照一定的规则进行解析和页面渲染,从而让我们看到了谷歌首页。

三、HTTP协议的前世今生

3.1 HTTP/0.9版本
HTTP 协议最早是由我们的 Tim 大神提出的,早在1989年,Tim 还在 CERN 担任研究员的时候,提出了一个提案,此提案描绘了他对万维网最初的设想,他希望在 CERN 内部建立一个网络,满足内部信息交换的需求。一开始 CERN 并没有重视。到了1990年,Tim大神完成了3项 WWW 构建技术,分别是:

URI,统一资源标识符,作为互联网上的唯一标识。
HTML,超文本标记语言,描述超文本。
HTTP ,超文本传输协议,传输超文本。
1990年11月,CERN 成功研制出世界上第一台 Web 服务器和 Web 浏览器。1991年,Tim 发表了一篇 HTTP 协议文章,后来被称为 HTTP/0.9。 实际上这篇文章比较简短,很难被称为标准的协议,只是 Tim 个人的作品,解释了程序的实现过程。不过鉴于 Tim 对互联网的开创性贡献,后人将其HTTP的文章作为HTTP的第一版标准。

最早的版本协议非常简单,特性只有如下:

内容非常简单,只有一个命令GET
没有HEADER等描述数据的信息
服务器发送完毕,就关闭TCP连接(一个HTTP请求在一个TCP连接中完成)
协议规定,服务器只能回应HTML格式的字符串,不能回应别的格式。也没有状态码来区分正确和错误消息。
比如发起一个GET请求:

GET /index.html

服务器响应:


Hello World

1
2
3
那时候是互联网初期,计算机的处理能力包括网速等等都很弱,所以 HTTP 也逃脱不了那个时代的约束,因此设计的非常简单,而且也是纯文本格式。

李老当时的想法是文档存在服务器里面,我们只需要从服务器获取文档,因此只有 “GET”,也不需要啥请求头,并且拿完了就结束了,因此请求响应之后连接就断了。

这就是为什么 HTTP 设计为文本协议,并且一开始只有“GET”、响应之后连接就断了的原因了。

在我们现在看来这协议太简陋了,但是在当时这是互联网发展的一大步!一个东西从无到有是最困难的。

这时候的 HTTP 还没有版本号的,之所以称之为 HTTP / 0.9 是后人加上去了,为了区别之后的版本。

3.2 HTTP/1.0版本
人们的需求是无止尽的,随着图像和音频的发展,浏览器也在不断的进步予以支持。需求促使添加各种特性来满足用户的需求,经过了一系列的草案 HTTP/1.0 于 1996 年正式发布。HTTP 1.0 扩展了0.9版,其中主要增加了几个变化:

在请求中加入了HTTP版本号,如:GET /coolshell/index.html HTTP/1.0
HTTP 开始有 header了,不管是request还是response 都有header了。
增加了HTTP Status Code 标识相关的状态码。
还有 Content-Type 可以传输其它的文件了。
可以看到引入了新的方法,填充了操作的语义,像 HEAD 还可以只拿元信息不必传输全部内容,提高某些场景下的效率。

引入的响应状态码让请求方可以得知服务端的情况,可以区分请求出错的原因,不会一头雾水。

引入了头部,使得请求和响应更加的灵活,把控制数据和业务实体进行了拆分,也是一种解耦。

新增了版本号表明这是一种工程化的象征,说明走上了正途,毕竟没版本号无法管理。

引入了 Content-Type,支持传输不同类型的数据,丰富了协议的载体,充实了用户的眼球。

举个例子,请求如下:

GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*
1
2
3
可以看到,这个格式与0.9版有很大变化。
第一行是请求命令,必须在尾部添加协议版本(HTTP/1.0)。后面就是多行头信息,描述客户端的情况。

客户端请求的时候,可以使用Accept字段声明自己可以接受哪些数据格式。上面代码中,客户端声明自己可以接受任何格式的数据。

服务器的回应如下:

HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84


Hello World

1
2
3
4
5
6
7
8
9
10
回应的格式是”头信息 + 一个空行(\r\n) + 数据”。其中,第一行是”协议版本 + 状态码(status code) + 状态描述”。

关于字符的编码,1.0版规定,头信息必须是 ASCII 码,后面的数据可以是任何格式。因此,服务器回应的时候,必须告诉客户端,数据是什么格式,这就是Content-Type字段的作用。

3.2 HTTP/1.1版本
HTTP/1.1 主要解决了HTTP 1.0的网络性能的问题,以及增加了一些新的东西:

新增了连接管理即 keepalive ,允许HTTP重用TCP链接。重用TCP链接可以省了每次请求都要在广域网上进行的TCP的三次握手的巨大开销。
支持 pipeline,无需等待前面的请求响应,即可发送第二次请求。可以减少整体的响应时间。(注:非幂等的POST 方法或是有依赖的请求是不能被pipeline化的)
允许响应数据分块(chunked),即响应的时候不标明Content-Length,客户端就无法断开连接,直到收到服务端的 EOF ,利于传输大文件。
新增缓存的控制和管理。
协议头注增加了 Language, Encoding, Type 等等头,让客户端可以跟服务器端进行更多的协商。
还正式加入了一个很重要的头—— HOST,这样的话,服务器就知道你要请求哪个网站了。因为可以有多个域名解析到同一个IP上,要区分用户是请求的哪个域名,就需要在HTTP的协议中加入域名的信息,而不是被DNS转换过的IP信息。

3.3 HTTP/2版本

虽然 HTTP/1.1 已经开始变成应用层通讯协议的一等公民了,但是还是有性能问题。在2010年时,Google 就在搞一个实验型的协议,这个协议叫SPDY,这个协议成为了HTTP/2的基础(也可以说成HTTP/2就是SPDY的复刻),HTTP/2 是2015年推出的,其发布后,Google 宣布移除对SPDY的支持,拥抱标准的 HTTP/2。主要新的特性有:

是二进制协议,不再是纯文本。
支持一个 TCP 连接发起多请求,移除了 pipeline。
利用 HPACK 压缩头部,减少数据传输量。
允许服务端主动推送数据。
从文本到二进制其实简化了整齐的复杂性,解析数据的开销更小,数据更加紧凑,减少了网络的延迟,提升了整体的吞吐量。

支持一个 TCP 连接发起多请求,即支持多路复用,像 HTTP/1.1 pipeline 还是有阻塞的情况,需要等前面的一个响应返回了后面的才能返回。

而多路复用就是完全异步化,这减少了整体的往返时间(RTT),解决了 HTTP 队头阻塞问题,也规避了 TCP 慢启动带来的影响。直观对比下HTTP1.1和HTTP2的速度区别:

HPACK 压缩头部,采用了静态表、动态表和哈夫曼编码,在客户端和服务器都维护请求头的列表,所以只需要增量和压缩过的头部信息,服务端拿到之后组装一下就能得到完整的头部信息。

形象一点就是如下图所示:

服务端主动推送数据,这个其实就是减少了请求的次数,比如客户端请求 1.html,我把 1.html 需要的 js 和 css 也一块送过去,省的之后客户端再请求我要 js ,我要这个 css。

3.4 HTTP/3版本

虽然 HTTP/2 解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题,主要是底层支撑的 TCP 协议造成的。

上文提到 HTTP/2 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了。

在出现丢包的情况下,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了,这就是队头阻塞问题。另外 TLS 协议层面也有一个队头阻塞,因为 TLS 协议都是按照 record 来处理数据的,如果一个 record 中丢失了数据,也会导致整个 record 无法正确处理。

但是对于 HTTP/1.1 来说,可以开启多个 TCP 连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。

那么可能就会有人考虑到去修改 TCP 协议,其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。

基于这个原因,Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议,并且使用在了 HTTP/3 上,HTTP/3 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC(Quick UDP Internet Connections)。

没错,它的名字就叫做“快”!!!我们先来说说这个QUIC!说完它就差不多说完了HTTP/3。

首先,队头阻塞的问题,因为 TCP 不认识每个流分别是哪个请求的,所以它只能全部阻塞住,而 QUIC 知道,因此比如请求 A 丢包了,我就把 A 卡住了就行,请求 B 完全可以全部放行,丝毫不受影响。

快总得有个指标吧?我们用RTT这个参数来衡量为什么QUIC快,我们以建立HTTPS连接为例来说明。

RTT:RTT是Round Trip Time的缩写,通俗地说,就是通信一来一回的时间。

对于RTT,举个例子,比如TCP三次握手:一去(SYN),二回(SYN+ACK),三去(ACK),相当于是一个半来回,故TCP连接时间等于1.5RTT。

对于HTTP,一去(HTTP Request),一回(HTTP Response),相当于一个来回,故HTTP交易时间是1RTT。

当然了,我们是HTTPS,中间有一个TLS协议,TLS1.2版本,一去(Client hello),二回(Server Hello),三去(Key Exchange),因此TLS需要1.5个RTT。

对于TLS1.2协议来说,咱们HTTPS通信的时间总和=TCP连接时间+TLS连接时间+HTTP交易时间=1.5RTT+1.5RTT+1RTT=4RTT。(可以直观看出来为什么建立新的TCP连接很慢了吧)

而咱们的QUIC协议,是基于UDP,UDP不需要连接,因此不会有附带的RTT时间。谷歌的QUIC协议集成TCP可靠传输机制、TLS安全加密、HTTP2流量复用技术,可以看到没有了TCP三次握手的1.5RTT,那么只需要2.5RTT时间。

TLS1.2+TCP QUIC+UDP

此外,完成QUIC交易的连接的Session ID会缓存在浏览器内存里,如果用户再次打开该页面,无需建立TLS连接,直接使用缓存的Session ID对应的加密参数,服务器可以根据Session ID在缓存中查找对应的加密参数,并完成加密。因此重连TLS连接是一个0RTT事件,用户索要等待的事件是=HTTP交易事件=1RTT。

这个特性很重要,因为 TCP 是基于四元组(源IP,源端口,目标IP,目标端口)来确定连接的,而在移动网络的情况下 IP 地址会频繁的换,这会导致反复的建链。

咱们的HTTP3基于QUIC,无论是队头阻塞问题,还是建链效率问题都得到了提升,但是HTTP3更进一步,使用的QUIC集成的是TLS1.3版本,此版本更加简练,建立TLS连接只要1RTT,是因为浏览器帝赐以就把自己的密钥交换的素材发给了服务器,省去了第三次去的消息。那么整体时间就变为:

第一次连接时间=TLS1.3连接时间+HTTP交易时间=1RTT+1RTT=2RTT
重连时间=HTTP交易时间=1RTT
从整体来看,HTTP3=HTTP2+QUIC。而QUIC=TLS1.3+UDP。期待QUIC普及,期待UDP翻身。不过可以预想到由于种种兼容问题,HTTP3的普及仍需要很久很久的时间。