跳到主要内容

9. 连接管理

HTTP 消息传递独立于底层传输层或会话层连接协议. HTTP 只假定可靠传输, 并要求请求按序交付, 对应响应也按序交付. 将 HTTP 请求和响应结构映射到底层传输协议数据单元的方式不属于本规范范围.

如 [HTTP] 第 7.3 节所述, HTTP 交互使用的具体连接协议由客户端配置和目标 URI 决定. 例如, "http" URI 方案 ([HTTP] 第 4.2.1 节) 表示默认连接为基于 IP 的 TCP, 默认 TCP 端口为 80, 但客户端也可能被配置为通过其他连接, 端口或协议使用代理.

HTTP 实现应当进行连接管理, 包括维护当前连接状态, 建立新连接或复用已有连接, 处理连接上收到的消息, 检测连接失败, 以及关闭每个连接. 大多数客户端会并行维护多个连接, 包括到同一服务器端点的多个连接. 大多数服务器被设计为维护数千个并发连接, 同时控制请求队列以实现公平使用并检测拒绝服务攻击.

9.1. 建立

本规范不描述如何通过各种传输层或会话层协议建立连接. 每个 HTTP 连接都映射到一个底层传输连接.

9.2. 将响应关联到请求

HTTP/1.1 不包含用于将给定请求消息同其对应的一个或多个响应消息相关联的请求标识符. 因此, 它依赖响应到达顺序与同一连接上发出请求的顺序完全对应. 每个请求出现多个响应消息的情况, 仅发生在一个或多个信息性响应 (1xx; 见 [HTTP] 第 15.2 节) 位于同一请求的最终响应之前时.

在某个连接上有多个未完成请求的客户端, MUST 按发送顺序维护未完成请求列表, 并且 MUST 将该连接上收到的每个响应消息关联到尚未收到最终 (非 1xx) 响应的第一个未完成请求.

如果客户端在没有未完成请求的连接上收到数据, 客户端 MUST NOT 将该数据视为有效响应; 客户端 SHOULD 关闭连接, 因为此时消息定界已变得含糊, 除非该数据仅由一个或多个 CRLF 组成 (这些 CRLF 可按第 2.2 节丢弃).

9.3. 持久性

HTTP/1.1 默认使用 "持久连接", 允许多个请求和响应承载在单个连接上. HTTP 实现 SHOULD 支持持久连接.

接收方根据最近收到的消息中的协议版本和 Connection 头字段 ([HTTP] 第 7.6.1 节) 判断连接是否持久, 如果有这样的消息:

  • 如果存在 "close" 连接选项 (第 9.6 节), 则当前响应之后连接不会保持; 否则,
  • 如果收到的协议是 HTTP/1.1 (或更高版本), 则当前响应之后连接会保持; 否则,
  • 如果收到的协议是 HTTP/1.0, 且存在 "keep-alive" 连接选项, 并且接收方不是代理或该消息是响应, 且接收方愿意遵循 HTTP/1.0 "keep-alive" 机制, 则当前响应之后连接会保持; 否则,
  • 当前响应之后连接将关闭.

不支持持久连接的客户端 MUST 在每个请求消息中发送 "close" 连接选项.

不支持持久连接的服务器 MUST 在每个非 1xx (Informational) 状态码的响应消息中发送 "close" 连接选项.

客户端 MAY 在持久连接上发送额外请求, 直到它发送或收到 "close" 连接选项, 或收到不带 "keep-alive" 连接选项的 HTTP/1.0 响应.

为了保持持久性, 连接上的所有消息都需要具有自定义的消息长度 (即不是由连接关闭定义的长度), 如第 6 节所述. 服务器 MUST 读取完整请求消息体, 或在发送响应后关闭连接; 否则, 持久连接上的剩余数据会被误解为下一个请求. 同样, 如果客户端打算为后续请求复用同一连接, 它 MUST 读取完整响应消息体.

代理服务器 MUST NOT 与 HTTP/1.0 客户端维护持久连接 (关于许多 HTTP/1.0 客户端实现的 Keep-Alive 头字段所带来的问题及讨论, 见附录 C.2.2).

关于同 HTTP/1.0 客户端向后兼容的更多信息, 见附录 C.2.2.

9.3.1. 重试请求

连接可能在任何时候关闭, 无论有意还是无意. 实现应当预期需要从异步关闭事件中恢复. 客户端可以自动重试一系列未完成请求的条件定义在 [HTTP] 第 9.2.2 节.

9.3.2. 流水线化

