RFC 7230 把请求行分了四种,网关只用得上两种

第一次把两种请求行并排摆出来的时候,我盯着 Host 头对了半天,两行一模一样,怎么看都不像配置有差异。后来才发现该看的是第一行。同一个接口、同一台客户端,只是第二次开了转发:

bash 复制代码
GET /api/v1/orders HTTP/1.1
Host: api.example.com
bash 复制代码
GET http://api.example.com/api/v1/orders HTTP/1.1
Host: api.example.com

Host 头一模一样,请求行却从一段路径变成了完整地址。把后面那种写法当成客户端多此一举,或者当成某个库的怪癖,都猜偏了。这是 RFC 7230 第 5.3 节定下来的四种 request-target 形式之一,用哪一种取决于这条请求的 TCP 对端是谁。

四种形式各自管什么

绝对路径加查询串属于 origin-form,最常见,直连源站时用,authority 单独放在 Host 头里。带 scheme 和 authority 的完整 URI 是 absolute-form,规范规定向网关发请求时用它。只有 host 和端口的 authority-form 专用于 CONNECT。字面一个星号的 asterisk-form 只出现在针对服务器整体的 OPTIONS * 里。

四种不是可以互换的写法,而是四种不同的语义:客户端打算把这条请求交给谁、让谁来决定下一个连接点。把这点想明白,后面几条规范读起来就顺了。

转发时为什么必须是完整地址

直连源站时,TCP 连接已经连到了目标主机,服务器从 Host 头就能知道该交给哪个虚拟主机,请求行只写路径完全够用。

经过网关时情况变了:TCP 连接的对端是网关,不是源站。网关只拿到一段路径,根本不知道该往哪台主机转发。Host 头里倒是有 authority。可 Host 是 HTTP/1.1 才强制的,HTTP/1.0 可以不带。它的语义是给源站选虚拟主机用的,网关不拿它来定路由。

RFC 7230 第 5.3.2 节写得很直接:客户端向网关发请求(CONNECT 和针对服务器整体的 OPTIONS 除外)时,必须用 absolute-form 作为 request-target。

与之配套的是第 5.5 节的 effective request URI:request-target 是 absolute-form 时,有效 URI 就是它本身;是 origin-form 时,才用 scheme 加 Host 里的 authority 加 path 拼出来。这意味着请求行已经带完整地址的情况下,Host 与它不一致也不会拿 Host 去覆盖。排查「连对了网关、进错了站点」这类问题,得看请求行里的 authority,只盯 Host 会看漏。

CONNECT 为什么只给地址和端口

建隧道的阶段还没有 URI 这个概念。客户端只是请网关帮忙连到某台主机的某个端口,所以请求行里只有 authority:

makefile 复制代码
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443

隧道通了之后,客户端在隧道内部对源站发的请求是 origin-form。对源站而言这就是一条普通直连,写完整地址反而不合规范。这是最容易混的一点:absolute-form 只出现在「客户端到网关」这一段,隧道一旦建立,后面全部按直连的形态写。

asterisk-form 顺带说一句:OPTIONS * 里的星号指的是服务器整体而非某个资源,网关收到应当自己应答,不该往源站转。

HTTP/2 把请求行拆掉了

HTTP/2 没有请求行,拆成了 :method、:scheme、:authority、:path 四个伪头。转发场景下网关靠 :authority 定位目标,:path 里不再塞完整 URI,Host 头也由 :authority 取代。所以「把完整地址写进请求行」是 HTTP/1.1 特有的形态,拿它去套 HTTP/2 的抓包会一无所获。

CONNECT 在 HTTP/2 里由 RFC 8441 的扩展 CONNECT 承载,多一个 :protocol 指明隧道之上跑什么协议,比如 WebSocket。有些 HTTP/2 客户端默认连不上隧道,原因不在地址或凭证,而在实现没开这个扩展。这一条在排查「HTTP/1.1 能通、切到 HTTP/2 就不通」时值得先确认。

从 effective request URI 到缓存键

缓存是这套规范最容易露馅的地方。网关按 absolute-form 拿到完整 URI,转发给源站时通常会把它改回 origin-form,再把 authority 放进 Host 头。这是标准动作,却带来一个副作用:网关日志里记的是完整地址,源站日志里只剩一段路径,两侧的缓存键不是同一套东西算出来的。

