详解 HTTP Keep-Alive 选项说明及注意事项
keep-Alive 首部只是请求将连接保持在活跃状态。发出 keep-alive 请求之后,客户端和服务器并不一定会同意进行 keep-alive 会话。它们可以在任意时刻关闭空闲的 keep-alive 连接,并可随意限制 keep-alive 连接所处理事务的数量。
可以用 Keep-Alive 通用首部中指定的、由逗号分隔的选项来调节 keep-alive 的行为。
参数 timeout 是在 Keep-Alive 响应首部发送的。它估计了服务器希望将连接保持在活跃状态的时间。这并不是一个承诺值。
参数 max 是在 Keep-Alive 响应首部发送的。它估计了服务器还希望为多少个事务保持此连接的活跃状态。这并不是一个承诺值。
Keep-Alive 首部还可支持任意未经处理的属性,这些属性主要用于诊断和调试。语法为 name [=value]。
Keep-Alive 首部完全是可选的,但只有在提供 Connection:Keep-Alive 时才能使用它。
此外使用 keep-alive 连接时有一些限制和一些需要注意的地方。
在 HTTP/1.0 中,keep-alive 并不是默认使用的。客户端必须发送一个 Connection: Keep-Alive 请求首部来激活 keep-alive 连接。
Connection: Keep-Alive 首部必须随所有希望保持持久连接的报文一起发送。如果客户端没有发送 Connection:Keep-Alive 首部,服务器就会在那条请求之后关闭连接。
通过检测响应中是否包含 Connection: Keep-Alive 响应首部,客户端可以判断服务器是否会在发出响应之后关闭连接。
只有在无需检测到连接的关闭即可确定报文实体主体部分长度的情况下,才能将连接保持在打开状态——也就是说实体的主体部分必须有正确的 Content-Length,有多部件媒体类型,或者用分块传输编码的方式进行了编码。在一条 keep-alive 信道中回送错误的 Content-Length 是很糟糕的事,这样的话,事务处理的另一端就无法精确地检测出一条报文的结束和另一条报文的开始了。
代理和网关必须执行 Connection 首部的规则。代理或网关必须在将报文转发出去或将其高速缓存之前,删除在 Connection 首部中命名的所有首部字段以及 Connection 首部自身。
严格来说,不应该与无法确定是否支持 Connection 首部的代理服务器建立 keep-alive 连接,以防止出现下面要介绍的哑代理问题。在实际应用中不是总能做到这一点的。
从技术上来讲,应该忽略所有来自 HTTP/1.0 设备的 Connection 首部字段(包括 Connection: Keep-Alive),因为它们可能是由比较老的代理服务器误转发的。但实际上,尽管可能会有在老代理上挂起的危险,有些客户端和服务器还是会违反这条规则。
除非重复发送请求会产生其他一些副作用,否则如果在客户端收到完整的响应之前连接就关闭了,客户端就一定要做好重试请求的准备。
HTTP/1.1 逐渐停止了对 keep-alive 连接的支持,用一种名为持久连接(persistent connection)的改进型设计取代了它。持久连接的目的与 keep-alive 连接的目的相同,但工作机制更优一些。
与 HTTP/1.0+的 keep-alive 连接不同,HTTP/1.1 持久连接在默认情况下是激活的。除非特别指明,否则 HTTP/1.1 假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1 应用程序必须向报文中显式地添加一个 Connection:close 首部。这是与以前的 HTTP 协议版本很重要的区别,在以前的版本中,keep-alive 连接要么是可选的,要么根本就不支持。
HTTP/1.1 客户端假定在收到响应后,除非响应中包含了 Connection: close 首部,不然 HTTP/1.1 连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送 Connection: close 并不意味着服务器承诺永远将连接保持在打开状态。
在持久连接的使用中有以下限制和需要注意的地方。
发送了 Connection: close 请求首部之后,客户端就无法在那条连接上发送更多的请求了。
如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个 Connection: close 请求首部。
只有当连接上所有的报文都有正确的、自定义报文长度时——也就是说,实体主体部分的长度都和相应的 Content-Length 一致,或者是用分块传输编码方式编码的——连接才能持久保持。
HTTP/1.1 的代理必须能够分别管理与客户端和服务器的持久连接——每个持久连接都只适用于一跳传输。
(由于较老的代理会转发 Connection 首部,所以)HTTP/1.1 的代理服务器不应该与 HTTP/1.0 客户端建立持久连接,除非它们了解客户端的处理能力。实际上,这一点是很难做到的,很多厂商都违背了这一原则。
尽管服务器不应该试图在传输报文的过程中关闭连接,而且在关闭连接之前至少应该响应一条请求,但不管 Connection 首部取了什么值,HTTP/1.1 设备都可以在任意时刻关闭连接。
HTTP/1.1 应用程序必须能够从异步的关闭中恢复出来。只要不存在可能会累积起来的副作用,客户端都应该重试这条请求。
除非重复发起请求会产生副作用,否则如果在客户端收到整条响应之前连接关闭了,客户端就必须要重新发起请求。
一个用户客户端对任何服务器或代理最多只能维护两条持久连接,以防服务器过载。代理可能需要更多到服务器的连接来支持并发用户的通信,所以,如果有 N 个用户试图访问服务器的话,代理最多要维持 2N 条到任意服务器或父代理的连接。
HTTP/1.1 允许在持久连接上可选地使用请求管道。这是相对于 keep-alive 连接的又一性能优化。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向地球另一端的服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。
对管道化连接有几条限制。
如果 HTTP 客户端无法确认连接是持久的,就不应该使用管道。
必须按照与请求相同的顺序回送 HTTP 响应。HTTP 报文中没有序列号标签,因此如果收到的响应失序了,就没办法将其与请求匹配起来了。
HTTP 客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。如果客户端打开了一条持久连接,并立即发出了 10 条请求,服务器可能在只处理了,比方说,5 条请求之后关闭连接。剩下的 5 条请求会失败,客户端必须能够应对这些过早关闭连接的情况,重新发出这些请求。
HTTP 客户端不应该用管道化的方式发送会产生副作用的请求(比如 POST)。总之,出错的时候,管道化方式会阻碍客户端了解服务器执行的是一系列管道化请求中的哪一些。由于无法安全地重试 POST 这样的非幂等请求,所以出错时,就存在某些方法永远不会被执行的风险。
版权声明: 本文为 InfoQ 作者【阿泽🧸】的原创文章。
原文链接:【http://xie.infoq.cn/article/931e85e5794760606b7a9c0f1】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论