📘 第一章 了解Web及网络基础
📄 一句话概括

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 秒实验

  1. 把路径全换成中文(/搜索/结果/详情/列表.html),重跑,看膨胀倍数往 3 靠多少。
  2. 用 urllib.parse.quote(p.path, safe=u'/') 和不带 safe 各跑一次,看差在哪。
  3. 把 query 里的中文换成 %E5%AE%9C%E6%98%8C,再用 unquote 还原,确认往返一致。

常见误区

  1. 「中文 URL 一定变 3 倍」——每个非 ASCII 字节变 3 个字符,ASCII 部分是 1 比 1, 所以整体倍数在 1 到 3 之间。
  2. 「URL 里可以直接写中文」——浏览器地址栏会显示中文,但发出去的请求行里一定是百分号编码。
  3. 「+ 就是空格」——只在 application/x-www-form-urlencoded 的 query 里成立, 在 path 里它就是加号。
自测三问(点开对答案) 1. 百分号编码的膨胀上限是多少,为什么不是定值? —— 3 倍。ASCII 部分 1 比 1,非 ASCII 字节 1 比 3,实际倍数取决于两者比例。 2. `fragment` 会不会出现在请求行里? —— 不会。`#` 之后的内容只在客户端用,不发给服务器。 3. 「本地好、线上 414」通常是什么原因? —— 链路中间某一环的 URI 长度上限比本地低,比如代理的 2048 字节或 Nginx 的 8 KB。

第一章到此结束。第二章开始拆报文本身:请求行、首部、正文各长什么样。

相关笔记