HTTP之持久连接

HTTP/1.1 允许 HTTP 设备在事务处理结束之后将 TCP 连接保持在打开状态,以便为未来的 HTTP 请求重用现存的连接。在事务处理结束后仍然保持在打开状态的 TCP 连接被称为持久连接。非持久连接会在每个事务结束之后关闭。持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。

持久连接降低时延和连接建立的开销,将连接保持在已调谐状态,而且减少了打开连接的潜在数量。

持久连接与并行连接配合使用可能是最高效的方式。持久连接有两种类型:比较老的 HTTP/1.0+ "keep-alive" 连接,以及现代的 HTTP/1.1 "persistent" 连接。

Keep-Alive 操作

实现 HTTP/1.0 keep-alive 连接的客户端可以通过包含 Connection: Keep-Alive 首部请求将一条连接保持在打开状态。

如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有 Connection: Keep-Alive 首部,客户端就认为服务器不支持 keep-alive,会在发回响应报文之后关闭连接。

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 说明服务器最多还会为另外 5 个事务保持连接的打开状态,或者将打开状态保持到连接空闲了 2 分钟之后。

Connection: Keep-Alive
Keep-Alive: max=5, timeout=120

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),因为它们可能是由比较老的代理服务器误转发的。
  • 除非重复发送请求会产生其他一些副作用,否则如果在客户端收到完整响应之前连接就关闭了,客户端就一定要做好重试请求的准备。

Keep-Alive 和哑代理

1. Connection 首部和盲中继

问题出在代理上--尤其是那些不理解 Connection 首部,而且不知道在沿着转发链路将其发送出去之前,应该将该首部删除的代理。很多老的或简单的代理都是盲中继(blind relay),它们只是将字节从一个连接转发到另一个连接中去,不对 Connection 首部进行特殊的处理。

  • Web 向代理发送包含 Connection: Keep-Alive 首部的报文,期待建立一个 keep-alive 连接;
  • 哑代理收到这条请求,但并不理解 Connection 首部(只是将其作为一个扩展首部对待)。因此只是沿着转发链路将报文一字不漏地发送给服务器。但 Connection 首部是个逐跳首部,只适用于单条传输链路,不应该沿着传输链路向下传输。
  • 经过中继的 HTTP 请求到达 Web 服务器。当 Web 服务器收到经过代理转发的 Connection: Keep-Alive 首部时,会误以为代理希望进行 keep-alive 对话,因此回送一个 Connection: Keep-Alive 响应首部。所以,此时 Web 服务器认为它在与代理进行 keep-alive 对话,会遵循 keep-alive 规则。但代理却对 keep-alive 一无所知。
  • 哑代理将 web 服务器的响应报文回送给客户端,并将来自 web 服务器的 Connection: Keep-Alive 首部一起传送过去。客户端看到这个首部,会认为代理同意进行 keep-alive 对话。所以,此时客户端和服务器都认为它们在进行 keep-alive 对话,但与它们进行对话的代理却对 keep-alive 一无所知。
  • 由于代理不知道 keep-alive,所以会将收到的所有数据都回送给客户端,然后等待源端服务器关闭连接。但源端服务器会认为代理已经显式地请求它将连接保持在打开状态了,所以不会去关闭连接。这样,代理就会挂在那里等待连接的关闭。
  • 客户端收到回送的响应报文时,会立即转向下一条请求,在 keep-alive 连接上向代理发送另一条请求。而代理并不认为同一条请求上会有其他请求到来,请求被忽略,浏览器就挂在那里了。
  • 这种错误的通信方式会使浏览器一直处于挂起状态,直到客户端或服务器将来连接超时,并将其关闭为止。

代理和逐跳首部

为避免这类代理通信问题的发生,现代的代理都绝不能转发 Connection 首部和所有名字出现在 Connection 值中的首部。因此,如果一个代理收到了一个 Connection: Keep-Alive 首部,是不应该转发 Connection 首部,或所有名为 Keep-Alive 首部的。

如下几个首部不能作为 Connection 首部值列出,也不能被代理转发或作为缓存相应使用的首部:Proxy-Authenticate、Proxy-Connection、Transfer-Encoding 和 Upgrade。

插入 Proxy-Connection

对盲中继的变通做法是引入了一个名为 Proxy-Connection 的新首部,解决了在客户端后面紧跟着一个盲中继所带来的问题--但并没有解决所有其他情况下存在的问题。

浏览器会向代理发送非标准的 Proxy-Connection 扩展首部,而不是官方支持的著名的 Connection 首部。如果代理是盲中继,它会将无意义的 Proxy-Connection 首部转发给 Web 服务器,服务器会忽略此首部,不会带来任何问题。但如果是能够理解持久连接的代理,就用一个 Connection 首部取代无意义的 Proxy-Connection 首部,然后将其发送给服务器。

Proxy-Connection 首部修正了单个盲中继带来的问题:

对有多层次代理的情况,Proxy-Connection 仍然无法解决问题:

HTTP/1.1 持久连接

HTTP/1.1 逐渐停止了对 keep-alive 连接的支持,用一种名为持久连接(persistent connection)的改进型设计取代了它。

HTTP/1.1 持久连接在默认情况下是激活的。除非特别指明,否则 HTTP/1.1 假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1 应用程序必须向报文中显式地添加一个 Connection: close 首部。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送 Connection: close 并不意味着服务器承若永远将连接保持在打开状态。

持久连接的限制和规则

  • 发送了 Connection: close 请求首部之后,客户端就无法在那条连接上发送更多的请求了。
  • 如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个 Connection: close 请求首部。
  • 只有当连接上所有的保卫都有正确的、自定义报文长度时--也就是说,实体主体部分的长度都和相应的 Connection-Length 一致,或者是用分块传输编码方式编码的--连接才能持久连接。
  • HTTP/1.1 的代理必须能够分别管理与客户端和服务器的持久连接--每个持久连接都只适用于一跳传输。
  • HTTP/1.1 的代理服务器不应该与 HTTP/1.O 客户端建立持久连接,除非它们了解客户端的处理能力。
posted @ 2018-07-10 21:36  季末的天堂  阅读(2521)  评论(0编辑  收藏  举报