RFC 7234 把缓存主键定为方法加 effective request URI,而这个 URI 的拼法随 request-target 的形式变化。同一个资源,网关用带 scheme 和 authority 的完整串做键,源站拿 scheme、Host 和 path 拼出来的串做键。默认端口省略、host 大小写、路径里的点段归一化,只要有一处处理不一致,命中率就会差一截。

排查这类命中异常时,我会在两侧各打印一次请求行、Host 头和归一化之后的键。只盯一侧日志,很容易把请求行形式不同误读成缓存坏了。Vary 头的处理也有同样的麻烦,网关和源站对同一个列表的解释差一点,表现出来就是命中率莫名往下掉。

几个边界和误配

把 absolute-form 直接发给源站,规范要求服务器必须接受,但不少框架会把带 scheme 的那一串当普通路径解析,结果回 400 或者路由不到。跨语言、跨框架调用时常在这里翻车,尤其是某个库自作主张把请求行拼成完整 URI 的情况。

请求行的 authority 与 Host 不一致更麻烦:网关按请求行连,源站按 Host 选虚拟主机,两边错开就是「连对了机器、进错了站点」,前面若有共享缓存,还会带出缓存污染。规范让接收方以请求行为准,但源站那一侧未必按这个来。

HTTPS 场景里网关看不到内部请求行。CONNECT 之后是加密隧道,网关能看到的就是 CONNECT 那一行里的主机名和端口。「网关能不能看到完整地址」的准确答案是:明文 HTTP 能看到,https 只能看到域名和端口这两个元数据。这一点跟把请求行写得多完整无关。

剩下几个是报文层面的小坑。IPv6 字面量要写成 [2001:db8::1]:443,不带方括号时端口会被解析错位。请求行里带用户信息(user:pass@host)在 RFC 7230 里已弃用,接收方应当拒绝或忽略。scheme 和 host 大小写不敏感,path 敏感,百分号编码的规范化由接收方处理,转发前别自己先解一遍。请求行长度上限由服务端自定,网关和源站限制不同时会出现「网关收下、源站拒绝」,表现出来是一个来源不明的 414。

看第一行就够了

把三种典型报文摆在一起:

bash 复制代码
GET /api/v1/orders HTTP/1.1                        # 直连源站
GET http://api.example.com/api/v1/orders HTTP/1.1  # 明文转发
CONNECT api.example.com:443 HTTP/1.1               # 建隧道

第一行是 origin-form,说明这条请求的对端就是源站。换成 absolute-form,就表示中间有个节点要替它决定往哪连。至于 authority-form,后面接的是一段加密隧道。只看请求行的第一段,就能判断请求有没有经过中间节点、走的是明文转发还是隧道,不用翻配置文件。

收尾

这四种形式的划分不是历史包袱,而是「谁来决定下一个连接点」在报文上的投影。客户端自己知道目标,写一段路径就够了。需要别人代连的时候,得把完整地址交出去,让对方知道该连哪儿。至于只求对方搭一条管子的情形,给地址和端口就行,多写反而没有对应的语义。文中条款号以 RFC 7230 为准,HTTP/2 部分见 RFC 7540 与 RFC 8441。各客户端库和网关对绝对形式的接受程度有差异,示例里的主机名是占位,实际环境请以所用实现的文档为准。

相关推荐
SomeOtherTime1 小时前
Rust生命周期介绍(AI回答)
开发语言·后端·rust
无责任此方_修行中2 小时前
「不止技术」AIE 2026 会后感悟
人工智能·后端·ai编程
SimonKing2 小时前
Workbuddy太占C盘空间了,写个迁移工具迁走10G的数据
java·后端·程序员
喵了几个咪3 小时前
GoWind Flow 拆解:一个 Go 工作流引擎与它的双模式设计器
开发语言·人工智能·后端·golang
倔强的石头_3 小时前
AI数据库是什么?我把金仓KES的库内三件套拆开看了一遍
后端
杨凯凡4 小时前
Spring Boot 大文件下载如何实现断点续传?HTTP Range、206 与 416 实战
spring boot·后端·http
量化分析码农4 小时前
股票实时行情接口返回字段说明:单次请求的时间字段与数值类型
后端
脚本自动化开发11 小时前
柔光屏平板怎么选?华为 MatePad Air Z 柔光版学
后端