切换深浅色
第 9 章 HTTP/1.1:语义、消息定界与持久连接
第 7 章已经使用 HTTP 请求行和 CONNECT。本章把普通消息也放回完整规则:目标怎样确定,头部怎样决定内容边界,动态内容怎样结束,连接怎样承载下一条消息。HTTP 的语义主要依据 RFC 9110,HTTP/1.1 线上语法与定界依据 RFC 9112;两个层面的要求不能互相代替。
9.1 一条请求表达操作,一条响应报告这次操作的结果
HTTP 把访问建模成对资源发起请求。资源可以是网页、某条消息、实时计算结果或由服务端管理的其它对象,不必对应磁盘文件。URI 标识资源,方法表达操作,字段提供请求条件与元信息,内容则承载需要传递的数据。
例如客户端希望创建一条文字消息,可以构造以下核心请求:
text
POST /messages HTTP/1.1\r\n
Host: example.com\r\n
Content-Type: text/plain; charset=utf-8\r\n
Content-Length: 6\r\n
\r\n
你好正文是六个 UTF-8 字节 e4 bd a0 e5 a5 bd,不含排版换行;Content-Length 表示字节而非两个汉字。服务器怎样保存它,属于资源语义;HTTP 消息只明确传送操作、目标和内容。服务器可以用201报告创建成功,并用Location指示新资源,也可以根据输入与权限返回其它状态。
方法的语义不等于字段语法。GET 通常用于取得资源表示,POST 请求资源按自身语义处理提交内容,PUT 表达创建或替换目标资源状态,DELETE 表达移除与目标资源的关联。不能把这几个名称简单当作服务器必然怎样操作文件的函数名。应用还需要定义资源本身的行为。RFC 9110 §9。
两项方法属性影响重试。安全方法的定义是客户端没有要求改变服务器状态,并不禁止服务端记日志等附带动作。幂等表示同一请求执行多次的预期作用,与执行一次相同,不要求每次响应状态或内容完全一致。对同一资源重复DELETE,第一次可能删除成功、第二次报告资源不存在,仍可符合幂等作用;重复POST创建新消息则可能生成两条。
这些属性不让网络故障变得可判定。客户端写完请求后断线,不能知道服务器究竟未收到、已执行但未返回,还是响应在途中丢失。方法语义帮助决定是否适合自动重试,但不能把传输失败转化为“服务器一定没做事”。对于非幂等操作,需要应用另有防重复机制或取得足够执行证据。RFC 9110 §9.2。
状态码的类别也有明确分工:1xx表示信息性响应,2xx表示成功,3xx提供重定向等后续处理信息,4xx表示请求侧错误类别,5xx表示服务器侧错误类别。分类不能代替具体状态语义,更不能把所有非200都视为连接失败。服务器已经返回可解析404,与TCP根本未连上是完全不同的事实。
HTTP 的“无状态”主要指每条请求语义可独立理解,不是服务器不能保存会话,也不是每次必须新建TCP。账户登录状态、Cookie、缓存和后端数据库都可以跨请求存在;持久连接又可以承载多次请求响应。应把应用业务状态、HTTP 消息配对和传输连接状态分开。
同一个逻辑HTTP交换也可以映射到HTTP/2或HTTP/3,它们保留大量方法、状态和字段语义,却采用不同的线上承载。后文比较传输性能时,要分清是操作本身改变了,还是表达同一操作所用的帧、流和连接发生变化。
9.2 请求目标与 Host:连接到同一 IP,不等于访问同一服务
一个 IP 可以承载多个域名,一个域名也可以解析到多个地址。TCP 连接只告诉服务端双方的网络端点,不能完整表达客户端想访问哪个网站及资源。HTTP 因而需要请求目标与主机信息,共同确定资源标识。
HTTP/1.1 有四种 request-target:
| 形式 | 例子 | 典型用途 |
|---|---|---|
| origin-form | /a?x=1 | 向源站请求资源 |
| absolute-form | http://example.com/a?x=1 | 显式代理请求转发 |
| authority-form | example.com:443 | CONNECT 的隧道端点 |
| asterisk-form | * | 对服务器整体的 OPTIONS |
它们不是同一格式的四种任意写法,而是受方法和接收角色约束的语法。直接访问一个HTTP站点、URI路径为空时,origin-form使用 /;CONNECT则要求显式端口,不附资源路径。接收者不能把任意一段文本都猜成这四种之一。RFC 9112 §3.2。
Host 的字段值包含主机,必要时还含端口,而不是URI方案或路径。例如 Host: example.com:8080。HTTP/1.1 客户端必须发送Host,服务器对于缺少Host、多个Host字段行或不合法Host值的请求,要按规范拒绝。Host不能只是“看起来有一个名字就行”的可选提示。RFC 9112 §3.2。
当 absolute-form 已经表达主机时,代理以该请求目标重建Host,不把冲突的收到字段当作另一份独立目标。authority-form则直接提供CONNECT的端点。对于普通origin-form,服务端还需结合Host及可信连接上下文恢复完整目标URI,包括方案。若TLS在前置网关终止,后端不能仅因它收到明文TCP就断言原请求一定是http,也不能无条件相信任何客户端伪造的“原始方案”字段;上下文传递要有明确的信任边界。
URI内部还有不同的语义分隔。路径、查询和片段不是可以任意拼接的一个字符串。http://example.com/a?x=1#part 的片段 part 供客户端处理,常规HTTP请求目标不包含它;服务端收到的是与 /a?x=1 对应的信息。路径中的百分号编码表达特定字节,不能在尚未确定结构前随意解码并重新分隔。RFC 3986 §2—3。
例如 %2F 代表斜杠字节,但把某个编码后的路径片段解码成 / 再当结构分隔符,可能改变路由匹配结果。不同应用可以有不同路径处理规则,代理和后端必须一致理解其约定,不能认为“解码越彻底越兼容”。类似地,域名比较、URI比较和业务资源比较各有规则,不能靠把整条URI转为小写统一处理。
HTTP路由身份与TLS身份仍要分开。TLS握手可能用于选择并验证某个服务身份,HTTP Host用于具体消息的目标处理;二者相关,但各自处于不同层次。客户端访问一个数值地址的TCP连接,可以在HTTP里请求某个域名;反过来,字段写了预期域名也不能弥补不正确的TLS验证。
因此,一条请求最终去了哪里,需要沿“原始URI→请求目标形式→主机与可信上下文→服务端资源映射”追踪。只检查TCP目标地址,或只看一条Host字段,都不足以解释全部路由行为。消息定界问题则在这个资源识别之后继续存在:找到了正确网站,也还必须准确知道这一条请求占哪些字节。
9.3 Content-Length:数的是传输内容字节,不是显示字符
接收方看到头部结束以后,还需要知道应该消费多少内容字节。对于没有传输编码、也不属于无正文或隧道等特殊情形的HTTP/1.1消息,有效Content-Length提供这一边界。数值是非负十进制文本,而不是二进制整数;Content-Length: 6中的数字在线上是ASCII 36。
在9.1的请求中,两个汉字编码后有六字节,因此长度为6。如果把长度写成2,通用HTTP解析器会认为只应取两个内容字节,剩下四字节落在下一消息位置;如果写成8而只发送六字节,接收方会等待缺失的两个字节,连接结束时则是不完整消息。错误的长度会破坏整个持久连接的消息边界,不只是让一个文本显示异常。
Content-Type描述内容的媒体类型,字符集参数帮助内容解释;它不替代消息定界。下列六字节二进制内容同样可以用长度6传送:
text
Content-Type: application/octet-stream\r\n
Content-Length: 6\r\n
\r\n
[内容十六进制] 00 0d 0a 0d 0a ff方括号行是本书说明,不是报文中的文本。内容里出现的 0d 0a 0d 0a 不会结束这条消息,因为解析器已经离开头部状态;它只需在内容起点后消费六字节。零字节也不表示字符串结束,ff不需要按UTF-8解释。
内容编码又引入一层表示。若服务器先用gzip压缩表示,并通过 Content-Encoding: gzip 声明,Content-Length在适用时计算的是已经压缩、实际作为HTTP内容传送的字节数,不是解压后的文字大小。解压可能显著增大输出,因此限制线上消息长度不能替代对解码输出与CPU成本的限制。RFC 9110 §8.4、§8.6。
相反,chunked属于后文的传输编码,会在HTTP/1.1消息体中加入块边界,其规则不能与Content-Encoding混为一谈。HTTP内容、经过内容编码的表示、传输编码后的消息体和底层TCP字节,分别属于不同的计量位置。长度字段要按其定义的那一层解释。
接收实现也不应因为知道总长度就一次性分配相同大小的缓冲。它可以维持剩余计数r,每交付k个内容字节便令 r←r−k,r到零时消息体完整。每次k受当前可用数据与r共同限制,超出本条消息范围的字节留给下一阶段。是否把完整内容先存储再交应用,是资源和应用语义选择,不是Content-Length的必然要求。
十进制解析需要防溢出。HTTP没有规定所有内容必须落在一个很小的整数范围;实现应检查数字合法性、自身可处理上限,以及转换时是否超出数值表示。一个极长数字串即使数学上表达非负数,也不能让解析器发生整数回绕后按一个很小长度继续。
长度零表示适用消息的内容为空,不表示TCP连接必须关闭,也不表示没有响应头。反过来,没有Content-Length不能普遍推导长度零:请求和响应有不同默认规则,特殊方法与状态又有更高优先级。HEAD响应中的长度可能描述假设GET会发送的内容,却不会让HEAD实际带上该内容;成功CONNECT更会直接切换隧道。这些情况必须放进完整优先规则统一解释,而不能只写一条“有长度就读长度”的算法。
依据是 RFC 9112 §6.2—6.3。同一消息中重复或冲突的长度、以及长度和传输编码同时出现的处理,将在9.5单独说明,避免把一个正常例子的定界方法误作对所有输入的许可。
9.4 chunked:总长未知时,分块声明局部长度
动态生成的响应可能在发送头部时还不知道最终有多少字节。若必须先缓存完整结果才能得到Content-Length,就无法尽早传送已经产生的内容。HTTP/1.1的chunked传输编码允许发送者逐块声明长度,最后显式标记结束,而不依赖关闭整个连接来定界。
每个非零块由三部分组成:十六进制块长及行结束、对应数量的数据、再一个行结束。块长只数块数据,不包括长度文本、扩展和前后CRLF。消息体最后有零块,随后是可选尾部字段区,再以空行结束。RFC 9112 §7.1。
设头部已声明 Transfer-Encoding: chunked,完整的编码后消息体为:
text
4\r\nWiki\r\n5\r\npedia\r\n0\r\n\r\n| 编码范围 | 内容 | 实际作用 |
|---|---|---|
[0,3) | 4加CRLF | 声明第一块四字节 |
[3,7) | Wiki | 四字节块数据 |
[7,9) | CRLF | 第一块结束语法 |
[9,12) | 5加CRLF | 声明第二块五字节 |
[12,17) | pedia | 五字节块数据 |
[17,19) | CRLF | 第二块结束语法 |
[19,22) | 0加CRLF | 零块,无块数据 |
[22,24) | CRLF | 空尾部字段区结束 |
线上消息体二十四字节,解码后的HTTP内容只有九字节 Wikipedia。数字4和5在这里只是恰好与十进制表示相同;若一块有十字节,长度可以写为 a,不是十进制文本 10所表示的十六字节。Content-Length与chunk-size采用不同的数制,解析器不能复用错误的转换规则。
零块不是“读到字符0就完了”。接收方先完整读取零块行,再处理尾部字段区及其结束空行;尾部也受大小、字段语法和允许字段的约束。若在上述第22字节之前连接结束,不能称为已收到一个正常完整chunked消息。
尾部字段用于内容之后才得到的、允许放在此位置的元信息。为说明结构,可以定义一个教学字段Example-Count,在头部用 Trailer: Example-Count 预告,结尾写:
text
0\r\nExample-Count: 9\r\n\r\nExample-Count是本节自定义说明字段,不声称它是某项现有校验规范。实际应用只能依赖已规定语义、允许位于尾部且对端能处理的字段。不能把认证、路由或消息定界所需的重要头部悄悄移到尾部,因为接收者可能在到达尾部之前已经作出相关决定,或者不向应用暴露这些尾部信息。RFC 9110 §6.5。
块长行还可以带扩展,如分号引入的块扩展。扩展属于块语法,不计入块数据长度;接收者要能按语法跳过不认识的扩展,并限制总体开销。它不能简单地读取到第一个非十六进制字符就立即把该字符当作正文,也不能用无限长扩展拖住解析和内存。
chunked的块边界与TCP分段无关,也不保证保留应用最初的写入边界。代理可以解块后重新分块,使九字节内容变成一块或多块,只要按规则维护传输编码及其它相关语义。应用若依靠某个HTTP块恰好等于一条业务消息,就把承载实现的分块误当成业务协议定界。
它同样不保证接收应用立即看到每一块。中间代理可能缓存,框架可能按自己的条件交付,内容解码也可能需要累计输入。chunked解决的是未知总长消息的语法定界;是否实时发送、怎样刷出缓冲、如何背压,是相邻但不同的工程问题。后面的XHTTP正需要同时分析HTTP语法允许的流式传送与中间设备的实际缓存行为。
9.5 消息体长度的优先规则:不能先搜索 Content-Length
HTTP/1.1接收者决定消息体边界时,必须同时知道请求还是响应;若是响应,还要知道对应请求方法与当前状态码。只读取头部字段表而没有请求上下文,无法正确处理HEAD或CONNECT。这是协议状态参与字节解析的直接例子。
下表按RFC 9112 §6.3的顺序表达判定,先命中的特殊情况优先。它确定边界,不免除字段语法验证、禁止字段和错误处理等其它义务。
| 优先位置 | 条件 | 边界或动作 |
|---|---|---|
| 1 | HEAD的响应,或状态为1xx、204、304 | 头部区结束即结束该HTTP消息,不含消息体与尾部区 |
| 2 | CONNECT的2xx响应 | 头部结束后切换隧道,忽略其中CL和TE |
| 3 | 同时收到Transfer-Encoding与Content-Length | TE优先,但这是危险的异常输入,应按错误处理;转发者先移除CL并正确处理TE |
| 4 | 有TE,最后一个编码是chunked | 按chunked语法读到完整结束 |
| 4的另一分支 | 响应有TE,最后不是chunked | 以服务端关闭连接定界 |
| 4的请求分支 | 请求有TE,最后不是chunked | 不能可靠定界,服务端返回400并关闭 |
| 5 | 无TE而CL不合法 | 不可恢复的定界错误;仅存在严格的相同合法值列表例外 |
| 6 | 无TE而有合法CL | 消费恰好声明的字节;提前结束或超时视为不完整并关闭 |
| 7 | 请求未命中以上情况 | 消息体长度为零 |
| 8 | 其余响应 | 内容一直到服务端关闭连接 |
这里用CL、TE分别简写两个字段名,不引入新的字段。尤其不能把第3项记成“二者同时出现也没关系,只看TE”。发送者本就不得在包含TE的消息里发送CL。接收服务器可以拒绝这种请求,或只按TE处理,但响应后必须关闭连接;中间节点选择转发时也不得把冲突CL原样留给下游。RFC 9112 §6.1—6.3。
第1项容易被长度字段误导。HEAD响应可以包含描述假设GET表示大小的Content-Length,却没有那些正文;304也可能按规定提供表示长度信息。204和1xx则有禁止生成CL等字段的额外要求。边界都是头部结束,并不意味着这几类响应允许的字段集合完全相同。
1xx还是信息性响应,之后可能有最终响应,客户端不能因为它不含正文就把整次请求当成完成。101涉及已经协商的协议切换,后面字节由升级协议解释,而不是普通HTTP响应内容。205虽然按语义不得生成内容,却不在表中第一类特殊定界清单内;发送者仍应提供适用的零长定界,不能擅自发未定界字节。
第7与第8项是请求和响应最重要的默认差异。请求没有CL或TE时,消息体长度是零,不能一直读到客户端关闭才决定POST内容;未声明长度的普通响应在满足前置条件后可以靠关闭定界。靠关闭结束响应不能在同一连接上继续下一条消息,也难以区分正常完成与某些中途失败,所以标准建议尽可能用显式长度或编码定界。
重复CL也需要确定规则。Content-Length: 5, 5以及合并后等价的相同合法值,存在按单值处理的规范例外;5, 6不在例外内。不能用“取第一项”“取最后一项”在冲突长度中任选。无TE且CL不可恢复地非法时,服务器对请求返回400并关闭;代理对上游非法响应关闭上游、丢弃响应并给客户端502;用户代理应关闭并丢弃响应。
完整结束还要用解析状态判断。CL声明十字节,收到九字节后TCP正常EOF,HTTP仍不完整;chunked收到零块但尾部未结束,同样不能完成。相反,HEAD响应头部后没有正文,即使CL很大,也不应继续等待该数量。传输结束、语法结束与操作成功依旧各有不同含义。
这套规则揭示了为什么消息定界属于安全边界。代理和后端若选择不同分支,同一批字节就会被分成不同数量的请求。可靠的做法是依据统一规则检查、拒绝歧义输入或生成无歧义的下游消息,而不是把全部收到的字段原样转发并期待下游自己补救。
9.6 持久连接:省下建连,不等于任意交错消息
HTTP/1.1默认支持持久连接:在消息能够正确自定界、没有关闭约束且双方仍愿意继续的条件下,一条TCP可以依次承载多个请求响应。它不要求为每次GET重新握手,也不需要一个 Connection: keep-alive 字段才从零获得这种能力。连接寿命仍可由端点策略、错误和网络状况结束。
复用的前提是前一条消息的范围已经处理清楚。服务端如果打算继续使用连接,必须消费完整请求体,否则要在响应后关闭,避免把未读内容误当下一请求。客户端如果打算继续使用连接,也必须完整读取响应体,不能收到状态码便把连接退回池中供另一请求使用。RFC 9112 §9.3。
客户端可以顺序发送GET /a,完整读取其响应,再发送GET /b。若采用流水线,还可以在第一个响应回来前发送第二个请求。HTTP/1.1没有为这些普通响应提供可任意重排的独立流编号,因此响应必须按对应请求的顺序发送。客户端也按顺序维护待完成请求队列。
例如请求A的响应很大或生成很慢,请求B的结果很小。即使服务器已准备好B,也不能把B的完整响应直接插进A的正文中;那样客户端会把B的状态行当成A的内容。B必须等待前面的消息边界和响应顺序,这种因前项占用而阻塞后项的现象称为应用层队头等待。
正在绘制图表…
查看图表原文
sequenceDiagram
participant C as HTTP 客户端
participant S as HTTP 服务器
C->>S: GET /a
C->>S: GET /b,可在A响应前提交
Note over S: 即使B先准备好,响应仍按A、B顺序
S-->>C: A响应头与完整内容
S-->>C: B响应头与完整内容
这与TCP层的有序交付又不同。若底层某段字节缺失,后续已经到达的TCP字节也不能越过缺口交给应用,这是传输层队头阻塞。HTTP/1.1的响应顺序限制即使没有丢包也会发生;消除其中一种,不自动消除另一种。后文HTTP/2会用流编号在应用承载上区分请求,但仍需要面对其TCP基础。
客户端可以建立多条独立HTTP/1.1连接分散等待,但每条连接都有建连、拥塞和缓存状态。把连接数调大并不是免费消除排队;它改变资源占用、竞争和故障分布。协议比较必须同时说明连接数和工作负载,否则不能把结果只归因于HTTP版本。
Connection: close 表达这一跳不继续复用的连接意图。它和Connection列出的其它选项是逐跳信息,中间节点不能把它们当作跨越所有链路的业务字段。代理可以与客户端保持一条连接,而与后端采用另一套连接池;一边关闭不等于另一边所有连接都必须永久关闭。具体连接的关闭和剩余内容处理仍按它所在消息状态执行。
响应配对也要处理信息性状态。一个请求可能先得到100等信息响应,再得到最终响应;不能每收到一条状态行就从请求队列弹出一个元素。HEAD与CONNECT所需的方法上下文同样来自这个配对状态。成功升级或CONNECT后,流进入新用途,不能再按普通持久HTTP消息池管理。
如果最后一个待完成请求的最终响应已经完整,随后却出现额外字节,客户端不得把它们无条件处理、缓存或转发为一个新响应。没有对应请求的“额外响应”会破坏配对并带来缓存污染风险。持久连接允许连续消息,不授予服务器发送任意无请求响应的能力。RFC 9112 §6.3、§9.2—9.3。
当连接在一串请求途中失效,客户端还必须判断哪些请求已有最终响应、哪些可能已经执行、哪些能够按语义重试。字节重传由TCP负责,跨新连接重复HTTP请求则是另一层决定。把两者都称为“自动重连”,会掩盖业务重复执行的风险。
9.7 观察报文与解析歧义:同一串字节为什么会有两种边界
报文分析首先要确认看到的是哪个处理阶段。TCP段中的一段负载,未必包括完整HTTP头;重组后的TCP流,才有连续应用字节;HTTP解析器可以进一步去掉chunked边界;内容解码器又可能解压gzip。不同视图显示的长度不同,不能直接互相代替。
| 观察对象 | 保留的信息 | 可能已经改变或去掉的信息 |
|---|---|---|
| 单个网络包 | 当次传输首部和部分负载 | 不保证完整应用消息 |
| 重组的单向TCP流 | 顺序应用字节 | 包和读取边界已合并 |
| HTTP消息视图 | 方法、字段、定界后的消息体 | 工具可能归并重复字段或隐藏编码细节 |
| 解码内容 | 应用需要的内容 | chunked边界、内容压缩等外层表示 |
若工具已经解块,把九字节 Wikipedia 展示成正文,就不能由界面长度推断线上消息体也是九字节;9.4的构造实际需要二十四字节。若主机网络卸载导致抓包里的分段与线上不同,也不能拿界面的某条“大包”证明HTTP一条消息就对应一个TCP段。
真正的协议歧义发生在参与通信的解析器之间。假设入口和后端面对两个互相冲突的长度声明:
text
Content-Length: 5\r\n
Content-Length: 6\r\n它们之后有六字节 ABCDEF。若错误入口取第一项,就认为A至E属于本条内容,F落在后续区域;若错误后端取最后项,则认为A至F都属于本条。偏移只差一个字节,连接上下一条请求的起点却已不同。正确HTTP/1.1实现应按前述规则把这种冲突当作不可恢复错误,而不是任选一项。
这种示意不声称上面片段本身就是完整可用的攻击报文。它说明攻击能够利用的根本条件:一个中间节点相信它只允许了一条请求,下游却从相同或转写后的字节中恢复出另一种消息序列。后端连接池还可能让错误分界影响同一连接上的其它任务,这就是请求走私类问题需要重视的原因。RFC 9112 §11.2。
CL与TE同时出现也有类似风险;字段名附近异常空白、过时折行和不一致的列表合并,还可能让某一层看见字段、另一层忽略字段。不是所有兼容性宽容都造成漏洞,但只要不同组件各自“修正”到不同解释,结构完整性就失去共同依据。
转发者因而不能只确认“这个缓冲区里有合法HTTP开头”。它需要完整解析本条消息,确定唯一消费范围,按规范处理重复与禁止字段,并在生成下游表示时保持一致。若决定拒绝,就按相应状态关闭或清理连接,避免把尚未归属的尾随字节带入下一次请求。
同样,监测系统、访问策略和代理后端必须使用一致的目标解释。如果策略按一份Host判断,实际路由按冲突absolute URI选择,即使正文定界准确,也可能执行了与策略检查不同的目标访问。消息边界与目标边界是两类独立的解释一致性问题。
受TLS保护的线上捕获通常不能直接读出内部HTTP;分析者可能需要在端点、代理终止处或具备授权解密条件的视图中观察。能看到一份解密后的消息,不代表同一链路上的任意观察者也有相同能力。数据来源、解码阶段、完整性和工具是否归一化,都应随结论一起说明。
因此,一个严谨案例应展示原始字段和值、确切字节范围,以及是哪一版实现怎样解释它;若只有归一化后的可读界面,就应保留对原始歧义的判断限制。本章使用明确声明的构造字节讨论规范,不把它们冒充生产服务的漏洞验证。
9.8 跨中间节点重表达:保留语义,不必保留所有线上字节
HTTP代理接收一条消息后,可以在下一跳采用不同的分段、传输编码和连接。这不天然违反HTTP语义,因为消息的资源操作、内容表示与某一跳的线上边界属于不同层次;但每次重表达都需要把相应字段同步到新表示,不能只改变内容而保留旧的定界声明。
沿用九字节 Wikipedia,上游通过二十四字节chunked消息体发送。若代理完整解块并选择在下游发送已知长度内容,可以去掉本跳chunked传输编码,以有效Content-Length 9加九字节内容生成下游消息。它也可以继续流式分块,选择另一组块长。两种路径的块边界与运输字节不同,交给应用的九字节内容相同。
如果只是解块,Content-Encoding所描述的内容编码并未必改变;如果还进行了内容解压或其它表示转换,就需要进一步维护对应元信息和验证条件。是否允许转换还受请求指令、资源语义和代理角色限制。不能把“格式能够重编码”理解为“所有响应都允许任意修改”。RFC 9110 §7.6—7.7。
逐跳字段需要在每次转发时处理。例如一个消息声明 Connection: X-Hop,又含 X-Hop: abc,前者把后者指明为当前连接的选项;转发者需要移除对应逐跳信息,而不是把它当作网站端必须接收的普通业务字段。Connection自身也不能作为端到端字段无条件继续传播。真实协议还有其明确规定的逐跳处理要求,不能只删除字符串名称恰好为Connection的一行就算完成。
连接池使这种区别更明显。来自不同客户端的已解析请求,可以按规则依次使用后端连接;后端一条连接上的响应,又需要归还到各自请求者。代理因此必须维护清楚的消息归属和完整状态。若把入口的一次读取直接等同于完整请求,再按读取块分配后端,就完全绕过了HTTP消息语义。
HTTP/1.1的文本形式也不是HTTP本身唯一的语义载体。下一章WebSocket先用HTTP协商,成功后切换成独立帧协议;第11章HTTP/2保持HTTP方法与状态,却用二进制帧和流编号表达。前者改变后续协议,后者改变同类HTTP消息的映射方式。名称里都带HTTP,不表示沿用同一套Content-Length、chunked和连接切换字节规则。
这也为理解现代代理承载提供一条连续路径:先明确目标代理协议需要流还是数据报,再分析HTTP层提供哪种消息或双向通道,最后检查中间节点会不会缓存、重写、终止或拒绝这类表示。一个协议能装进HTTP内容,不等于所有HTTP网关都以它需要的时机交付内容。
本章依据 RFC 9110 的方法、内容和转发语义,以及 RFC 9112 的HTTP/1.1语法、定界、持久连接和歧义处理;URI结构另据 RFC 3986。示例只计明确列出的编码字节,不把最小教学字段集合当作实际产品自动生成的完整字段清单。