请求行的第二段叫 request-target,有四种写法:路径、绝对 URI、授权形式、星号。 用哪种取决于你在跟谁说话。
上一节说请求里没带状态。 这一节看请求行里到底带了什么——那个被称作「URI」的东西,实际有四种形态。
一、四种形态
| 形态 | 长什么样 | 什么时候用 |
|---|---|---|
| origin-form | GET /a/b.html |
直连源站,最常见 |
| absolute-form | GET http://h/a/b.html |
经代理 |
| authority-form | CONNECT h:443 |
建隧道 |
| asterisk-form | OPTIONS * |
问整台服务器 |
| 日常写网页只会遇到第一种。后三种出现在代理、隧道、以及各种「400 Bad Request」的排查现场。 | ||
| 为什么要有绝对形式? | ||
因为代理可能同时服务多个站点,它得知道这个请求要去哪个主机——而 Host 首部在 HTTP/1.0 里没有, |
||
| 所以老代理靠绝对 URI 判断目标。 |
二、主机名到底写在哪
HTTP/1.1 强制要求 Host 首部,所以源站可以用「路径形式」加 Host 知道目标。
一台服务器上跑几十个网站,靠的就是 Host 区分——这叫虚拟主机(5.1 节)。
缺 Host 的 HTTP/1.1 请求,服务端应当回 400。
这一条在实测里很值得注意:很多框架默认宽容,缺了也照处理,于是上线后接真实代理才炸。
三、几个容易踩的细节
请求目标区分大小写(路径部分),/Index.html 和 /index.html 是两个资源。域名部分不区分。
查询串里的 ?、&、= 是分隔符,值里要用必须编码。
片段(#)根本不会出现在请求行里——它在浏览器就被截掉了。
路径里的 .. 要小心:服务端必须做归一化再判断权限,否则就是目录遍历漏洞(11.2.6)。
四、动手 Demo:四种请求目标真发一遍
分别用四种形态构造请求行发出去,看服务端收到的第二段是什么。 服务端不做校验,原样打印,这样能看清「线上真正传的是什么」。
forms = [(u'origin-form', u'GET /a/b.html HTTP/1.1'),
(u'absolute-form', u'GET http://example.com/a/b.html HTTP/1.1'),
(u'authority-form', u'CONNECT example.com:443 HTTP/1.1'),
(u'asterisk-form', u'OPTIONS * HTTP/1.1')]
for name, line in forms:
raw = line.encode() + b'\r\nHost: 127.0.0.1\r\n\r\n'
s.sendall(raw)
实跑输出:
--------------------------------------------------------------------
请求目标(URI)的四种写法
--------------------------------------------------------------------
origin-form GET /a/b.html HTTP/1.1
服务端收到 GET /a/b.html HTTP/1.1
用途 直连服务器时用,最常见
absolute-form GET http://example.com/a/b.html HTTP/1.1
服务端收到 GET http://example.com/a/b.html HTTP/1.1
用途 经代理时用
authority-form CONNECT example.com:443 HTTP/1.1
服务端收到 CONNECT example.com:443 HTTP/1.1
用途 建隧道时用
asterisk-form OPTIONS * HTTP/1.1
服务端收到 OPTIONS * HTTP/1.1
用途 问整台服务器能力时用
这四种形态在 RFC 里叫 request-target,位置都是请求行的第二段。
日常写网页只会遇到第一种;写代理、抓包、排查 400 错误时才需要后三种。
一个常见故障:代理配置错了,客户端把绝对 URI 直接发给源站,
源站要么当路径处理、要么回 400——日志里那行请求行会直接暴露。
四种形态服务端都原样收到了——因为它只是记字节,不做语义校验。
真实服务器会按 RFC 校验,形态不对直接 400。
CONNECT example.com:443 这一行之后,这条连接不再是 HTTP 了,它变成一条透明隧道,
里面跑的是 TLS 字节。所以 CONNECT 的响应必须是 2XX,之后双方开始裸传。
OPTIONS * 里的星号不是通配路径,是「针对整台服务器」的意思。
用它问服务器支持哪些方法,响应里会有 Allow 首部。
30 秒实验
- 把 origin-form 那行改成
GET(不带路径),看服务端收到什么。 - 发一个
GET /a/../b.html,服务端会收到原样的串——想想为什么必须由服务端归一化。 - 发一个带
#frag的请求行,看服务端收到的是不是原样(会,因为浏览器才负责截掉)。
常见误区
- 「请求行里写的一定是完整 URL」——直连时只有路径,主机在
Host首部。 - 「
*是通配符」——在OPTIONS *里它表示「整台服务器」。 - 「路径大小写无所谓」——Windows 服务器可能无所谓,Linux 上就是两个文件。
自测三问(点开对答案)
1. 直连源站时,请求目标用哪种形态? —— origin-form,只写路径与查询串,例如 `/a/b.html?x=1`。 2. 主机名在 HTTP/1.1 里写在哪? —— `Host` 首部。HTTP/1.0 没有这个要求,所以老代理用绝对 URI 形式。 3. `CONNECT` 之后这条连接还是 HTTP 吗? —— 不是。它变成透明隧道,里面传的是 TLS 之类的原始字节。请求目标说清了。下一节看第一段:那个动词位置能填什么。
- ☐ (本节完,下一节 2.5 告知服务器意图的HTTP方法)