7. Routing HTTP Messages (HTTP消息路由)
HTTP请求消息路由由每个客户端根据目标资源,客户端的代理配置以及入站连接的建立或重用来确定. 相应的响应路由沿着相同的连接链返回到客户端.
7.1. Determining the Target Resource (确定目标资源)
尽管HTTP用于各种应用程序,但大多数客户端依赖与通用Web浏览器相同的资源识别机制和配置技术. 即使通信选项在客户端的配置中是硬编码的,我们也可以将它们的组合效果视为URI引用(第4.1节).
URI引用被解析为其绝对形式以获得"目标URI" (target URI). 目标URI排除引用的片段组件(如果有),因为片段标识符保留用于客户端处理([URI],第3.5节).
要对"目标资源" (target resource) 执行操作,客户端发送包含其解析的目标URI的足够组件的请求消息,以使接收者能够识别该相同资源. 由于历史原因,解析的目标URI组件(统称为"请求目标" (request target))在消息控制数据和Host头部字段(第7.2节)中发送.
有两种不寻常的情况,请求目标组件采用特定于方法的形式:
- 对于CONNECT(第9.3.6节),请求目标是隧道目的地的主机名和端口号,由冒号分隔.
- 对于OPTIONS(第9.3.7节),请求目标可以是单个星号("*").
有关详细信息,请参阅相应的方法定义. 这些形式不得 (MUST NOT) 与其他方法一起使用.
在接收到客户端的请求后,服务器根据其本地配置和传入连接上下文从接收到的组件重构目标URI. 此重构特定于每个主要协议版本. 例如,[HTTP/1.1]的第3.3节定义了服务器如何确定HTTP/1.1请求的目标URI.
注意: 先前的规范将重组的目标URI定义为一个独特的概念,"有效请求URI" (effective request URI).
7.2. Host and :authority
请求中的"Host"头部字段提供来自目标URI的主机和端口信息,使源服务器能够在为多个主机名提供请求服务时区分资源.
在HTTP/2 [HTTP/2]和HTTP/3 [HTTP/3]中,Host头部字段在某些情况下被请求控制数据的":authority"伪头部字段取代.
Host = uri-host [ ":" port ] ; Section 4
目标URI的权威信息对于处理请求至关重要. 用户代理必须 (MUST) 在请求中生成Host头部字段,除非它将该信息作为":authority"伪头部字段发送. 发送Host的用户代理应该 (SHOULD) 将其作为请求头部区段中的第一个字段发送.
例如,对源服务器的GET请求 http://www.example.org/pub/WWW/ 将以以下内容开始:
GET /pub/WWW/ HTTP/1.1
Host: www.example.org
由于主机和端口信息充当应用程序级路由机制,它是恶意软件寻求污染共享缓存或将请求重定向到意外服务器的常见目标. 如果拦截代理依赖主机和端口信息将请求重定向到内部服务器或用作共享缓存中的缓存键,而不首先验证拦截的连接是否针对该主机的有效IP地址,则拦截代理特别容易受到攻击.
7.3. Routing Inbound Requests (路由入站请求)
一旦确定了目标URI及其源,客户端就决定是否需要网络请求来实现所需的语义,如果需要,该请求将被定向到哪里.
7.3.1. To a Cache (到缓存)
如果客户端有缓存[CACHING]并且请求可以由它满足,则请求通常首先定向到那里.
7.3.2. To a Proxy (到代理)
如果请求未被缓存满足,则典型的客户端将检查其配置以确定是否使用代理来满足请求. 代理配置取决于实现,但通常基于URI前缀匹配,选择性权威匹配或两者,并且代理本身通常由"http"或"https" URI标识.
如果"http"或"https"代理适用,客户端通过建立(或重用)到该代理的连接然后向其发送包含与客户端目标URI匹配的请求目标的HTTP请求消息来入站连接.
7.3.3. To the Origin (到源)
如果没有适用的代理,典型的客户端将调用处理程序例程(特定于目标URI的方案)以获得对标识资源的访问. 如何实现这一点取决于目标URI方案并由其关联规范定义.
第4.3.2节定义了如何通过建立(或重用)到标识的源服务器的入站连接然后向其发送包含与客户端目标URI匹配的请求目标的HTTP请求消息来获得对"http"资源的访问.
第4.3.3节定义了如何通过建立(或重用)到对标识的源具有权威性的源服务器的入站安全连接然后向其发送包含与客户端目标URI匹配的请求目标的HTTP请求消息来获得对"https"资源的访问.
7.4. Rejecting Misdirected Requests (拒绝误导的请求)
一旦服务器接收到请求并解析到足以确定其目标URI,服务器就决定是自己处理请求,将请求转发到另一台服务器,将客户端重定向到不同的资源,用错误响应或断开连接. 此决定可能受到有关请求或连接上下文的任何内容的影响,但特别针对服务器是否已配置为处理该目标URI的请求以及连接上下文是否适合该请求.
例如,请求可能被故意或意外地误导,使得接收到的Host头部字段中的信息与连接的主机或端口不同. 如果连接来自受信任的网关,这种不一致可能是预期的; 否则,它可能表明试图绕过安全过滤器,欺骗服务器传递非公开内容或污染缓存. 有关消息路由的安全注意事项,请参阅第17节.
除非连接来自受信任的网关,否则如果未满足目标URI的任何特定于方案的要求,源服务器必须 (MUST) 拒绝请求. 特别是,除非通过对该目标URI的源有效的证书保护的连接接收到"https"资源的请求,否则必须 (MUST) 拒绝该请求,如第4.2.2节所定义.
响应中的421(Misdirected Request)状态码表示源服务器已拒绝请求,因为它似乎被误导了(第15.5.20节).
7.5. Response Correlation (响应关联)
连接可能用于多个请求/响应交换. 用于在请求和响应消息之间关联的机制取决于版本; HTTP的某些版本使用消息的隐式排序,而其他版本使用显式标识符.
所有响应,无论状态码如何(包括临时响应),都可以在接收到请求后的任何时间发送,即使请求尚未完成. 响应可以在其相应的请求完成之前完成(第6.1节). 同样,客户端不期望等待任何特定时间来获得响应. 如果在合理的时间段内未收到响应,客户端(包括中间人)可能会放弃请求.
在仍在发送关联请求时接收到响应的客户端应该 (SHOULD) 继续发送该请求,除非它接收到相反的明确指示(例如,参见[HTTP/1.1]的第9.5节和[HTTP/2]的第6.4节).
7.6. Message Forwarding (消息转发)
如第3.7节所述,中间人可以在HTTP请求和响应的处理中发挥各种作用. 一些中间人用于提高性能或可用性. 其他用于访问控制或过滤内容. 由于HTTP流具有类似于管道和过滤器架构的特征,因此中间人可以增强(或干扰)流的任一方向的程度没有固有限制.
即使无法识别协议元素(例如,新方法,状态码或字段名称),中间人也应该转发消息,因为这保留了下游接收者的可扩展性.
不充当隧道的中间人必须 (MUST) 实现Connection头部字段,如第7.6.1节所规定,并排除仅用于传入连接的字段被转发.
中间人不得 (MUST NOT) 将消息转发给自己,除非它受到保护免受无限请求循环. 通常,中间人应该识别自己的服务器名称,包括任何别名,本地变体或文字IP地址,并直接响应此类请求.
HTTP消息可以作为流解析以进行增量处理或向下游转发. 然而,发送者和接收者不能依赖部分消息的增量传递,因为某些实现将为了网络效率,安全检查或内容转换而缓冲或延迟消息转发.
7.6.1. Connection
"Connection"头部字段允许发送者列出当前连接所需的控制选项.
Connection = #connection-option
connection-option = token
连接选项不区分大小写.
当除Connection之外的字段用于为当前连接提供控制信息或关于当前连接的信息时,发送者必须 (MUST) 在Connection头部字段中列出相应的字段名称. 请注意,某些HTTP版本禁止使用字段来提供此类信息,因此不允许Connection字段.
中间人必须 (MUST) 在转发消息之前解析接收到的Connection头部字段,并且对于此字段中的每个connection-option,从消息中删除与connection-option同名的任何头部或尾部字段,然后删除Connection头部字段本身(或用中间人自己的控制选项替换它以用于转发的消息).
因此,Connection头部字段提供了一种声明性方式来区分仅用于直接接收者("逐跳")的字段和用于链上所有接收者("端到端")的字段,使消息自描述并允许部署未来的特定于连接的扩展,而不必担心它们会被旧的中间人盲目转发.
此外,中间人应该 (SHOULD) 删除或替换已知在转发之前需要删除的字段,无论它们是否作为connection-option出现,在应用这些字段的语义之后. 这包括但不限于:
- Proxy-Connection([HTTP/1.1]的附录C.2.2)
- Keep-Alive([RFC2068]的第19.7.1节)
- TE(第10.1.4节)
- Transfer-Encoding([HTTP/1.1]的第6.1节)
- Upgrade(第7.8节)
发送者不得 (MUST NOT) 发送与用于所有内容接收者的字段对应的连接选项. 例如,Cache-Control从不适合作为连接选项([CACHING]的第5.2节).
连接选项并不总是对应于消息中存在的字段,因为如果没有与连接选项关联的参数,则可能不需要特定于连接的字段. 相反,在没有相应连接选项的情况下接收到的特定于连接的字段通常表示该字段已被中间人不当转发,并且应该被接收者忽略.
在定义不对应于字段的新连接选项时,规范作者应该无论如何保留相应的字段名称,以避免以后的冲突. 此类保留字段名称在"超文本传输协议(HTTP)字段名称注册表"(第16.3.1节)中注册.
7.6.2. Max-Forwards
"Max-Forwards"头部字段提供了一种机制,与TRACE(第9.3.8节)和OPTIONS(第9.3.7节)请求方法一起限制请求被代理转发的次数. 当客户端尝试跟踪似乎失败或在链中循环的请求时,这可能很有用.
Max-Forwards = 1*DIGIT
Max-Forwards值是一个十进制整数,指示此请求消息可以被转发的剩余次数.
接收到包含Max-Forwards头部字段的TRACE或OPTIONS请求的每个中间人必须 (MUST) 在转发请求之前检查并更新其值. 如果接收到的值为零(0),中间人不得 (MUST NOT) 转发请求; 相反,中间人必须 (MUST) 作为最终接收者响应. 如果接收到的Max-Forwards值大于零,中间人必须 (MUST) 在转发的消息中生成更新的Max-Forwards字段,其字段值为a)接收到的值减一(1)或b)接收者对Max-Forwards支持的最大值中的较小者.
接收者可以 (MAY) 忽略与任何其他请求方法一起接收到的Max-Forwards头部字段.
7.6.3. Via
"Via"头部字段指示用户代理和服务器之间(在请求上)或源服务器和客户端之间(在响应上)的中间协议和接收者的存在,类似于电子邮件中的"Received"头部字段([RFC5322]的第3.6.7节). Via可用于跟踪消息转发,避免请求循环以及识别请求/响应链上发送者的协议能力.
Via = #( received-protocol RWS received-by [ RWS comment ] )
received-protocol = [ protocol-name "/" ] protocol-version
; see Section 7.8
received-by = pseudonym [ ":" port ]
pseudonym = token
Via字段值的每个成员代表已转发消息的代理或网关. 每个中间人附加有关如何接收消息的自己的信息,使得最终结果根据转发接收者的序列排序.
代理必须 (MUST) 在其转发的每条消息中发送适当的Via头部字段,如下所述. HTTP到HTTP网关必须 (MUST) 在每个入站请求消息中发送适当的Via头部字段,并且可以 (MAY) 在转发的响应消息中发送Via头部字段.
对于每个中间人,received-protocol指示消息的上游发送者使用的协议和协议版本. 因此,Via字段值记录请求/响应链的公布协议能力,使它们对下游接收者保持可见; 这对于确定在响应中或在以后的请求中使用哪些向后不兼容的功能可能是安全的很有用,如第2.5节所述. 为简洁起见,当接收到的协议是HTTP时,省略protocol-name.
received-by部分通常是随后转发消息的接收者服务器或客户端的主机和可选端口号. 然而,如果真实主机被认为是敏感信息,发送者可以 (MAY) 用假名替换它. 如果未提供端口,接收者可以 (MAY) 将其解释为在received-protocol的默认端口(如果有)上接收.
发送者可以 (MAY) 生成注释以识别每个接收者的软件,类似于User-Agent和Server头部字段. 然而,Via中的注释是可选的,接收者可以 (MAY) 在转发消息之前删除它们.
例如,可以从HTTP/1.0用户代理向内部代理(代号"fred")发送请求消息,该代理使用HTTP/1.1将请求转发到p.example.net的公共代理,该代理通过将其转发到www.example.com的源服务器来完成请求. www.example.com接收到的请求将具有以下Via头部字段:
Via: 1.0 fred, 1.1 p.example.net
用作通过网络防火墙的门户的中间人不应该 (SHOULD NOT) 转发防火墙区域内主机的名称和端口,除非明确启用这样做. 如果未启用,此类中间人应该 (SHOULD) 用该主机的适当假名替换防火墙后面的任何主机的每个received-by主机.
中间人可以 (MAY) 将Via头部字段列表成员的有序子序列组合成单个成员,如果条目具有相同的received-protocol值. 例如,
Via: 1.0 ricky, 1.1 ethel, 1.1 fred, 1.0 lucy
可以折叠为
Via: 1.0 ricky, 1.1 mertz, 1.0 lucy
发送者不应该 (SHOULD NOT) 组合多个列表成员,除非它们都在同一组织控制下并且主机已被假名替换. 发送者不得 (MUST NOT) 组合具有不同received-protocol值的成员.
7.7. Message Transformations (消息转换)
一些中间人包括用于转换消息及其内容的功能. 例如,代理可能会在图像格式之间转换以节省缓存空间或减少慢速链路上的流量. 然而,当这些转换应用于用于关键应用程序(如医学成像或科学数据分析)的内容时,可能会出现操作问题,特别是当使用完整性检查或数字签名来确保接收到的内容与原始内容相同时.
如果HTTP到HTTP代理被设计或配置为以语义上有意义的方式修改消息(即,除了正常HTTP处理所需的修改之外,以对原始发送者有意义或对下游接收者可能有意义的方式更改消息的修改),则称为"转换代理" (transforming proxy). 例如,转换代理可能充当共享注释服务器(修改响应以包含对本地注释数据库的引用),恶意软件过滤器,格式转码器或隐私过滤器. 这种转换被假定为选择代理的任何客户端(或客户端组织)所期望的.
如果代理接收到具有非完全限定域名的主机名的目标URI,它可以 (MAY) 在转发请求时将自己的域添加到接收到的主机名. 如果目标URI包含完全限定域名,代理不得 (MUST NOT) 更改主机名.
代理不得 (MUST NOT) 修改接收到的目标URI的"absolute-path"和"query"部分,除非该转发协议要求,否则在将其转发到下一个入站服务器时. 例如,通过HTTP/1.1将请求转发到源服务器的代理将用"/"([HTTP/1.1]的第3.2.1节)或"*"([HTTP/1.1]的第3.2.4节)替换空路径,具体取决于请求方法.
代理不得 (MUST NOT) 转换包含no-transform缓存指令的响应消息的内容(第6.4节)([CACHING]的第5.2.2.6节). 请注意,这不适用于不更改内容的消息转换,例如添加或删除传输编码([HTTP/1.1]的第7节).
代理可以 (MAY) 转换不包含no-transform缓存指令的消息的内容. 转换200(OK)响应内容的代理可以通过将响应状态码更改为203(Non-Authoritative Information)(第15.3.4节)来通知下游接收者已应用转换.
代理不应该 (SHOULD NOT) 修改提供有关通信链端点,资源状态或所选表示(内容除外)的信息的头部字段,除非字段的定义特别允许此类修改或认为修改对于隐私或安全是必要的.
7.8. Upgrade (升级)
"Upgrade"头部字段旨在提供一种简单的机制,用于在同一连接上从HTTP/1.1过渡到某些其他协议.
客户端可以 (MAY) 在请求的Upgrade头部字段中发送协议名称列表,以邀请服务器在发送最终响应之前切换到一个或多个命名协议,按降序优先级排列. 如果服务器希望继续在该连接上使用当前协议,则可以 (MAY) 忽略接收到的Upgrade头部字段. Upgrade不能用于坚持协议更改.
Upgrade = #protocol
protocol = protocol-name ["/" protocol-version]
protocol-name = token
protocol-version = token
尽管协议名称使用首选大小写注册,但接收者在将每个protocol-name与支持的协议匹配时应该 (SHOULD) 使用不区分大小写的比较.
发送101(Switching Protocols)响应的服务器必须 (MUST) 发送Upgrade头部字段以指示连接正在切换到的新协议; 如果正在切换多个协议层,发送者必须 (MUST) 按层升序列出协议. 服务器不得 (MUST NOT) 切换到客户端在相应请求的Upgrade头部字段中未指示的协议. 服务器可以 (MAY) 选择忽略客户端指示的优先级顺序,并根据其他因素(例如请求的性质或服务器上的当前负载)选择新协议.
发送426(Upgrade Required)响应的服务器必须 (MUST) 发送Upgrade头部字段以指示可接受的协议,按降序优先级排列.
服务器可以 (MAY) 在任何其他响应中发送Upgrade头部字段,以宣传它实现了对升级到列出的协议的支持,按降序优先级排列,当适合未来的请求时.
以下是客户端发送的假设示例:
GET /hello HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: websocket, 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: websocket
[... data stream switches to websocket with an appropriate response
(as defined by new protocol) to the "GET /hello" request ...]
Upgrade的发送者还必须 (MUST) 在Connection头部字段(第7.6.1节)中发送"Upgrade"连接选项,以通知中间人不要转发此字段. 在HTTP/1.0请求中接收到Upgrade头部字段的服务器必须 (MUST) 忽略该Upgrade字段.
客户端在完全发送请求消息之前不能开始在连接上使用升级的协议(即,客户端不能在消息中间更改它正在发送的协议). 如果服务器同时接收到Upgrade和带有"100-continue"期望(第10.1.1节)的Expect头部字段,服务器必须 (MUST) 在发送101(Switching Protocols)响应之前发送100(Continue)响应.