🌐 第四章 Web网站的运行原理
📄 一句话概括

URL(Uniform Resource Locator,统一资源定位符)就是"网上那个东西在哪、怎么取"的一串写法。它由六段拼成:协议名 / 主机名 / 端口 / 路径 / 查询串 / 片段。它的精妙之处不在"长什么样",而在于每一段归不同的人管——协议段浏览器用掉,主机段交给 DNS 换成 IP(3.21),端口段交给 TCP 去连(3.17),路径与查询串装进请求行发给 Web 服务器软件,而最后那段 # 锚点连服务器都收不到。所以你改网址的哪一段、有没有用,完全取决于改的是哪一段。

📝 本节和前面几节的分工

4.1 里已经出现过一条完整 URL,当时只是顺手指给我们看"为什么 #top 服务器收不到"。

本节把它扶正做主角,干三件事:① 逐段拆开,说清每段是什么、能不能省;② 说清每段归谁管——这是理解"改了网址为什么没反应"的钥匙;③ 补上它最反直觉的一层:你写的和真正发出去的往往不是同一串字符(百分号编码)。

一、URL 是什么

URL = Uniform Resource Locator = 统一资源定位符。 三个词逐个拆:

词 意思 说人话
Uniform(统一) 一套统一的写法 网页、图片、视频、接口……所有东西都用同一套格式描述,浏览器不必为每种东西学一套语法
Resource(资源) 被取的东西 严格说不只是"文件",也可能是"程序现算出来的一段内容"(4.1 的动态内容)
Locator(定位符) 按位置去指 它说的是"在哪",不是"是什么"。所以同一个 URL 昨天给的是这篇文、今天可能换了一版
📝 URL 与 URI:一对容易打架的名字

规范里的正式名字其实是 URI(Uniform Resource Identifier,统一资源标识符)。它下面分两支:

  • URL:按位置指东西——"去那个地址拿"
  • URN:按名字指东西——"这本书的 ISBN 是 978-7-…",不管它放在哪个书架

理想世界应该用 URN(东西搬家了链接也不用改),但现实中几乎全是 URL。所以日常说的"网址"、课本里的"URL"、规范里的"URI",指的多半是同一件东西——知道它们是"一家人"就够了,不必在措辞上纠结。

⚠️ URL ≠ 域名(这是最容易混的一步)

域名只回答一个问题:"哪台主机"(收 3.21)。

URL 要多回答两个:"用什么协议取" 和 "取这台主机上的哪一个资源"。

所以 www.example.com 是域名,而 https://www.example.com/about.html 才是 URL——域名只是 URL 里的第二段。

二、六段解剖

拿一条最全的 URL 为例:

https://www.example.com:443/path/page.html?q=1#top
# 段名 这一段 作用 能省吗
1 协议名 scheme https:// 用哪种协议说话,同时定下默认端口 能(浏览器补,但有时补错)
2 主机名 host www.example.com 哪台主机,交给 DNS 换成 IP 不能(可写 IP 代替)
3 端口号 port :443 连那台主机的哪一个程序 能(省了就用协议的默认端口)
4 路径 path /path/page.html 那台主机上的哪个资源 能(省了就是 /)
5 查询串 query ?q=1 取这个资源时附加的参数 能
6 片段 fragment #top 跳到页面里的哪一小节 能
💡 记忆抓手

前四段合起来读像一句话:「用 https 去 example.com 这台机器的 443 号门口,拿 /path/page.html 这个文件」。

后两段是给这次"取"附加的修饰:? 是给服务器的备注,# 是给自己看的位置。

几个容易忽略的细节:

三、每一段归谁管

这是本节最值钱的一条:URL 不是一句口令,是一张分工单。

⚠️ 由此可以解释三件日常怪事

① 在地址栏里改 # 后面那截,页面不会重新加载——它压根不出门,浏览器只是自己滚了一下屏幕。 ② 同一个页面链接发给人,对方打开后"没跳到那一节"——因为 # 那段到了对方浏览器才生效,中间任何跳转、重定向都可能把它丢掉。 ③ 改了路径或查询串,页面立刻变——因为这两段是真的发给服务器的,服务器照着重新取了一遍。

四、能省掉的那些段

URL 有一堆"省略写法",浏览器会替你补全。这正是"同一个页面有好多条网址"的来源:

你写的 浏览器实际理解的 省掉了什么
example.com http://example.com/ 或 https://example.com/ 协议 + 路径
http://example.com/ http://example.com:80/index.html 端口 + 默认文档名
https://example.com:443/ https://example.com/ 端口(地址栏干脆把它隐藏了)
example.com/a http://example.com/a(路径区分大小写) 协议 + 端口
💡 三个"更省"的经验
  • 协议最好写全。让浏览器替你猜,它可能补成 http://(明文),也可能因为记着上次的 https 而补对——猜错就是"该加密的没加密"。
  • index.html 不用写。路径以 / 结尾时,服务器会拿出它配置的默认文档(通常是 index.html,收 4.2 里"index 是首页惯例")。
  • 默认端口不用写。但非默认端口必须写——比如本地那个 python -m http.server 8000 起的服务站,URL 里不写 :8000 就永远连不上(收 4.1)。

五、绝对 URL 与相对 URL

写链接(<a href>、<img src>、<link href>)时,地址可以有两种写法:

类型 长什么样 相对谁算 用在哪
绝对 URL https://example.com/about.html 不相对谁,自带全套 跨站链接、邮件、文档里引用
相对 URL about.html / images/a.png / ../style.css 相对当前页面所在地址 站内自己的图片、样式、链接
根相对 /images/a.png(以 / 开头) 相对这个网站的根 站内跨目录引用
📝 为什么站内链接要写成相对地址

两个实在好处:

① 换域名不用改文件。整站从 test.example.com 搬到 www.example.com,绝对链接全得改,相对链接一个不用动。 ② 本地预览和线上一致。在本地用 file:// 或 python -m http.server 打开,相对路径照样能戳到图;绝对地址一到本地就废了。

代价是容易算错层级。4.2 说的"图片破图 404",九成就是相对路径算错——浏览器是拿"当前页面的地址"当基准去拼的,不是拿"文件在硬盘的哪里"。路径里多一层目录、少一个 ../,结果就完全不同。

一条可以背下来的拼法:相对地址 = 把当前页面地址的最后一段(文件名)删掉,再把相对地址接上去。

六、你写的 ≠ 发出去的:百分号编码

URL 里有一批字符是"语法字符",它们本身有结构含义:

/  ?  #  &  =  +  空格

如果这些字符出现在数据内容里(比如搜索词里真有空格、参数值里真有 &),直接放进去就会把 URL 的结构搅乱:服务器会把你内容里的 & 当成"下一个参数的开始"。

于是有了百分号编码(percent-encoding):把"不安全、不能直接放"的字符,按字节写成 % + 两位十六进制。

原来 编码后 说明
空格 %20 URL 里最经典的转义
& %26 不然会被当成参数分隔符
? %3F 不然会被当成查询串起点
# %23 不然会被当成锚点起点
中 %E4%B8%AD 先按 UTF-8 变成三个字节 E4 B8 AD,再逐字节加 %

注意最后一行:一个中文字 = 9 个字符。所以含中文的地址会"凭空长胖三倍"——这也解释了一条长中文搜索 URL 为什么那么长。

⚠️ `+` 是个历史坑

在查询串里,+ 常常被当成空格(这是老式表单提交留下的惯例);但在路径里,+ 就是加号。

所以最稳的做法:空格一律用 %20,别赌 +。

谁来做编码?

📝 中文域名其实不是靠百分号编码

你输入 中文.com 也能访问,但它靠的是另一套叫 Punycode 的转换:中文.com 实际被转成 xn--fiq228c.com 这样的形式再去查 DNS。

看到以 xn-- 开头的域名,那就是一条中文(或日文、韩文)域名——知道这个就够,不必深挖。

七、发出去的时候,六段各去哪了

把这一节和 4.1 的"门房"接上。当你敲下回车、请求真正离开你的电脑时,URL 的六段被"拆散分家"了:

URL 里的段 最后去了哪
协议名 不发送。浏览器自己用它决定协议与默认端口
主机名 不按原样发送给网页程序。先被 DNS 换成 IP 用于连接;再作为 HTTP 的 Host 头带上(HTTP/1.1 必须带,否则服务器不知道你要哪个站——收 4.1"一台机器托管多个站靠域名区分")
端口号 不发送。在建立 TCP 连接时就"用掉"了(收 3.17)
路径 + 查询串 原样装进请求行,这才是服务器真正读到的东西:GET /path/page.html?q=1 HTTP/1.1
片段 不发送。留在浏览器手里
📄 一句话记住

服务器永远只看到「路径 + 查询串」,其它几段都在路上被各层消化掉了。

这正好接上第四章的下一站:网页是怎么被"要"回来的——那条请求行本身,就是 HTTP 协议的开场白。

