🎬 第六章 HTTP-FLV流媒体协议
📄 一句话概括

URL 是七个部件拼起来的,每个缺省都有默认值。 自己拼地址时,路径段必须过一遍编码。

📝 本节和前面几节的关系

6.2.2 与 6.2.3 讲报文,这一节讲报文里那行路径从哪来。 6.3 进入 FLV 的字节层。

一、七个部件

scheme://host:port/path?query#fragment,一条地址就这七段。 ① scheme:http 或 https,决定用哪套默认端口。 ② netloc:主机加端口,host:port 的原文。 ③ hostname:去掉端口的主机名。 ④ port:端口。没写时是 None,不是 0。 ⑤ path:从第一个斜杠到问号之前。 ⑥ query:问号之后、井号之前,参数串。 ⑦ fragment:井号之后,只在浏览器内部用,不会发给服务端。

第 ⑦ 条容易误解: fragment 从来不会被发到服务端。 所以用它传 token 是无效的——服务端根本收不到。

二、三个默认值

① 端口:http 默认 80,https 默认 443。 ② 路径:没写就是 /。 ③ 协议:浏览器地址栏里没写协议,默认按 http 处理。

代码里要注意 port 的行为: 没写端口时 urlsplit(...).port 返回 None。 所以判断要写成「None 就按 scheme 取默认」,不能当 0 处理。

还有一个坑:urlsplit 不会做归一化。 它不补默认端口、不合并多余斜杠、也不解引用。 想要归一化得自己写,或换用 urljoin 之类的函数。

三、中文与空格必须编码

URL 里只允许一小部分字符直接出现,其余都要转成百分号形式。

实测三个例子: 测试.ts 变成 %E6%B5%8B%E8%AF%95.ts(UTF-8 逐字节转), a b.ts 变成 a%20b.ts(空格不能直接出现), a&b=c 变成 a%26b%3Dc(& 与 = 是分隔符,必须转义)。

第三个例子的后果最隐蔽: 不转义的话,服务端会把 a&b=c 拆成「文件名 a」加「参数 b=c」, 取到的文件就不是你想要的那个。 所以凡是自己拼 URL 的代码,路径段都要过一遍 quote。

注意 quote 与 quote_plus 的区别: 前者把空格转成 %20,后者转成 +。 路径里必须用 quote——+ 在路径里就是加号本身,不会被解释成空格。

四、动手 Demo:URL——五个部件,各有默认值

拆一个带端口、查询串、锚点的地址,把七段都打出来。 再实测三种必须编码的字符。

import urllib.parse as up

url = "https://live.example.com:8443/hls/index.m3u8?token=abc&t=123#pos"
sp = up.urlsplit(url)
for k in ("scheme", "netloc", "hostname", "port",
          "path", "query", "fragment"):
    print("  %-9s %s" % (k, getattr(sp, k)))

print()
sp2 = up.urlsplit("http://a.com/x")
print("没写端口时 port = %r" % (sp2.port,))
print("按 scheme 取默认端口:%d" % (sp2.port or (443 if sp2.scheme == "https" else 80)))

print()
for s in ("测试.ts", "a b.ts", "a&b=c"):
    print("  %-8s -> %s" % (s, up.quote(s)))
print("  %-8s -> %s" % ("a b.ts (quote_plus)", up.quote_plus("a b.ts")))

实跑输出:

--------------------------------------------------------------------
URL——五个部件,各有默认值
--------------------------------------------------------------------
**① 拆一个带查询串的地址**

  **原地址:**https://live.example.com:8443/hls/index.m3u8?token=abc&t=123#pos

  部件         值
  --------------------------------------
  scheme       https
  netloc       live.example.com:8443
  hostname     live.example.com
  port         8443
  path         /hls/index.m3u8
  query        token=abc&t=123
  fragment     pos

**② 三个默认值**

  省略的部分     默认值
  -------------------------------------
  端口           http 80/https 443
  路径           /
  协议           http

  **`sp.port` 在没写端口时会返回 None,不是 0。**
  **所以判断要用「None 就按 scheme 取默认」。**

**③ 中文与空格必须编码**

  原始        编码后                    为什么
  ---------------------------------------------------------
  测试.ts     %E6%B5%8B%E8%AF%95.ts     UTF-8 转百分号
  a b.ts      a%20b.ts                  空格变 %20
  a&b=c       a%26b%3Dc                 & 与 = 必须转义

  **不编码的后果很隐蔽:**
  **`a&b=c` 会被服务端拆成参数,文件名就变了。**
  **所以凡是自己拼 URL 的代码,路径段都要过一遍 quote。**

port 打印出来是 8443(整数),不是字符串,因为 urlsplit 顺手转了。 sp2.port 是 None,所以不能写 if sp2.port == 0。 quote 给 %20、quote_plus 给 +——路径用前者,表单用后者。

30 秒实验

  1. 把地址里的端口删掉,看 port——变成 None,这就是「默认值要靠自己补」。
  2. 用 up.urljoin 把相对路径接到基地址上,看结果——能自动处理斜杠,比手工拼字符串可靠得多。
  3. 把 quote(s, safe='') 的参数改成默认,看 / 的变化——默认 safe='/' 会保留斜杠,拼单段路径时要显式清空。

常见误区

  1. 「fragment 会发给服务端」——不会,只在浏览器内部用。
  2. 「没写端口时 port 是 0」——是 None。
  3. 「路径里的空格用 + 代替」——那是表单编码,路径要用 %20。
自测三问(点开对答案) 1. URL 有哪七段? —— **scheme、netloc、hostname、port、path、query、fragment。** 2. 没写端口时该返回什么? —— **None**,再按 scheme 取 80 或 443。 3. 路径段为什么必须 quote? —— **`&` 与 `=` 是分隔符**,不转义会被拆成参数,文件名就变了。

相关笔记