URI 的语法是 scheme://host:port/path?query#fragment。
非 ASCII 字符必须先按 UTF-8 编码,再逐字节写成百分号形式。
上一节拆了五个部件,这一节拆到字节:一个部件能有多长、中文怎么进去、哪些字符是保留的。 第三章讲报文时还会再用一次。
一、语法与保留字符
标准写法是 scheme://[userinfo@]host[:port][/path][?query][#fragment],方括号部分可省。
有一批字符被保留(:/?#[]@!$&'()*+,;=),它们在 URL 里有特殊含义,
要当普通字符用必须百分号编码。
另有一批字符不安全(空格、<、>、"、#、%),也必须编码。
百分号编码的规则:把字符按 UTF-8 编成字节,每个字节写成 %XX。
所以一个汉字通常变成三组 %XX,长度变三倍。
二、实测:中文路径的膨胀
Demo 用了 /搜索/结果.html:UTF-8 是 19 字节、11 个字符(含斜杠),百分号编码之后变成 43 字节,
膨胀 2.26 倍。
不是 3 倍,因为路径里的 / 和 . 是 ASCII,编码后还是 1 个字符。
上限是 3 倍——非 ASCII 字节占的比例越高,越接近 3。
这条规则有一个直接后果:中文 URL 更容易撞上长度上限。
老标准说 2048 字节,现代浏览器和服务器普遍放宽到 8 KB 以上,但代理、日志、网关各有限制,
实际工程里中文路径超过几百字节就该考虑换个写法。
三、三个常见的长度陷阱
| 位置 | 典型上限 | 超了会怎样 |
|---|---|---|
| 浏览器地址栏 | 现代 2 MB 级 | 基本不炸 |
| 老代理与网关 | 2048 字节 | 414 URI Too Long |
| 服务端请求行 | 8 KB(Nginx 默认) | 414,日志里能看到 |
| 注意这三条限制是叠加的:链路里任何一环顶不住,请求就到不了终点。 | ||
| 所以「本地测得好好的,上线就 414」几乎总是中间某一环的上限比本地低。 | ||
顺带一个易错点:+ 在 query 里表示空格,在 path 里就是加号本身。 |
||
这种不一致来自表单编码的历史,urllib.parse.quote 有个 safe 参数专门处理它。 |
四、动手 Demo:逐段拆一个真 URL,看中文怎么变成百分号
拿一个含中文路径与中文查询串的 URL,拆五段,再单独算 path 的 UTF-8 字节数与百分号编码后的长度。
url = u'https://www.example.com/搜索/结果.html?q=宜昌&page=2#list'
p = urllib.parse.urlsplit(url)
print(u'path 字节', len(p.path.encode()), u'字符', len(p.path))
print(u'编码后', len(urllib.parse.quote(p.path)))
print(u'膨胀 %.2f 倍' % (len(urllib.parse.quote(p.path)) * 1.0
/ len(p.path.encode())))
实跑输出:
--------------------------------------------------------------------
逐段拆一个真 URL,顺带看看中文怎么变成百分号
--------------------------------------------------------------------
完整 URL https://www.example.com/搜索/结果.html?q=宜昌&page=2#list
scheme https
host www.example.com
port (省略,https 默认 443)
path /搜索/结果.html
query q=宜昌&page=2
fragment list
path 的 UTF-8 字节数 19
path 的字符数 11
百分号编码之后 43 字节
膨胀倍数 2.26
上限 每个非 ASCII 字节变成 3 个字符,最多 3 倍
长度上限(旧说法) 2048 字节,现代浏览器和服务器普遍放宽
这一段实际长度 63 字节
地址栏里那一串看着像中文,发到线上全是 %E4%B8%AD 这样的字节。
URL 是「给程序看的字符串」,不是「给人看的文本」。
path 的 UTF-8 字节数 19、path 的字符数 11、百分号编码之后 43 字节、膨胀倍数 2.26。
四个数一起看才明白 3 倍是上限不是定值。
这一段实际长度 63 字节——整个 URL 只有 63 字节,其中 26 字节是中文撑出来的。
换成拼音能省一半,这也是很多老系统坚持用英文 slug 的原因。
30 秒实验
- 把路径全换成中文(
/搜索/结果/详情/列表.html),重跑,看膨胀倍数往 3 靠多少。 - 用
urllib.parse.quote(p.path, safe=u'/')和不带safe各跑一次,看差在哪。 - 把 query 里的中文换成
%E5%AE%9C%E6%98%8C,再用unquote还原,确认往返一致。
常见误区
- 「中文 URL 一定变 3 倍」——每个非 ASCII 字节变 3 个字符,ASCII 部分是 1 比 1, 所以整体倍数在 1 到 3 之间。
- 「URL 里可以直接写中文」——浏览器地址栏会显示中文,但发出去的请求行里一定是百分号编码。
- 「
+就是空格」——只在application/x-www-form-urlencoded的 query 里成立, 在 path 里它就是加号。
自测三问(点开对答案)
1. 百分号编码的膨胀上限是多少,为什么不是定值? —— 3 倍。ASCII 部分 1 比 1,非 ASCII 字节 1 比 3,实际倍数取决于两者比例。 2. `fragment` 会不会出现在请求行里? —— 不会。`#` 之后的内容只在客户端用,不发给服务器。 3. 「本地好、线上 414」通常是什么原因? —— 链路中间某一环的 URI 长度上限比本地低,比如代理的 2048 字节或 Nginx 的 8 KB。第一章到此结束。第二章开始拆报文本身:请求行、首部、正文各长什么样。
- ☐ (本章完,下一节 2.1 HTTP协议用于客户端和服务器端之间的通信)
相关笔记
- 21 课 · Web技术原理(URL 到页面渲染的完整路径)
- 📘 图解HTTP · 索引