超文本传输协议(Hypertext Transfer Protocol,HTTP)是互联网上使用最多的协议,也是推动传输层进化的主要力量。
图1-20 TLS更快发送数据的尝试
图1-21 TLS 1.3首次连接和恢复连接
1990年Tim Berners-Lee设计的万维网(World Wide Web,WWW)包含三大基础技术:命名方案URI(Uniform Resource Identifier,统一资源标识符),通信协议HTTP和用来表示信息的标记语言HTML(Hyper Text Markup Language,超文本标记语言),并于当年完成了第一个浏览器原型。对于万维网的开创性贡献使得他赢得了万维网之父的称号。
1991年,Tim Berners-Lee基于之前的设计和实现发表了定义HTTP的文章 ,其中定义的HTTP就是HTTP0.9(本书中HTTP/0.9简写为HTTP0.9)版本。在这篇文章中,HTTP分成了如下四个阶段。
●连接:客户端使用域名或者IP地址和端口号连接到服务器,服务器接收连接。
●请求:客户端发送一个请求,请求内容是一个ASCII字符串,包含GET方法和请求文件的路径,以CRLF(回车换行)结束。
●响应:响应是一个ASCII字符的字节流,内容是HTML格式的超文本,行之间以CRLF隔开。
●关闭连接:服务器将响应发送完后关闭连接。
HTTP0.9过程如图1-22所示。
图1-22 HTTP 0.9过程
HTTP0.9响应内容格式如图1-23所示。
图1-23 HTTP 0.9响应内容格式
HTTP0.9并不是一个标准,且仅支持简单的请求响应,只能访问简单的文本文档。随着万维网的发展,资源种类开始变得多样化,HTTP也需要支持更复杂的表达方式。于是1996年5月,HTTP工作组(HTTP-WG)发布了RFC1945,这就是HTTP1(本书中HTTP/1.0简写为HTTP1)。HTTP1是一个信息性的RFC,总结了当时应用较广泛的HTTP机制。
HTTP1中引入了请求头和响应头,请求时可以指定HTTP版本号、用户代理、接收类型等,响应可以指明响应状态、内容长度、内容类型等。HTTP1可以支持除HTML格式外的多种类型文件,比如图像、纯文本等,已经由超文本协议变成了超媒体协议。
另外,HTTP1在GET之外增加了POST和HEAD请求方法;增加了缓存,并使用Expire、If-Modified-Since、Last-Modified首部字段协助客户端和中间件判断缓存是否过期;响应增加了状态码,这是之前HTTP 0.9版本在HTTP层无法表示的信息。
HTTP1中的请求和响应仍然是ASCII码字符流,但是请求可以是包含由CRLF分隔的多个行;响应由状态开始,之后是首部字段的多个行,常见的如Content-Type(文件格式)、Content-Length(内容长度)等。HTTP1请求和响应分别如图1-24和图1-25所示。
图1-24 HTTP1请求
图1-25 HTTP1响应
在使用TCP连接方面,HTTP1与HTTP0.9一样,在请求之前打开一条TCP连接,完成响应后关闭,一个TCP连接上只能完成一个请求和响应,如图1-26所示。
从图1-26可以看出,每个TCP连接只能完成一个请求和响应,在响应较小的情况下建立连接和关闭连接的代价较大。除此之外,TCP连接建立后的慢启动也会影响效率。
1997年1月IETF发布了RFC 2068,这是HTTP第一个IETF官方标准。1999年6月,IETF发布RFC 2616更新了RFC 2068,对HTTP1.1(本书中HTTP/1.1简为为HTTP1.1)做了一些改进。2014年,IETF发布了6个新的RFC(RFC 7230-7235)取代了RFC 2616,但并没有大的修改。
图1-26 HTTP1与TCP
HTTP1.1最大的改进在于增加了重用TCP连接的方法,默认保持连接,除非显式通知关闭连接 。这样可以在一个TCP连接上完成多个请求-响应,消除了TCP建立的延迟,也避免了新建立的TCP连接的慢启动过程。如图1-27所示,HTTP1完成两个请求-响应需要两次TCP连接建立和关闭的过程,请求2-响应2还经过了TCP慢启动;HTTP1.1在完成第一个请求后不关闭TCP连接,请求2-响应2可以直接在原来的TCP连接上进行。
图1-27 HTTP1与HTTP1.1对比
保持连接的功能对性能非常重要,所以也移植到了HTTP1,HTTP1可以通过头Connection:keep-alive来显式通知保持连接。
另外,HTTP1.1在HTTP请求首部中增加了Host字段,用来支持共享IP地址的虚拟主机服务器;同时支持了更多的方法,如PUT、PATCH、DELETE、OPTIONS;引入分块传输支持动态内容;引入了更多的缓存控制策略;支持请求部分内容等。HTTP1.1的请求响应过程如下。
虽然改善了之前版本的问题,但是HTTP1.1版本仍然存在一些问题导致带宽利用率并不高。HTTP1.1多个请求-响应重用连接时,后面的请求-响应必须等前面的完成才能进行,这就导致了HTTP的头部阻塞问题,即如果前面的请求-响应卡住了,后面的都无法进行。在一个TCP连接上,请求-响应只能一个一个排队进行,效率比较低。所以,在使用HTTP1.1时,客户端一般会建立多个TCP连接(比如大部分浏览器会控制到一个目的地的TCP连接个数为6),以便增加多个请求-响应的速度,减轻头部阻塞问题。但是到一个目的地的多个TCP连接都会慢启动,如果浏览器允许6个连接,但是需要执行300个请求,每个连接上平均有50个请求在排队,有带宽也不能发送,必须等前面请求的响应完成,这导致了用户感知的时延的增加,并且有头部阻塞的风险。另外TCP连接之间的竞争可能会导致丢包,从而出现大幅度的拥塞窗口减小,影响传输效率。
注意 HTTP1.1为了解决传输效率的问题,曾经提出过管道化传输,但效果不好,并没有被广泛采用。
为了解决HTTP1.1的问题,谷歌于2009年开始实验性协议SPDY,主要目的是降低用户感知到的延迟。
2012年,IETF开始基于SPDY进行HTTP2(本书中HTTP/2简写为HTTP2)版本的标准化,并于2015年5月发布了HTTP2标准RFC 7540。
HTTP2保留了HTTP1的语义,但修改了HTTP的封装格式,增加了一个二进制分帧层。基于二进制分层,HTTP2实现了HTTP的多路复用。HTTP2为每个请求分配了一个流标识,服务器响应时带上相同的流标识,客户端就可以方便地将响应与请求关联起来,而不用依赖顺序,从而可以降低延迟和提高吞吐量。
通过多路复用,HTTP发送多个请求时就可以不用等待其他请求的响应(除优先级限制外),这样可以充分利用带宽,减少用户感知到的延迟。如图1-28所示,在带宽充足的情况下,HTTP2客户端比HTTP1.1可以更早地收到所有响应。
图1-28 HTTP1.1与HTTP2对比
HTTP2不再使用人类可读的字符串格式,而是采用二进制帧结构传输:
HTTP的首部和数据根据帧类型封装成帧结构,在TCP+TLS上传输。由于帧内有流标识,可以将不同的请求使用不同的流标识发送,服务器回应时也可以根据流标识进行回应,客户端收到帧就可以知道收到的响应对应哪个请求,从而做到多路复用,如图1-29所示。
图1-29 HTTP2多路复用
另外,HTTP2还增加了首部压缩HPACK(Header Compression for HTTP2,HTTP2首部压缩算法)以改善HTTP1首部字段在负载中占比过多的问题,支持请求优先级,支持服务器主动推送,增加了ALPN(Application-Layer Protocol Negotiation,应用层协议协商)。
HTTP2虽然实现了HTTP层的多路复用,但多个请求或响应在同一个TCP上发送时,仍然受制于TCP的队首阻塞问题,无法进一步提高效率。以HTTP发送3个请求,但请求2所在TCP报文丢失为例:在TCP不支持SACK的情况下,服务器即使收到了请求3,TCP也会丢弃等重传,无法处理请求2之后的请求,如图1-30a所示;在TCP支持SACK的情况下,服务器收到了请求3,但因为是乱序报文,只能缓存不能交付给应用(这里应用指的是HTTP),所以也必须等请求2重传后才能继续处理之后的请求,如图1-30b所示。
图1-30 HTTP2的头部阻塞:TCP不支持SACK和支持SACK
HTTP2支持认证、加密和完整性保护,这就是著名的HTTPS(Hypertext Transfer Protocol Secure,超文本传输安全协议),使用TCP+TLS发送一个请求的过程如图1-31所示。
图1-31 HTTP2使用TCP+TLS发送请求