八、URL 的四个脾气(限制与坑)

① 长度有上限。 规范没写死,但浏览器、服务器、中间代理各有自己的长度限制(几千字符是常见量级)。所以别拿 URL 传大块数据——搜索关键词、分页参数可以,一整篇文档就不行。

② 同一个资源可能有好几条 URL。 example.com / www.example.com / example.com:80 / example.com/index.html 很可能指向同一个页面;再叠上 ?utm_source=… 这类跟踪参数,同一个页面能有几十条不同地址。后果很具体:浏览器缓存算成好几份(该命中的没命中)、统计后台把一页算成十几页。所以正规站点要做 URL 规范化:把多写法 301 跳到唯一那条。

③ 路径不是文件路径,但确实有脾气的细节。 - 域名不区分大小写(Example.com = example.com),路径区分大小写(/About 和 /about 是两个地址,在 Linux 服务器上尤其如此)。 - 结尾斜杠常被区别对待:/about 和 /about/ 在服务器看来可能不是一回事,常常一个会 301 跳到另一个(收 4.1 的状态码)。 ——这也是"本地能打开、上传服务器就 404"的经典原因之一。

④ URL 不能当安全边界。 路径里出现了 ../(/img/../../etc/passwd 这种写法),如果是"拿到路径就直接拼到磁盘上打开"的服务器,就会被一步步走出网站目录。服务器必须校验路径、限制在网站根目录内——这是 Web 安全里最古老的漏洞之一。"URL 是用户能随便改的输入",这条意识要先有。

九、动手 Demo(Demo41)

目标:把你每天敲的那串字符,从"眼熟"变成"看得懂、拆得动"。

第 1 步 · 看浏览器替你补全了什么 地址栏只敲 example.com 回车,再点一下地址栏看完整形式:前面被补上了协议,端口看不见(被隐藏了)。换成 http://example.com:80/,看它又怎么简化。

第 2 步 · 在网络面板里看"被拆开的查询串" 按 F12 → 网络 → 刷新 → 随便点一条请求 → 右侧「标头」里找 请求 URL,再找 查询字符串参数——浏览器已经把它翻译成一张"键 = 值"的表了。以后看参数别肉眼数 &,直接看这张表。

第 3 步 · 控制台两行,亲手编一次码

encodeURIComponent('中文 空格&')   // → "%E4%B8%AD%E6%96%87%20%E7%A9%BA%E6%A0%BC%26"
decodeURIComponent('%E4%B8%AD')   // → "中"

对照图二:中 → %E4%B8%AD,空格 → %20,& → %26。编码和解码就是一对逆运算。

第 4 步 · 在地址栏里做一次"真编码" 随便找个搜索引擎,敲带空格和中文的关键词搜一次,然后把地址栏里的地址整条复制出来——你会看到 %E4%B8%AD%E6%96%87%20 这种东西。这就是"你写的"和"发出去的"的差距。

第 5 步 · 验证"片段真的不发给服务器"(本节最关键的实验) 找一个长文档页(任何带目录/锚点的说明书页都行),点一下目录里的锚点跳转: - 地址栏的 # 后面变了 - 网络面板里一条新请求都没有

这就铁证了:# 后面那段服务器根本收不到。

第 6 步 · 用 curl -v 看请求行,六段"分家"的现场

curl -v "https://example.com/search?q=%E4%B8%AD"

在输出里找那一行 > GET /search?q=%E4%B8%AD HTTP/1.1——里面没有 https://、没有主机名、没有片段,只剩路径与查询串。主机名则出现在下面的 > Host: example.com 一行。 这一屏,就是第七节那张表的实物。

第 7 步 · 亲手撞一次相对路径的坑 用 4.2 那套起个本地服务器:

mkdir -p sub && echo '<img src="img/a.png">' > sub/index.html
python -m http.server 8000

然后分别访问 http://localhost:8000/sub/ 和 http://localhost:8000/sub,看图片能不能出来(服务器会替你 301,浏览器再次拼路径时的基准就变了)。这一下能让你彻底明白"相对路径是相对谁"。

十、30 秒实验

  1. 30 秒:把一条 URL 里的 # 改成 ?,观察行为完全不同——# 那版不发请求,? 那版会重新发一次请求。同一条网址,一个字符之差,去的地方完全不同。
  2. 30 秒:在地址栏手工输入 http://example.com:80/ 和 https://example.com:443/,看地址栏怎么把默认端口悄悄收起来。
  3. 30 秒:故意把路径里一个字母改成大写(/About → /about),看是否 404。验证路径区分大小写。