支持持久连接的客户端 MAY 对请求进行 "流水线化" (即发送多个请求而不等待每个响应). 如果流水线化请求序列全部使用安全方法 ([HTTP] 第 9.2.1 节), 服务器 MAY 并行处理这些请求, 但它 MUST 按收到请求的相同顺序发送对应响应.

如果连接在客户端收到所有对应响应之前关闭, 进行流水线化的客户端 SHOULD 重试未应答的请求. 在失败连接 (即服务器未在其最后一个完整响应中显式关闭的连接) 之后重试流水线化请求时, 客户端 MUST NOT 在连接建立后立即进行流水线化, 因为先前流水线中剩余的第一个请求可能已导致一个错误响应; 如果在过早关闭的连接上发送多个请求, 该错误响应可能再次丢失 (见第 9.6 节描述的 TCP reset 问题).

幂等方法 ([HTTP] 第 9.2.2 节) 对流水线化很重要, 因为它们可以在连接失败后被自动重试. 在非幂等方法之后, 用户代理 SHOULD NOT 对请求进行流水线化, 直到收到该方法的最终响应状态码, 除非用户代理有办法检测并从涉及流水线化序列的部分失败条件中恢复.

接收流水线化请求的中介 MAY 在入站转发这些请求时也进行流水线化, 因为它可以依赖出站用户代理来确定哪些请求可以安全地流水线化. 如果入站连接在收到响应前失败, 且这些请求全部使用幂等方法, 流水线化中介 MAY 尝试重试尚未收到响应的一系列请求; 否则, 流水线化中介 SHOULD 转发任何已收到的响应, 然后关闭对应的出站连接, 以便出站用户代理能够相应恢复.

9.4. 并发

客户端应当限制它对给定服务器同时保持打开的连接数量.

HTTP 的先前修订版给出了一个具体连接数作为上限, 但后来发现这对许多应用并不实际. 因此, 本规范不强制规定某个特定最大连接数, 而是鼓励客户端在打开多个连接时保持保守.

多个连接通常用于避免 "队头阻塞" 问题, 即需要大量服务器端处理和/或传输极大内容的请求会阻塞同一连接上的后续请求. 然而, 每个连接都会消耗服务器资源.

此外, 使用多个连接可能在拥塞网络中造成不良副作用. 使用较多连接也可能在本来未拥塞的网络中造成副作用, 因为这些连接聚合且初始同步的发送行为可能导致拥塞, 而如果使用较少的并行连接则不会出现这种拥塞.

注意, 服务器可能会拒绝其认为具有滥用性质或拒绝服务攻击特征的流量, 例如来自单个客户端的打开连接数量过多.

9.5. 失败和超时

服务器通常会有某个超时值, 超过该值后便不再维护非活动连接. 代理服务器可能将该值设得更高, 因为客户端很可能会通过同一代理服务器建立更多连接. 对于客户端或服务器而言, 使用持久连接并不要求该超时具备特定长度 (甚至不要求存在该超时).

希望超时的客户端或服务器 SHOULD 在连接上发起优雅关闭. 实现 SHOULD 持续监控打开的连接是否收到关闭信号, 并作出适当响应, 因为及时关闭连接双方可以回收已分配的系统资源.

客户端, 服务器或代理 MAY 在任何时候关闭传输连接. 例如, 客户端可能正开始发送一个新请求, 同时服务器决定关闭该 "空闲" 连接. 从服务器角度看, 连接是在空闲时被关闭; 但从客户端角度看, 一个请求正在进行中.

在可能的情况下, 服务器 SHOULD 维持持久连接, 并允许底层传输的流量控制机制解决临时过载, 而不是终止连接并期望客户端重试. 后一种技术可能加剧网络拥塞或服务器负载.

正在发送消息体的客户端 SHOULD 在传输请求期间监控网络连接上的错误响应. 如果客户端看到某个响应表明服务器不希望接收该消息体并正在关闭连接, 客户端 SHOULD 立即停止传输该消息体并关闭自身一侧的连接.

9.6. 拆除

"close" 连接选项被定义为一个信号, 表示发送方将在响应完成后关闭此连接. 当发送方打算关闭连接时, SHOULD 发送包含 "close" 连接选项的 Connection 头字段 ([HTTP] 第 7.6.1 节). 例如,

Connection: close

作为请求头字段时, 它表示这是客户端将在此连接上发送的最后一个请求; 而在响应中, 同一字段表示服务器将在响应消息完成后关闭此连接.

注意, 字段名 "Close" 是保留的, 因为将该名称用作头字段可能与 "close" 连接选项冲突.

