HTTP 进化史
这篇文章笔者从互联网远古时代的 HTTP/0.9 协议一直谈到了今天还在完善的 HTTP/3 协议,希望能推广 HTTP 这种常见的协议在国内编程环境中的认识程度。
HTTP 进化路线图
HTTP/0.9
已过时。只接受 GET 一种请求方法,没有在通讯中指定版本号,且不支持请求头。由于该版本不支持 POST 方法,因此客户端无法向服务器传递太多信息。
No HTTP headers (cannot transfer other content type files), No status/error codes, No URLs, No versioning.
没有 HTTP 头,没有状态码,没有URL,没有版本控制。
HTTP/1.0
HTTP/1.0 在请求和响应头中添加了版本号、状态码、内容类型,支持 GET HEAD POST 等请求方式。
但是在 HTTP/1.0 中,服务器响应结束后,会立即终止 TCP 连接。
HTTP/1.1
持久化连接和管线化
在HTTP 0.9和1.0中,TCP连接在每一次请求/回应对之后关闭,所以需要大量的时间进行 TCP 三次握手。
在HTTP 1.1中,引入了持久化连接(persistent connections)的机制,一个连接可以重复在多个请求/回应使用。持续连线的方式可以大大减少等待时间,因为在发出第一个请求后,双方不需要重新运行TCP握手程序握手。
同时 HTTP/1.1 支持更多的请求方式:GET HEAD POST PUT DELETE TRACE OPTIONS,以及因为在默认情况下启动持久化连接,所以 Keep-Alive
标签被用于指定持久化连接策略,添加timeout
和max
参数以设置超时或限制每个连接的最大请求数。
请求结果管线化使得 HTML 网页加载时间动态提升,特别是在具体有高延迟的连接环境下,如卫星上网。在宽带连接中,加速不是那么显著的,因为需要服务端要遵循 HTTP/1.1 协议,必须按照客户端发送的请求顺序来回复请求,这样整个连接还是先进先出的,队头阻塞(HOL blocking)可能会发生,造成延迟。
管线化机制须透过持久化连线完成,并且只有 GET 和 HEAD 等要求可以进行管线化,非幂等的方法,例如 POST 将不会被管线化。连续的 GET 和 HEAD 请求总可以管线化的。管线化意味着无需为每一个请求开启一个 TCP 连接,也无需等待前一个请求的响应后才进行下一个请求的发送。
队头阻塞的发生是因为,在服务器端会有一个缓冲区,来存放那些已经被处理好了但是还没轮到被发送的响应。比如服务器先后收到了A,B两个请求,A资源比B资源优先级要高,处理A需要10ms,处理B需要1ms,假设服务器可以并行处理请求,那么B的响应肯定是最先处理好了的,但是B响应不能先发出去,必须待在缓冲区里,等待A响应处理好了之后,先把A的响应发出去,B的响应才能够被发出去。服务端必须要遵循FIFO这个原则,因为HTTP请求和响应并没有序号标识,无法将乱序的响应与请求关联起来。
分块传输编码
分块传输编码(Transfer-Encoding)就是这样一种解决方案:它把数据分解成一系列数据块,并以多个块发送给客户端,服务器发送数据时不再需要预先告诉客户端发送内容的总大小,只需在响应头里面添加Transfer-Encoding: chunked
,以此来告诉浏览器我使用的是分块传输编码,这样就不需要 Content-Length 了,这就是分块传输编码 Transfer-Encoding 的作用。
HTTP分块传输编码允许服务器为动态生成的内容维持HTTP持久链接。通常,持久链接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是对于动态生成的内容来说,在内容创建完之前是不可知的。
HTTP服务器有时使用压缩(gzip)以缩短传输花费的时间。分块传输编码可以用来分隔压缩对象的多个部分。在这种情况下,块不是分别压缩的,而是整个负载进行压缩,压缩的输出使用本文描述的方案进行分块传输。在压缩的情形中,分块编码有利于一边进行压缩一边发送数据,而不是先完成压缩过程以得知压缩后数据的大小。
HTTP Upgrade
客户端通过 Upgrade
头部字段列出所希望升级到的协议和版本,如果服务端不同意升级或者不支持 Upgrade
所列出的协议,直接忽略即可;如果服务端同意升级,那么需要响应101 Switching Protocols
。
HTTP/2
这是一个基于 SPDY 协议的修改版本。
ALPN Application-Layer Protocol Negotiation 应用层协商协议
HTTP/2 协议本身并没有要求它必须基于 HTTPS(TLS)部署,但是在实际使用中HTTP/2 和 HTTPS 几乎都是捆绑在一起的,所以在使用 HTTP Upgrade 升级到 HTTP/2.0 时,客户端是在建立 HTTPS 连接过程中,在 TLS 连接的 Client Hello 握手中,通过 ALPN 扩展列出了自己支持的各种应用层协议。其中,HTTP/2 协议名称是 h2
。如果服务端支持 HTTP/2,在 Server Hello 中指定 ALPN 的结果为 h2
就可以了;如果服务端不支持 HTTP/2,从客户端的 ALPN 列表中选一个自己支持的即可。
二进制帧
在 HTTP 2.0 中,它把数据报的两大部分分成了 header frame
和 data frame
。也就是头部帧和数据体帧。帧的传输最终在流中进行,流中的帧,头部(header)帧
和 data 帧
可以分为多个片段帧,例如data
帧即是可以 data = data_1 + data_2 + ... + data_n
。
流
双向性:同一个流内,可同时发送和接受数据。
有序性:流中被传输的数据就是二进制帧
。帧在流上的被发送与被接收都是按照顺序进行的。
并行性:流中的 二进制帧
都是被并行传输的,无需按顺序等待。但却不会引起数据混乱,因为每个帧都有顺序标号。它们最终会被按照顺序标号来合并。
流的创建:流可以被客户端或服务器单方面建立, 使用或共享。
流的关闭:流也可以被任意一方关闭。
帧是流中传输的基本数据单位,每个帧具有自己的顺序标号,以使得并行以及无需连续发送加快传输速度,Header 帧或者 Data 帧都可以被拆分。
多路复用
HTTP/2.0 的多路复用是 HTTP/1.1 持久化连接的升级,借由流、二进制帧的特性解决了队头阻塞的问题。
HPACK 算法
为了解决 HTTP/1.X 无意义重复头部的问题。HPACK 使用静态索引表和动态索引表来把头部映射到索引值,并对不存在的头部使用 huffman 编码,并动态缓存到索引,从而达到压缩头部的效果。
当要发送的值符合静态表时,用对应的 Index 替换即可。而动态表是一个由先进先出的队列维护的有空间限制的表,里面同样维护的是头部与对应的索引,每个动态表只针对一个连接,每个连接的压缩解压缩的上下文有且仅有一个动态表。
Server Push
服务器推送即服务端向客户端发送比客户端请求更多的数据。这允许服务器直接提供浏览器渲染页面所需资源,而无须浏览器在收到、解析页面后再提起一轮请求,节约了加载时间。
HTTP/3
这是一个基于 QUIC 协议的应用层协议。
QUIC 是一个基于 UDP 的传输层协议,采用 TLS 1.3 传输层安全协议来安全的建立连接,QUIC没有非加密的版本。
QUIC建立连接时,加密算法的版本协商与传输层握手合并完成,以减小延迟。而 TCP 建立 HTTPS 连接需要七次握手。
QUIC包含一个连接标识符,该标识符唯一地标识客户端与服务器之间的连接,而无论源 IP 地址是什么。这样只需发送一个包含此 ID 的数据包即可重新创建连接,因为即使用户的 IP 地址发生变化,原始连接 ID 仍然有效。反观 TCP 在需要切换 IP 地址时需要大量的工作。
TCP 由于其协议的特性,致使其无法真正的解决队头阻塞的问题,而且由于网络基础设施建设的快速进展,使得一个新的传输层协议会对原有的基础设施造成极大的更新压力,这就是 QUIC 出现的环境基础。笔者希望这样的基于 UDP 的协议能够发展起来,因为 TCP 协议在今天网络环境下看起来过于保守。
相关概念
队头阻塞
TCP 的队头阻塞,使用快速重传机制缓解,但无法避免。
HTTP/1.1 的队头阻塞,在 HTTP/2.0 中使用二进制帧及序号解决,但是令人遗憾的是,队头阻塞还是会出现在 TCP 中。
幂等方法
A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.
幂等方法也就是使用该方法发送相同的多个请求的预期效果与单个此请求的效果相同,此方法即为幂等方法。
引用
本作品采用知识共享署名 4.0 国际许可协议(CC BY-NC-SA 4.0)进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。
最后更新于