十一、常见误区

  1. "URL 就是域名。" —— 域名只是六段里的第二段。URL 还包含协议、端口、路径、查询串、片段。
  2. "URL 里的路径就是服务器上的文件路径。" —— 静态网站像,动态网站只是"路由",/user/123 背后可能压根没有这个文件(4.1 静态 vs 动态)。
  3. "改了 # 后面那截会重新加载页面。" —— 不会。它只在浏览器内部生效,一个字节都不出门。
  4. "http 和 https 只是多了个字母。" —— 一个是明文,一个是加了加密。不是版本高低,是"有没有穿衣服"。
  5. "我在地址栏敲的中文就是原样发给服务器的。" —— 出门前被换成 %E4%B8%AD 这类形式,中文会"长胖"到三倍。
  6. "URL 里不写端口就是没有端口。" —— 是默认端口:http 80、https 443(收 3.17)。
  7. "末尾那个斜杠有没有都一样。" —— 服务器常常区别对待,一个会 301 跳到另一个。这是"本地能开、上线 404"的常见原因。
  8. "+ 就是空格。" —— 只在查询串里这么理解,路径里 + 就是加号。稳妥做法是一律用 %20。
  9. "一个资源就一个 URL。" —— 一个页面往往有好几条等价 URL(www 与否、末尾斜杠、跟踪参数),缓存和统计都会因此对不上账。
  10. "URL 是安全的,服务器直接拿它去找文件就行。" —— URL 是用户能随手改的输入。路径里的 ../ 这类东西,服务器必须防。

十二、动手自测

❓ 自测一下
  1. URL 三个字母分别是什么?它和"域名"的关系是什么?
  2. URL 的六段分别是什么?哪一段"服务器根本收不到"?
  3. 请求真正发出时,路径和查询串装在哪里?主机名又去哪了?
  4. https:// 和 http:// 的默认端口各是多少?"不写端口"是什么意思?
  5. 为什么说"路径不一定对应磁盘上的文件"?
  6. https://example.com/ 这条地址里,实际被省掉了哪两段?
  7. 绝对 URL 和相对 URL 怎么区分?站内链接为什么优先用相对地址?
  8. 空格在 URL 里写成什么?中 一个字为什么变成 9 个字符?
  9. 为什么"改了 # 后面的内容,页面不重新加载"?用本节的分工来解释。
  10. 域名和路径,哪个区分大小写?结尾斜杠为什么要注意?

答案: ① Uniform Resource Locator(统一资源定位符)。域名只是 URL 里的"主机名"那一段——域名回答"哪台主机",URL 还要回答"用什么协议取、取哪一个资源"。 ② 协议名 / 主机名 / 端口号 / 路径 / 查询串 / 片段。片段(# 后面那段)服务器根本收不到,它只在浏览器里生效。 ③ 路径和查询串原样装进 HTTP 的请求行(GET /path/page.html?q=1 HTTP/1.1);主机名先被 DNS 换成 IP 用于建连接,再作为 Host 头带上——因为一台服务器上可能挂着好几个网站,得靠它区分。 ④ http 默认 80、https 默认 443(收 3.17)。"不写端口"=用协议的默认端口,不是"没有端口"。 ⑤ 因为动态网站的路径是程序自己定的规则(路由)。/user/123 这种地址背后可能没有对应的文件,是程序现场生成了页面(4.1 动态内容)。只有静态网站才近似"路径 = 文件位置"。 ⑥ 省掉了端口(补默认 80/443)和路径后半段(补默认文档名,通常是 index.html)。 ⑦ 绝对 URL 从协议写起、自带全套;相对 URL 只写一部分,由浏览器拿"当前页面地址"当基准拼出来。站内用相对地址:换域名不用改文件、本地预览也能用。代价是层级算错就 404。 ⑧ 空格写成 %20;中 先按 UTF-8 变成三个字节 E4 B8 AD,每个字节再写成 %XX,于是 中 → %E4%B8%AD,一个汉字 9 个字符。 ⑨ 因为片段根本不在"发给服务器的部分"里——它归浏览器自己管。所以改它不会产生新请求,页面自然不重新加载,只是滚动一下。 ⑩ 域名不区分大小写,路径区分大小写。结尾斜杠常被服务器区别对待(/about 与 /about/ 可能 301 互相跳),所以"本地能打开、上线 404"很多是它惹的。

十三、下一步学什么

相关笔记