发送 "close" 连接选项的客户端 MUST NOT 在该连接上发送更多请求 (包含 "close" 的那个请求之后), 并且 MUST 在读取与此请求对应的最终响应消息后关闭连接.

收到 "close" 连接选项的服务器 MUST 在发送对包含该 "close" 连接选项的请求的最终响应后, 发起连接关闭 (见下文). 服务器 SHOULD 在该连接上的最终响应中发送 "close" 连接选项. 服务器 MUST NOT 处理在该连接上收到的任何后续请求.

发送 "close" 连接选项的服务器 MUST 在发送包含该 "close" 连接选项的响应后, 发起连接关闭 (见下文). 服务器 MUST NOT 处理在该连接上收到的任何后续请求.

收到 "close" 连接选项的客户端 MUST 停止在该连接上发送请求, 并在读取包含 "close" 连接选项的响应消息后关闭该连接; 如果已经在该连接上发送了额外的流水线化请求, 客户端 SHOULD NOT 假定服务器会处理它们.

如果服务器立即关闭 TCP 连接, 客户端将无法读取最后一个 HTTP 响应的风险很高. 如果服务器在完全关闭的连接上收到来自客户端的额外数据, 例如客户端在收到服务器响应前发送的另一个请求, 服务器的 TCP 栈会向客户端发送 reset 包; 不幸的是, 该 reset 包可能在客户端的 HTTP 解析器读取并解释未确认输入缓冲区之前将其清除.

为避免 TCP reset 问题, 服务器通常分阶段关闭连接. 首先, 服务器通过只关闭读/写连接的写入侧来执行半关闭. 然后, 服务器继续从连接读取, 直到收到客户端对应的关闭, 或直到服务器有合理把握认为自己的 TCP 栈已经收到客户端对包含服务器最后响应的数据包的确认. 最后, 服务器完全关闭连接.

尚不清楚 reset 问题是否仅限于 TCP, 或也可能存在于其他传输连接协议中.

注意, 由客户端半关闭的 TCP 连接并不会定界一个请求消息, 也不意味着客户端不再关心响应. 一般而言, 不能依赖传输信号来表示边缘情况, 因为 HTTP/1.1 独立于传输.

9.7. TLS 连接启动

概念上, HTTP/TLS 只是通过 TLS [TLS13] 保护的连接发送 HTTP 消息.

HTTP 客户端同时充当 TLS 客户端. 它在适当端口发起到服务器的连接, 并发送 TLS ClientHello 以开始 TLS 握手. TLS 握手完成后, 客户端随后可以发起第一个 HTTP 请求. 所有 HTTP 数据 MUST 作为 TLS "application data" 发送, 但除此之外按 HTTP 的普通连接处理 (包括可能复用为持久连接).

9.8. TLS 连接关闭

TLS 在 (非错误) 连接关闭之前使用关闭警报交换来提供安全连接关闭; 见 [TLS13] 第 6.1 节. 收到有效关闭警报时, 实现可以确信该连接上不会再收到更多数据.

当实现知道自己已经发送或收到其关心的全部消息数据时, 通常是通过检测 HTTP 消息边界, 它可能通过发送关闭警报并在未等待收到对端对应关闭警报的情况下关闭连接, 从而产生一个 "不完整关闭".

不完整关闭不会质疑已经收到的数据的安全性, 但它可能表示后续数据已被截断. 由于 TLS 并不直接感知 HTTP 消息分帧, 因此必须检查 HTTP 数据本身以确定消息是否完整. 不完整消息的处理定义在第 8 节.

遇到不完整关闭时, 对于已收到以下任一内容的所有请求, 客户端 SHOULD 将其视为完成:

  1. Content-Length 头字段指定的足够多数据, 或
  2. 终止的零长度 chunk (当使用 chunked 的 Transfer-Encoding 时).

既没有 chunked 传输编码也没有 Content-Length 的响应, 只有在收到有效关闭警报时才是完整的. 将不完整消息视为完整可能使实现暴露于攻击.

检测到不完整关闭的客户端 SHOULD 优雅恢复.

客户端 MUST 在关闭连接前发送关闭警报. 不期望再收到任何数据的客户端 MAY 选择不等待服务器的关闭警报而直接关闭连接, 从而在服务器端产生不完整关闭.

服务器 SHOULD 准备好接收来自客户端的不完整关闭, 因为客户端通常能够定位服务器数据的结束位置.

服务器 MUST 尝试在关闭连接前与客户端发起关闭警报交换. 服务器 MAY 在发送关闭警报后关闭连接, 从而在客户端侧产生不完整关闭.