HTTP 0.9
流程
- 因为 HTTP 都是基于 TCP 协议的,所以客户端先要根据 IP 地址、端口和服务器建立 TCP 连接,而建立连接的过程就是 TCP 协议三次握手的过程。
- 建立好连接之后,会发送**一个 GET 请求行的信息**,如GET /index.html用来获取 index.html。
- 服务器接收请求信息之后,读取对应的 HTML 文件,并将数据以 ASCII 字符流**返回**给客户端。
-
特点
第一个是只有一个请求行,并**没有HTTP 请求头和请求**体,因为只需要一个请求行就可以完整表达客户端的需求了。
- 第二个是服务器也没**有返回头信息**,这是因为服务器端并不需要告诉客户端太多信息,只需要返回数据就可以了。
- 第三个是返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件,所以使用 ASCII 字节码来传输是最合适的。
HTTP 1.0
背景
请求的内容不再局限于HTML文件,还需要JS文件、图片、css等等各种类型的文件。改进
要支持多种类型的文件,需要解决以下几个问题
- 浏览器需要知道服务器返回的数据是什么类型的,然后浏览器才能根据不同的数据类型做针对性的处理。
- 由于万维网所支持的应用变得越来越广,所以单个文件的数据量也变得越来越大。为了减轻传输性能,服务器会对数据进行压缩后再传输,所以浏览器需要知道服务器压缩的方法。
- 由于万维网是支持全球范围的,所以需要提供国际化的支持,服务器需要对不同的地区提供不同的语言版本,这就需要浏览器告诉服务器想要什么语言版本的页面。
- 由于增加了各种不同类型的文件,而每种文件的编码形式又可能不一样,为了能够准确地读取文件,浏览器需要知道服务器返回的文件的编码类型
针对以上问题,HTTP 1.0使用了请求头与响应头进行协商:
请求头**的例子如下:
accept: text/html // 可接受文件类型accept-encoding: gzip, deflate, br // 可接受的压缩方式accept-Charset: ISO-8859-1,utf-8 // 可接受的文件编码accept-language: zh-CN,zh // 可接受的语言类型
响应头的例子如下:
content-encoding: br // 压缩方法为brcontent-type: text/html; charset=UTF-8 // 文件类型为text/html 字符编码为utf-8
其余特性
- 有的请求服务器可能无法处理,或者处理出错,这时候就需要告诉浏览器服务器最终处理该请求的情况,这就引入了状态码。状态码是通过响应行的方式来通知浏览器的。
- 为了减轻服务器的压力,在 HTTP/1.0 中提供了Cache 机制,用来缓存已经下载过的数据。如 Expires 和 Last-Modified 响应头和 If-Modified-Since 请求头。
- 服务器需要统计客户端的基础信息,比如 Windows 和 macOS 的用户数量分别是多少,所以 HTTP/1.0 的请求头中还加入了用户代理(User-Agent)的字段
HTTP 1.1
针对HTTP 1.0的各种问题,HTTP 1.1做出了以下主要改进:持久连接
HTTP 1.0中,每进行一次HTTP通信都需要一条TCP链接,这在一个需要多请求(如图片网站)的页面中造成巨大浪费。
因此,在HTTP 1.1中,增加了持久连接的方法,一条TCP链接可进行多次的HTTP通信。设置的方法是在请求头中加上Connection: keep-alive,这个请求头是默认加上的,无需手动进行。
虚拟主机支持
在 HTTP/1.0 中,每个域名绑定了一个唯一的 IP 地址,因此一个服务器只能支持一个域名。但是随着虚拟主机技术的发展,需要实现在一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址。
因此,HTTP/1.1 的请求头中增加了Host字段,用来表示当前的域名地址,这样服务器就可以根据不同的 Host 值做不同的处理。支持动态内容生成
在设计 HTTP/1.0 时,需要在响应头中设置完整的数据大小,如Content-Length: 901,这样浏览器就可以根据设置的数据大小来接收数据,从而根据内容长度来判断传输是否完成。不过随着服务器端的技术发展,很多页面的内容都是动态生成的,因此在传输数据之前并不知道最终的数据大小,这就导致了浏览器不知道何时会接收完所有的文件数据。
HTTP/1.1 通过引入Chunk transfer 机制来解决这个问题,服务器会将数据分割成若干个任意大小的数据块,通过 Transfer-Encoding: chunked 来表示当前内容为一个数据块,每个数据块发送时会附上上个数据块的长度,最后使用一个零长度的块作为发送数据完成的标志。这样就提供了对动态内容的支持。
改善缓存机制
添加了 ETag/If-None-Match 和 Cache-Control 缓存消息头代替 Last-Modified/If-Modified-Since 和 Expires 缓存消息头。
HTTP 2.0
背景
HTTP 1.1的主要问题是其对网络带宽的利用率并不高,主要有三方面造成:
- TCP的慢启动。HTTP是基于TCP协议的,而TCP刚进入发送数据的状态时,是会以很慢的速度进行,然后再逐步进行加速,这是为了减少网络堵塞的策略。这种策略用在一些小文件的传输身上往往会出现连接没有跑到满速就传输完成的现象。
- 多个TCP连接会竞争带宽。网络的带宽是固定的,如果出现多个TCP连接,每个TCP所能分到的带宽就会变小。
- HTTP 1.1队头阻塞。虽然HTTP 1.1 加入了持久连接的机制,但是对于单个TCP连接来说,如果出现需要传输多个HTTP的情况,则后一个HTTP连接必须要等前一个完成以后才能继续进行。
多路复用
- 针对问题一和二,HTTP 2.0的解决方法是一个域名只使用一条TCP连接。
- 针对问题三,解决方法是一条TCP连接同时进行多条HTTP连接。
上面两点综合起来的技术称为多路复用,参考下图。
在多路复用技术中,每个HTTP消息被分解为多个具有ID的帧,浏览器和服务器交错发送具有不同ID的帧,这些帧在另一端被重新组装起来形成完整的消息,由此就可以实现单条TCP连接上承载多条HTTP连接。
在实现层面上,HTTP 2.0是通过 二进制分帧层 将一条HTTP消息转换成多个帧,基本流程如下:
- 首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。
- 这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。
- 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息。
- 然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
- 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。
- 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求
其余特性
设置请求优先级
我们知道浏览器中有些数据是非常重要的,但是在发送请求时,重要的请求可能会晚于那些不怎么重要的请求,如果服务器按照请求的顺序来回复数据,那么这个重要的数据就有可能推迟很久才能送达浏览器,这对于用户体验来说是非常不友好的。
为了解决这个问题,HTTP/2 提供了请求优先级,可以在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。
服务器推送
服务器推送指的是服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。
**
一个简单的例子就是当浏览器请求一个HTML,服务端经过解析后将这个HTML需要的CSS和JS一并发送给浏览器,这就省去了浏览器解析html文件请求CSS文件和JS文件的过程。
头部压缩
HTTP 2.0会将请求头和响应头进行压缩,从而减少HTTP消息的长度。其中用到的一个技术是字典编码,这里的字典分为静态字典和动态字典两类。
静态字典指的是将常用的请求头和键值对建立成一个字典,如下图(完整请看这里):
浏览器与服务器会共享同样的静态字典**,当传输对应的头部信息时,就可以使用单个字符代替,其中这里有两种情况:
- 对于完全匹配的头部键值对,例如
:method: GET,可以直接使用一个字符表示; - 对于头部名称可以匹配的键值对,例如
cookie: xxxxxxx,可以将名称使用一个字符表示。
动态字典则需要浏览器与服务器协商**,譬如浏览器通知服务器将 cookie: xxxx 添加到动态字典当中,那么此后的消息就能使用一个字符来代替了。
HTTP 3.0
背景
HTTP 2.0的局限性大多是由于TCP本身固有的特点所导致的,主要有下列几个问题:
- TCP队头阻塞 。一条TCP连接当中,如果出现了某个数据包丢失的情况,则整条TCP连接都会停下来等待该数据包的重传,而由于HTTP 2.0是多条HTTP连接公用1条TCP连接,这就意味着一旦发生数据包丢失,所有的HTTP连接都会暂停直至该数据包成功重传,这种情况下HTTP 2.0的性能甚至比不上HTTP 1.1,因为后者只会阻塞单条HTTP连接。
- TCP连接延时 。TCP建立连接需要3次握手,如果算上TLS协议的握手时间的话甚至需要更多的时间。
QUIC
由于上述提到的TCP的种种问题,Google决定开发一个基于UDP协议的QUIC协议,集成了以下功能:
- 实现了类似 TCP 的拥塞控制、传输可靠性的功能。由于 UDP 不提供可靠性的传输,所以 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
- 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。
- 实现了 HTTP/2 中的多路复用功能,同时避免了队头堵塞。和 TCP 不同,UDP对于包的处理顺序没有要求,所以不会出现队头堵塞的现象。
- 实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
参考资料:
