6. 连接管理
HTTP 消息传递独立于底层的传输层或会话层连接协议. HTTP 只假定存在一种可靠传输, 它按顺序投递请求, 并相应地按顺序投递响应. 把 HTTP 请求和响应结构映射到底层传输协议的数据单元上, 不在本规范的范围内.
如第 5.2 节所述, HTTP 交互要使用的具体连接协议由客户端配置和目标 URI 决定. 例如, "http" URI 方案 (第 2.7.1 节) 表明默认连接是基于 IP 的 TCP, 默认 TCP 端口为 80; 但客户端可能被配置为通过其他连接、端口或协议来使用代理.
HTTP 实现应当进行连接管理, 其中包括: 维护当前连接的状态、建立新连接或复用已有连接、处理在某条连接上收到的消息、检测连接失败, 以及关闭每条连接. 大多数客户端会并行维护多条连接, 包括对同一个服务器端点保持多于一条的连接. 大多数服务器被设计为维护成千上万条并发连接, 同时通过控制请求队列来实现公平使用并检测拒绝服务攻击.
6.1. Connection
"Connection" 头部字段允许发送方表明对当前连接所期望的控制选项. 为了避免使下游接收方产生混淆, 代理或网关 MUST 在转发消息之前移除或替换收到的任何连接选项.
当使用 Connection 之外的某个头部字段来提供针对或关于当前连接的控制信息时, 发送方 MUST 在 Connection 头部字段中列出相应的字段名. 代理或网关 MUST 在转发消息之前解析收到的 Connection 头部字段, 并针对该字段中的每个 connection-option, 从消息中移除与该作者选项同名的任何头部字段, 然后移除 Connection 头部字段本身 (或者用该中间方自己针对被转发消息的连接选项替换它).
因此, Connection 头部字段提供了一种声明式的方式, 用来区分仅针对直接接收方的头部字段 ("逐跳", hop-by-hop) 与针对链上所有接收方的字段 ("端到端", end-to-end); 这使消息能够自描述, 并使得未来的连接专属扩展得以部署, 而无需担心被较旧的中间方盲目转发.
Connection 头部字段的值具有如下文法:
Connection = 1#connection-option
connection-option = token
连接选项不区分大小写.
发送方 MUST NOT 发送与 "面向载荷所有接收方" 的头部字段相对应的连接选项. 例如, Cache-Control 从来不适合作为连接选项 ([RFC7234] 第 5.2 节).
连接选项并不总是与消息中存在的某个头部字段相对应, 因为当连接选项没有关联任何参数时, 可能不需要相应的连接专属头部字段. 相反, 如果收到某个连接专属头部字段却没有对应的连接选项, 通常表明该字段已被某个中间方不当转发, 接收方应当忽略它.
在定义新的连接选项时, 规范作者应当普查已有的头部字段名, 确保新的连接选项不会与已部署的头部字段同名. 定义一个新连接选项实质上就是把那个潜在的字段名保留下来, 用于携带与该连接选项相关的附加信息, 因为发送方把该字段名用于其他目的是不明智的.
"close" 连接选项的定义是: 供发送方表明这条连接将在响应完成之后被关闭. 例如,
Connection: close
出现在请求或响应头部字段中, 表明发送方将在当前请求/响应完成之后关闭连接 (第 6.6 节).
不支持持久连接的客户端 MUST 在每条请求消息中发送 "close" 连接选项.
不支持持久连接的服务器 MUST 在每条不具有 1xx (Informational) 状态码的响应消息中发送 "close" 连接选项.
6.2. 建立连接
通过各种传输层或会话层协议建立连接的过程, 超出本规范的范围. 每条连接只对应一条传输链路.
6.3. 持久性
HTTP/1.1 默认使用 "持久连接" (persistent connection), 允许在单条连接上承载多次请求和响应. "close" 连接选项用于表明某条连接在当前请求/响应之后不再保持. HTTP 实现 SHOULD 支持持久连接.
接收方根据最近收到的消息的协议版本和 Connection 头部字段 (如果有的话) 来判断一条连接是否持久:
- 如果存在 "close" 连接选项, 则该连接在当前响应之后不保持; 否则,
- 如果收到的协议是 HTTP/1.1 (或更晚), 则该连接在当前响应之后保持; 否则,
- 如果收到的协议是 HTTP/1.0, 存在 "keep-alive" 连接选项, 接收方不是代理, 且接收方愿意遵循 HTTP/1.0 的 "keep-alive" 机制, 则该连接在当前响应之后保持; 否则,
- 该连接将在当前响应之后关闭.
客户端 MAY 在持久连接上发送更多请求, 直到它发出或收到 "close" 连接选项, 或者收到不带 "keep-alive" 连接选项的 HTTP/1.0 响应为止.
为了保持持久性, 一条连接上的所有消息都需要具有自定义的消息长度 (即不是由连接关闭来界定的长度), 如第 3.3 节所述. 服务器 MUST 在发送响应之后读完整个请求消息主体, 或者关闭连接; 否则持久连接上的剩余数据会被误解释为下一个请求. 同样, 如果客户端打算为后续请求复用同一条连接, 它 MUST 读完整个响应消息主体.
代理服务器 MUST NOT 与 HTTP/1.0 客户端保持持久连接 (关于许多 HTTP/1.0 客户端所实现的 Keep-Alive 头部字段的问题与讨论, 见 [RFC2068] 第 19.7.1 节).
关于与 HTTP/1.0 客户端向后兼容的更多信息, 见附录 A.1.2.
6.3.1. 重试请求
连接可以在任何时候被关闭, 无论是有意还是无意. 实现应当预见到需要从异步关闭事件中恢复.
当入站连接被过早关闭时, 如果被中止的那一批请求全部使用幂等方法 ([RFC7231] 第 4.2.2 节), 客户端 MAY 打开一条新连接并自动重传这一批请求. 代理 MUST NOT 自动重试非幂等请求.
用户代理 MUST NOT 自动重试使用非幂等方法的请求, 除非它有某种手段可以知道该请求语义 (与所用方法无关) 实际上是幂等的, 或者有某种手段可以检测出原请求从未被施加. 例如, 一个 (通过设计或配置) 知道对某个给定资源的 POST 请求是安全的用户代理, 可以自动重复该请求. 同样, 一个专门针对版本控制仓库而设计的用户代理, 可能能够在连接失败后检查目标资源的修订版本、回退或修复任何被部分施加的改动, 然后自动重试失败的请求, 从而从部分失败状态中恢复.
客户端 SHOULD NOT 对失败的自动重试再次自动重试.
6.3.2. 流水线
支持持久连接的客户端 MAY 对其请求进行 "流水线化" (pipeline, 即发送多个请求而不等待每个响应). 如果一序列流水线化请求全部使用安全方法 ([RFC7231] 第 4.2.1 节), 服务器 MAY 并行处理它们, 但 MUST 按收到请求的相同顺序发送相应的响应.
对请求进行流水线化的客户端, 如果连接在收到所有相应响应之前关闭, SHOULD 重试未获应答的请求. 在连接失败 (即服务器在其最后一次完整响应中未显式关闭的连接) 之后重试流水线化请求时, 客户端 MUST NOT 在连接建立后立即进行流水线化, 因为先前流水线中第一个剩余的请求可能导致了错误响应, 而如果在一条过早关闭的连接上发送多个请求, 该错误响应可能再次丢失 (见第 6.6 节描述的 TCP 复位问题).
幂等方法 ([RFC7231] 第 4.2.2 节) 对流水线化很重要, 因为它们在连接失败之后可以被自动重试. 用户代理 SHOULD NOT 在非幂等方法之后继续进行流水线化, 直到收到该方法的最终响应状态码为止; 除非该用户代理有手段检测并恢复涉及该流水线序列的部分失败状态.
收到流水线化请求的中间方 MAY 在入站转发这些请求时对它们进行流水线化, 因为它可以依赖出站用户代理来判断哪些请求可以被安全地流水线化. 如果入站连接在收到响应之前失败, 那么当这些请求全部使用幂等方法时, 进行流水线化的中间方 MAY 尝试重试尚未收到响应的那一批请求; 否则, 该中间方 SHOULD 转发收到的任何响应, 然后关闭相应的出站连接, 以便出站用户代理能够相应地恢复.
6.4. 并发
客户端应当限制它对某个给定服务器所保持的同时打开的连接数量.
以前版本的 HTTP 给出过一个具体的连接数上限, 但人们发现这对许多应用并不实用. 因此, 本规范并不规定某个最大连接数, 而是鼓励客户端在打开多条连接时保持保守.
使用多条连接通常是为了避免 "队头阻塞" (head-of-line blocking) 问题: 某个需要大量服务器端处理和/或带有大载荷的请求会阻塞同一连接上的后续请求. 然而, 每条连接都会消耗服务器资源. 此外, 使用多条连接可能在拥塞网络中造成不良副作用.
请注意, 服务器可能拒绝它认为具有滥用性质或具有拒绝服务攻击特征的流量, 例如来自单个客户端的过多打开连接.
6.5. 失败与超时
服务器通常会有某个超时值, 超过该值后就不再保持一条非活动连接. 代理服务器可能把该值设得更高, 因为客户端很可能会通过同一代理服务器建立更多连接. 对持久连接的使用, 并不对客户端或服务器的该超时长度 (或其存在性) 施加任何要求.
希望超时的客户端或服务器 SHOULD 对该连接发起优雅关闭. 实现 SHOULD 持续监视已打开的连接是否有收到的关闭信号, 并作出适当的响应, 因为及时关闭连接的两端能够使已分配的系统资源得以回收.
客户端、服务器或代理 MAY 在任何时候关闭传输连接. 例如, 客户端可能刚开始发送一个新请求, 而服务器同时决定关闭这条 "空闲" 连接. 从服务器的角度看, 该连接是在空闲时被关闭的; 但从客户端的角度看, 有一个请求正在进行中.
服务器 SHOULD 尽可能维持持久连接, 并让底层传输的流控机制去解决暂时的过载, 而不是以 "客户端会重试" 为预期去终止连接. 后一种做法可能加剧网络拥塞.
发送消息主体的客户端 SHOULD 在传输请求期间监视网络连接是否有错误响应. 如果客户端看到某个响应表明服务器不希望接收该消息主体并且正在关闭连接, 客户端 SHOULD 立即停止传输主体并关闭它这一侧的连接.
6.6. 拆除连接
Connection 头部字段 (第 6.1 节) 提供了一个 "close" 连接选项, 发送方希望在当前请求/响应交换之后关闭连接时 SHOULD 发送它.
发送了 "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) 报文; 不幸的是, 该复位报文可能在客户端的 HTTP 解析器读取和解释客户端那些尚未确认的输入缓冲区之前就把它们抹掉.
为避免 TCP 复位问题, 服务器通常分阶段关闭连接. 首先, 服务器执行半关闭, 即只关闭这条读/写连接的写侧. 然后服务器继续从该连接读取, 直到收到客户端相应的关闭, 或者直到服务器有理由确信它自己的 TCP 栈已收到客户端对 "包含服务器最后一个响应的报文" 的确认. 最后, 服务器完全关闭该连接.
复位问题是否只存在于 TCP, 还是也可能出现在其他传输连接协议中, 目前尚不清楚.
6.7. Upgrade
"Upgrade" 头部字段的意图是提供一种简单机制, 用于在同一条连接上从 HTTP/1.1 过渡到其他某个协议. 客户端 MAY 在请求的 Upgrade 头部字段中发送一个协议列表, 以邀请服务器在发送最终响应之前按偏好递减的顺序切换到其中一个或多个协议. 如果服务器希望在该连接上继续使用当前协议, 它 MAY 忽略收到的 Upgrade 头部字段. Upgrade 不能用来强制要求改变协议.
Upgrade = 1#protocol
protocol = protocol-name ["/" protocol-version]
protocol-name = token
protocol-version = token
发送 101 (Switching Protocols) 响应的服务器 MUST 发送 Upgrade 头部字段, 以表明连接正被切换到哪个 (些) 新协议; 如果切换的是多层协议, 发送方 MUST 按层次由低到高的顺序列出这些协议. 服务器 MUST NOT 切换到客户端在相应请求的 Upgrade 头部字段中未表明的协议. 服务器 MAY 选择忽略客户端所表明的偏好顺序, 而依据其他因素 (例如请求的性质或服务器当前的负载) 来选择新协议.
发送 426 (Upgrade Required) 响应的服务器 MUST 发送 Upgrade 头部字段, 以按偏好递减的顺序表明可接受的协议.
服务器 MAY 在任何其他响应中发送 Upgrade 头部字段, 以通告: 当未来某个请求适宜时, 它支持按偏好递减的顺序升级到所列出的那些协议.
下面是一个由客户端发送的假设性示例:
GET /hello.txt HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
协议改变之后应用层通信的能力和性质完全取决于所选的新协议. 然而, 在发送 101 (Switching Protocols) 响应之后, 服务器应当立即继续响应最初的请求, 就如同它是在新协议内收到等价请求一样 (即: 协议改变之后服务器仍有一个尚未满足的请求, 并应当在不要求重复该请求的情况下满足它).
例如, 如果在某个 GET 请求中收到 Upgrade 头部字段, 且服务器决定切换协议, 它会先用 HTTP/1.1 响应一条 101 (Switching Protocols) 消息, 然后立即接着以新协议给出 "对目标资源的 GET 的等价响应". 这样就能把连接升级到与 HTTP 语义相同的协议, 而无需付出多一次往返的延迟代价. 除非收到的消息语义能够被新协议满足, 否则服务器 MUST NOT 切换协议; OPTIONS 请求可以被任何协议满足.
下面是对上述假设性请求的一个响应示例:
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: HTTP/2.0
[... data stream switches to HTTP/2.0 with an appropriate response
(as defined by new protocol) to the "GET /hello.txt" request ...]
发送 Upgrade 时, 发送方 MUST 同时发送一个包含 "upgrade" 连接选项的 Connection 头部字段 (第 6.1 节), 以防止 Upgrade 被可能未实现所列协议的中间方意外转发. 服务器 MUST 忽略在 HTTP/1.0 请求中收到的 Upgrade 头部字段.
客户端在完全发送完请求消息之前, 不能开始在该连接上使用升级后的协议 (即客户端不能在消息中途改变它正在发送的协议). 如果服务器同时收到 Upgrade 头部字段和带有 "100-continue" 期望的 Expect 头部字段 ([RFC7231] 第 5.1.1 节), 服务器 MUST 在发送 101 (Switching Protocols) 响应之前先发送 100 (Continue) 响应.
Upgrade 头部字段只适用于在既有连接之上切换协议; 它不能用来切换底层连接 (传输) 协议, 也不能用来把既有通信切换到另一条连接上. 对于那些目的, 更合适的做法是使用 3xx (Redirection) 响应 ([RFC7231] 第 6.4 节).
本规范只为超文本传输协议家族定义了协议名 "HTTP", 其依据是第 2.6 节的 HTTP 版本规则以及本规范的未来更新. 其他 token 应当按照第 8.6 节定义的登记流程向 IANA 登记.