§ 4.1 · Section

输入网址与 URL 解析

URL Parsing · RFC 3986

整段旅程的第一步,发生在数据还一个字节都没出你的手机之前。浏览器要先看懂你输入的那串字:它是网址还是搜索词?协议写全了吗?主机是哪一段?端口是几号?——这一节讲的就是这场"读单子"的过程,以及它为什么比你想象的复杂得多。

生活场景
🧾 快递小哥拿到一张手写地址条

你写了一张地址条塞给快递小哥:"朝阳区幸福路 88 号 3 栋 1201 室 张三 收 · 易碎"。
小哥不会一眼就"看到一个地址",他是一段一段拆的:先看城市(决定发哪个分拨中心)、再看区和路(决定哪个网点)、再看栋和室(决定哪个快递员的哪一趟)、最后看备注(决定要不要加气泡膜)。

拆错一段,包裹就跑偏:城市看错,货直接飞去另一个省;室号看错,敲了半天邻居家的门。

浏览器处理网址完全是这个套路。https://shop.example.com:443/item/8848?color=red#reviews 这一串在你眼里是"一个链接",在浏览器眼里是七个必须分开处理的字段——而其中有一段(#reviews)压根不会发给服务器,就像"易碎"这个备注只给快递员看、收件人根本不知道。

上一步刚完成了什么,这一步要干什么

这是全章的第一节,所以"上一步"就是你的手指:你点了链接,或者在地址栏敲完字按了回车。此刻浏览器手里只有一样东西——一串字符。除此之外它什么都没有:不知道对方在哪、没有任何连接、没发出任何一个字节。

这一节要干的事是:把这串字符变成一份"可执行的行动计划"。干完之后,浏览器手里会多出下面这几样东西,它们是后面每一节的入场券:

一句话概括这一节的产出:把"一串字"变成"协议 + 主机 + 端口 + 路径"这四件确定的事。后面五节全是在这四件事的基础上往下走。

先把这一节的术语翻译成人话

这一节会冒出十来个术语,多数长得很像、很容易混。先用一张表把它们对齐到你熟悉的东西上,后面再遇到就不慌了。

术语换成大白话生活里对应的东西
URL(Uniform Resource Locator,统一资源定位符,读作"U-R-L")说白了就是"一件东西在网上的完整地址"一张写全了的快递面单:省市区路号栋室,一样不少
scheme(方案 / 协议部分,如 https说白了就是"用哪种方式送"邮局窗口的选项:平邮、挂号、EMS、同城闪送——同一个地址,送法不同
host(主机,域名或 IP)说白了就是"哪一台机器""城东那家老李杂货店"这个店名
port(端口号)说白了就是"这栋楼的哪一个门"小区门牌里的"几号房"(§ 1.3 讲过)
path(路径)说白了就是"进门之后往哪走"图书馆的索书号:三楼 → B 区 → 第 7 排 → 第 3 格
query(查询字符串,问号后面那串)说白了就是"附加的要求"点菜时说的"少盐、不要葱、加个蛋"
fragment(片段标识,井号后面那段)说白了就是"拿到之后翻到第几页"借到书之后自己翻到第 88 页——图书馆压根不知道你要看哪一页
百分号编码(Percent-Encoding,用 % 加两位十六进制表示特殊字符)说白了就是"把不能直接写的字,换成一个代号写"填表时姓名栏写不下生僻字,就在备注里注音说明
IDN(Internationalized Domain Name,国际化域名——非英文的域名)说白了就是"中文、日文、阿拉伯文写的域名"银行的中英文双名招牌:门口挂中文,营业执照上是英文全称
HSTS(HTTP Strict Transport Security,严格传输安全——网站声明"以后只准用 HTTPS 来找我")说白了就是"这家店贴了张条:只收刷卡,别递现金"商场门口的"本店谢绝自带饮食"告示,你还没进门就已经生效
omnibox(多功能地址栏——既能输网址也能输搜索词的那一栏)说白了就是"一个既能查号又能拨号的框"医院的一体化窗口:挂号、缴费、查报告都在这一个窗口办

看完这张表,你大概已经能猜到这一节的味道了:URL 这个东西,本质上就是把"一份写得足够清楚的地址"标准化成机器能严格解析的格式。标准化到什么程度?连"哪个字符算分隔符"都写进了国际规范——那份规范叫 RFC 3986(2005 年发布,URI 的通用语法标准)。后面凡是提到"规范怎么规定的",指的都是它。

Analogy · 一个 URL 就是一张填满的快递面单

打个比方,把 https://shop.example.com:443/item/8848?color=red#reviews 摆到快递面单上,是这样一一对应的:

https —— 面单最上面勾选的"服务方式":这次要走加密专线(顺丰保价件)而不是普通平邮。勾错这一栏,整个运输方式都不同。
shop.example.com —— 收件单位名称。注意:这是名字不是地址,快递系统还得拿它去查真实门牌(这就是 § 4.2 的 DNS)。
:443 —— 门牌里的房间号。多数时候不用写,因为"走加密专线默认送 443 室"是行业惯例。
/item/8848 —— 进门之后的内部路线:"货架区 → 8848 号格位"。
?color=red —— 面单备注栏的附加要求:"要红色那款"。
#reviews —— 这一栏最特别:它不写在面单上,只记在你自己的小本子上。意思是"东西到手后,我先翻到评价那一页看"。快递公司完全不知道你要看哪一页,也不需要知道。

为什么要强调最后这一条?因为它有非常实际的后果:你在网址后面加 #reviews,服务器日志里压根不会出现这几个字。所以靠服务器统计"多少人看了评价区"是统计不到的——这一段从来没离开过你的手机。

URL 的七个部分:逐个拆开看

现在正式动手拆。RFC 3986 定义的通用语法,写成一行是这个样子:

  scheme ":" ["//" [userinfo "@"] host [":" port]] path ["?" query] ["#" fragment]

把它套到一个真实网址上:

  https://alice:s3cret@shop.example.com:8443/item/8848?color=red&size=42#reviews
  └─┬─┘   └─────┬────┘ └───────┬──────┘ └┬─┘└────┬───┘ └───────┬──────┘ └──┬──┘
  ①scheme    ②userinfo      ③host    ④port  ⑤path      ⑥query      ⑦fragment
   协议        用户信息        主机     端口   路径        查询串       片段

七个部分,只有 schemehost 是访问一个网站时几乎必须有的,其余五个都可以省略。下面这张表把每一部分的职责、默认值、以及"省略了会怎样"一次讲清。

#部分例子它决定什么省略时会怎样
scheme 协议https用哪套规矩通信,以及要不要加密浏览器地址栏里会帮你猜(通常猜 https);在网页源码里省略则表示"跟当前页一样"
userinfo 用户信息alice:s3cret直接把用户名密码写在网址里绝大多数情况都省略。现代浏览器对含密码的 URL 极为警惕,因为这是钓鱼常用招数
host 主机shop.example.com找哪台机器——DNS 查询的输入不能省。省了浏览器不知道该找谁
port 端口8443敲这台机器的哪个门省略则用协议的默认端口:http → 80,https → 443
path 路径/item/8848要这台机器上的哪份资源省略等价于 /,也就是网站首页
query 查询串color=red&size=42给这次请求附加参数省略就是"没有附加要求"
fragment 片段reviews拿到内容后,本机滚动到哪个位置省略就是从头显示。这一段永远不发给服务器

这张表里有三处最容易被误解,值得单独说清。

第一处:// 那两个斜杠不是装饰,它是"后面跟着一个主机名"的信号。对比一下就明白了:https://example.com/a 有双斜杠,表示"去 example.com 这台机器上取 /a";而 mailto:zhang@example.com 没有双斜杠,因为它压根不需要"连到某台机器",冒号后面直接就是内容。tel:10086 同理。说白了,双斜杠的意思是"这是个网络地址,得先找到机器"。

第二处:端口的默认值是"协议自带的常识",不是网址里省略了什么。你写 https://example.com,浏览器实际连的是 443 端口——这个 443 不是从网址里读出来的,是"https 默认 443"这条规矩带来的。所以如果某个网站部署在 8443 端口,你不写端口号就永远连不上,会一直卡在建连接那一步(§ 4.3 会讲这种卡法长什么样)。

第三处:userinfo 这一段是历史遗留,而且是钓鱼的经典道具。看这个网址:https://www.yourbank.com@evil.example/。粗看以为是去银行,其实 @ 前面全是"用户信息",真正的主机是 evil.example换成大白话:这就像有人递给你一张名片,正面印着"中国银行",背面小字才写着真实地址是城郊某民房。现代浏览器会警告或直接拒绝这类地址,但你自己也该记住这条读法——看主机名只看最后一个 @ 之后、第一个 / 之前的那一段。

浏览器在联网之前先干了什么

很多人以为按下回车的下一件事就是"发数据出去"。其实在任何一个字节离开你的设备之前,浏览器已经忙完了好几轮。这些活儿全在本机完成,快得你察觉不到(微秒到毫秒级),但它们会实质性地改变后面的走向——甚至可能让整段旅程直接跳过大半。

按真实顺序,浏览器大致做这七件事:

顺序动作说白了就是可能的后果
1判断输入意图你敲的到底是网址还是搜索词?判成搜索词就直接送去搜索引擎,整个 URL 流程另起一遍
2补全与规范化你少写的部分我替你补,写乱的部分我理顺Example.COMexample.com/a/./b/../c/a/c
3查 HSTS 名单这个域名有没有声明过"只准用 HTTPS"?在名单上就把 http 直接改成 https不发出任何明文请求
4查本地缓存(HTTP 缓存)这份东西我是不是刚拿过、还没过期?命中且新鲜 → 整段旅程结束,直接进渲染,一个包都不发
5查 Service Worker这个站有没有装过"本地小代理"?装了的话它可能直接返回离线内容,同样不联网
6查 DNS 缓存这个域名的 IP 我记着没?记着就跳过 § 4.2 的大部分工作
7查连接池跟这台机器我是不是还连着一条线?连着就跳过 § 4.3 和 § 4.4,直接发请求

看出规律了吗?这七步里有四步的目标都是"能不联网就不联网"。本质上就是四道由近到远的关卡,每一道都在问同一句话:"这活儿我在本地能不能办了?"办得了就当场结束,办不了才往外走一步。

打个比方,这相当于你想做个菜要用酱油:先看灶台边的调料罐(浏览器内存缓存)→ 再看厨房吊柜(磁盘缓存)→ 再看阳台储物间(本机 DNS 记录)→ 都没有才穿鞋下楼去超市(真正联网)。没人会为了一勺酱油每次都下楼,浏览器也一样。这就是为什么第二次打开同一个网站往往快得像本地文件——因为它根本就是本地文件。

第一件事:你输的是网址还是搜索词

现代浏览器的地址栏叫 omnibox(多功能地址栏),既收网址也收搜索词。这带来一个每天发生几十亿次的判断题:用户敲的这串字,该当地址解析,还是该丢给搜索引擎?

各家浏览器的具体规则不完全一样、也在持续调整,但共同的判断线索是清楚的:

这里有个真实会踩到的坑:公司内网的单词主机名和搜索词长得一样。你在办公室敲 wiki 想上内部百科,浏览器可能直接给你搜了一堆维基百科的结果。换成大白话:这就像你在办公室喊一声"小李",前台不知道你是要找同事小李,还是要她帮你查一下"小李"是谁。解决办法很朴素——wiki/ 加个斜杠,或者干脆写全 http://wiki/,把意图明确掉。

还有一个安全后果值得知道:你在地址栏里敲的每一个字,都可能被实时发给搜索引擎做联想补全。这就是为什么有些公司会关掉地址栏联想功能——因为你打了一半的内部系统地址,可能已经飘出去了。相当于你在窗口前刚说出半句话,旁边的广播已经把它播出去了。

第二件事:URL 规范化——把你写歪的地址理顺

人手输入的网址往往不规矩:大小写混乱、多余斜杠、带点的相对路径、末尾少个斜杠。浏览器会先做一轮规范化(Normalization,把等价但写法不同的 URL 统一成一种标准写法)。这不是洁癖,是必须——因为缓存查找、同源判断都要靠"字符串是否相同"来做,写法不统一就查不中。

规范化动作处理前处理后为什么要这么做
scheme 和 host 转小写HTTPS://Shop.Example.COM/Ahttps://shop.example.com/A域名不区分大小写;但路径区分,所以 /A 保持不动
去掉默认端口https://a.com:443/https://a.com/443 是 https 默认值,写与不写等价
补上空路径https://a.comhttps://a.com/空路径等价于根路径
解析点段/a/b/../c/./d/a/c/d.. 是上一级,. 是当前级,跟文件夹一个道理
百分号编码大写化%3a%3A规范建议十六进制用大写,便于字符串比较
解开不必要的编码%7Euser~user~ 属于"无需编码字符",编了反而不规范

其中"域名不区分大小写、路径区分大小写"这一条是最容易出事的。https://Example.com/Photo.JPGhttps://example.com/photo.jpg——前半段等价,后半段在多数 Linux 服务器上是两个不同的文件,后者很可能直接 404。本质上就是:域名这一段由 DNS 管,DNS 规定不分大小写;路径这一段由服务器上的文件系统管,而 Linux 的文件系统认大小写。相当于寄快递时"BEIJING"和"beijing"邮局都认,但收件人姓名写错一个字,前台就是找不到人。

另有一个尾斜杠的坑:https://a.com/bloghttps://a.com/blog/ 在规范上是两个不同的 URL。多数网站会用重定向(§ 4.5 讲)把其中一个跳到另一个,但那要多花一个来回。说白了,多打一个斜杠有时真的会慢一点点——虽然肉眼感觉不到。

第三件事:HSTS——有些站你连明文都发不出去

假设你在地址栏敲了 http://shop.example.com,明明写的是不加密的 http。但如果这个域名启用了 HSTS(HTTP Strict Transport Security,严格传输安全),浏览器会在发出任何请求之前,自己把 http 改成 https

为什么要有这个机制?因为不这么做,会留下一个致命缝隙。设想没有 HSTS 的情况:

你敲 http://bank.example
   ↓ 明文请求已经发出去了(这一步就是缝隙)
服务器回:301 跳转到 https://bank.example
   ↓
浏览器改用 HTTPS 重新访问,从此加密

问题在于:第一个明文请求已经在路上被人看见了。
更糟的是——如果路上有坏人,他可以拦下这个明文请求,
假装自己是服务器,永远不告诉你"该用 HTTPS",
你就会一直在明文里跟他聊,密码照样输给他。
这种攻击叫 SSL Stripping(降级剥离)。

HSTS 的做法是让网站在响应头里声明一句"以后 N 秒内只准用 HTTPS 来找我",浏览器记下来,之后本机自行改写,那个明文请求根本不产生。打个比方,这相当于一家银行给所有老客户发过通知:"以后来我们这儿办事只走后门那条监控走廊,前门那条巷子一律不走。"客户记住了,下次即使随口说"从前门进吧",自己走到路口也会自动拐弯。

但这里还有一个"第一次"的问题:你从来没访问过这个网站,怎么会知道它要求 HSTS?解法是 HSTS 预加载列表(Preload List)——一份内置在浏览器里的域名清单,凡在清单上的域名,哪怕你是第一次访问,浏览器也一律强制 HTTPS换成大白话:这份清单相当于印在员工手册里的"以下这些客户一律走加密流程",新员工上岗第一天就知道,不需要客户再说一次。

这带来一个很实用的知识点:你在浏览器里访问某些站点,明明输的是 http,地址栏却瞬间变成 https,中间连一次网络请求都没有。这不是网站跳转的,是你自己的浏览器改的。查看方法是在浏览器的网络面板里看——你会发现根本没有那条 301 记录。

第四件事:四级缓存查找——最快的请求是没发出去的请求

接下来浏览器要问一句非常实在的话:"这东西我手上是不是已经有了?"这一问不是问一次,而是由近到远问四次,因为缓存不是一个地方,是好几层。

层级存在哪速度量级生活里对应清空方式
内存缓存(Memory Cache)浏览器进程的内存里纳秒到微秒级灶台边那个盐罐——伸手就够关掉标签页就基本没了
Service Worker网站自己注册的本地脚本微秒到毫秒级你雇的私人采买——他自己有小仓库在开发者工具里注销
磁盘缓存(Disk Cache)硬盘上的缓存目录毫秒级(随机读)厨房吊柜里那袋备用的清除浏览数据
推送缓存 / 连接级缓存当前连接的生命周期内微秒级刚拆开还摊在案板上的那份连接一断就没了

关键在于"命中"分两种,后果完全不同,这是很多人混淆的地方:

本质上就是两种不同的做法:前者是"冰箱里的酸奶还没到保质期,直接喝";后者是"保质期到了,但打电话问店家说这批延期了还能喝"。后一种还是得打个电话,前一种连电话都不用打。所以做网站优化时,把资源设成长期强缓存的收益远大于依赖协商缓存——省掉的是整个来回。

顺便说清一个常见误解:按 F5 刷新和按 Ctrl+F5 强制刷新,走的路完全不同。普通刷新一般还会用缓存(但可能带上协商);强制刷新则会告诉浏览器"当我从来没来过,全都重新拿"。相当于普通刷新是"再看一眼冰箱",强制刷新是"把冰箱清空重新采购一遍"。这就是为什么改了网页样式看不到效果时,强制刷新往往有用——这也正是本站每次改共享样式都要给文件加版本号的原因。

路径与查询串:一条分界线两种性质

路径(/item/8848)和查询串(?color=red)都在描述"我要什么",但它们的性质有一条重要区别,理解了这条区别,你会一下看懂很多网站的地址设计。

路径描述的是"东西在哪",查询串描述的是"怎么处理它"。换成大白话:路径是图书馆的索书号,指向书架上确定的那一本;查询串是你递给馆员的附加条件——"我要复印版""帮我按出版年排序""只给我第二章"。索书号变了就是另一本书;附加条件变了还是那本书,只是给你的形式不同。

path 路径query 查询串
写法斜杠分层:/a/b/c问号开头,键值对用 & 连:?k1=v1&k2=v2
层级关系有——像文件夹一层套一层无——键值对之间是平的,顺序原则上无所谓
典型用途定位资源:哪个商品、哪篇文章、哪个用户筛选、排序、分页、追踪来源、翻译语言
大小写区分(由服务器/文件系统决定)区分(由后端程序决定)
对缓存的影响不同路径必然是不同缓存条目不同查询串通常也算不同条目——这点常被忽略
会不会进日志会——所以绝对不要把密码放在查询串里

最后那一条是这一节里最该被记住的安全常识。查询串会出现在服务器访问日志、浏览器历史、代理日志、甚至第三方统计里。如果登录接口写成 /login?user=alice&pass=123456,那这个密码就同时被记在了至少四个地方。打个比方,这相当于你在银行大厅把密码大声念给柜员——柜员听清了没错,但排队的人、监控录像、大堂经理的记录本全都收到了。正确做法是把敏感数据放进请求体(POST 的 body),这一点 § 4.5 会详说。

还有一个查询串带来的实际问题:它会撑爆缓存。很多分享链接后面会挂一串追踪参数(比如 ?from=wechat&share_id=xxxxx),每个人分享出来的参数都不一样,于是同一个页面在缓存里变成了成千上万个不同条目说白了,就好比同一款商品因为每张标签上的经手人编号不同,仓库被迫按编号分格存放,一款货占了一万个格子。这也是为什么专业的 CDN 配置里会有"忽略指定查询参数"这一项。

片段标识:那个永远不出门的 #

七个部分里,fragment(片段标识,井号后面那段)最特殊,因为它从来不会被发送给服务器。这不是某个浏览器的实现选择,是 RFC 3986 明确规定的:片段的解释责任在客户端。

它的原始用途很朴素:页内跳转https://a.com/doc#chapter3 的意思是"把 /doc 这份文档拿来,然后滚动到 id 为 chapter3 的那个元素"。前半段是网络的事,后半段是渲染的事(§ 4.6 讲)。

这个"不出门"的性质带来三个很实际的后果:

另外补一条容易混的:# 后面的内容和 ? 后面的内容顺序不能颠倒。规范规定 query 在前、fragment 在后。写成 /a#b?c=d 的话,?c=d 会整个被当成片段的一部分——因为片段一旦开始,后面所有字符都归它。本质上就是"井号是最后一道闸门,闸门一落,后面全归自己管"。

百分号编码:为什么网址里会出现一堆 %E4%B8%AD

你一定见过这种网址:https://a.com/search?q=%E7%89%9B%E8%82%89%E9%9D%A2。那一串百分号看着像乱码,其实是百分号编码(Percent-Encoding)——把一个不能直接写进 URL 的字符,换成"% + 该字符字节的两位十六进制"来表示。

为什么需要这套东西?因为 URL 里有些字符已经被派了"结构性差事":/ 分层、? 开查询、& 分参数、# 开片段、= 连键值。如果你要搜的内容本身就含这些字符,就必须先把它"伪装"起来,否则解析器会当成结构符号处理,整个网址被拆错。

Analogy · 百分号编码就是"填表时的转义写法"

打个比方,你去邮局填一张汇款单,"附言"那一栏只有 20 格,而且格与格之间用竖线分开。现在你的附言里本身就含一个竖线符号——直接写进去,柜员会以为那是分格线,附言被劈成两截。

正规做法是:约定一个"转义写法",比如规定"竖线一律写成 #124 这样的代号"。柜员看到 #124 就知道"这不是分格线,这是内容里的一个竖线字符"。

URL 里的百分号编码就是这个代号系统。/ 写成 %2F、空格写成 %20? 写成 %3F& 写成 %26。至于中文,因为 URL 规范只允许 ASCII 字符,所以中文得先按 UTF-8 编码变成几个字节,每个字节再各自写成一个 %XX"中"这个字在 UTF-8 里是三个字节,所以写出来就是三段:%E4%B8%AD

这也解释了一个常见现象:为什么复制一个含中文的网址,粘出来会变得巨长。因为你在地址栏看到的中文是浏览器"翻译回来给你看"的样子,真正传输的一直是那串百分号。相当于柜员在单据上写的是代号,但打印给你的回执上贴心地还原成了你能读的字。

常见字符的编码值,记几个最有用的就够:

字符编码为什么必须编码常见踩坑场景
空格%20(表单里也可能是 +URL 里不允许出现裸空格文件名带空格的下载链接,复制到聊天软件被截断
/%2F它是路径分隔符参数值里带日期 2026/08/08,不编码会被当成三层路径
&%26它是参数分隔符公司名叫"张三 & 李四",不编码会被切成两个参数
=%3D它是键值连接符Base64 编码的值结尾常有 =,不处理会解析错乱
#%23它是片段起始符参数值里带话题标签,不编码则后面全被吞进片段
%%25它自己就是转义符要表示"打折 50%",不编码会被误当成一个编码的开头
中文(如"中")%E4%B8%ADURL 只允许 ASCII不同系统按不同字符集编码,导致对方看到乱码

最后一行藏着一个真实的经典 bug:如果一边按 UTF-8 编码、另一边按 GBK 解码,中文就会变成乱码。这不是"网络问题",是两头对同一串字节的理解不一致。说白了,就好比你按公制写了"3 尺",对方按英制读成了"3 英尺"——数字没错,单位错了,结果差得远。所以现代规范一律要求 URL 中的非 ASCII 字符按 UTF-8 编码。

中文域名:那个 xn-- 开头的怪东西

域名部分的非英文字符处理,跟路径部分完全是另一套机制,这也是最容易混淆的地方。路径里的中文用百分号编码,域名里的中文用一套叫 Punycode 的转换法。

原因在于 DNS 系统(§ 3.4 会详讲 DNS 协议本身)诞生于 1980 年代,它的设计里只认得 ASCII 里的字母、数字和连字符。要在这套老系统上支持全世界的文字,又不能推翻重来,工程师们想出的办法是"把非英文字符可逆地翻译成一段合法的 ASCII"。这套翻译法叫 Punycode,翻译出来的域名一律以 xn-- 开头。

你看到的(浏览器显示给人看):      中国.cn
实际在 DNS 里查询的(机器用):     xn--fiqs8s.cn

你看到的:                        例子.测试
实际查询的:                       xn--fsqu00a.xn--0zwm56d

规律:xn-- 是固定前缀,意思是"后面这段是被翻译过的"。
所以在地址栏看到 xn-- 开头的域名,说明它原本是非英文的。

这套机制叫 IDN(Internationalized Domain Name,国际化域名)。它带来一个非常严重的安全隐患,叫同形字攻击(Homograph Attack):世界上有些字母长得几乎一样但编码完全不同——比如西里尔字母的 а 和拉丁字母的 a,肉眼几乎无法区分。攻击者可以注册一个用西里尔字母拼的"apple.com",域名在你眼里跟真的一模一样,实际却是完全不同的域名,指向完全不同的服务器。

换成大白话:这就像有人开了一家店,招牌上"永和大王"四个字里的"和"用的是另一个长得几乎一样的异体字。你站在门口看不出差别,工商登记里却是两家毫不相干的公司。浏览器的应对办法是:当检测到一个域名混用了多种文字体系时,直接把地址栏里的显示切换成 xn-- 原形——让你一眼看出"这域名不对劲"。所以下次看到地址栏出现 xn--,而你并没有故意访问中文域名,那就要提高警惕了。

URL、URI、URN:三个词到底什么关系

你会在文档里同时见到 URL 和 URI 两个词,有时还会冒出 URN。它们的关系其实很简单,一张表加一句话就说完了。

缩写全称中文它回答的问题例子
URIUniform Resource Identifier统一资源标识符"这是哪个东西?"——只要能唯一标识就算以下两者都是 URI
URLUniform Resource Locator统一资源定位符"这东西在哪、怎么拿?"https://a.com/book/1
URNUniform Resource Name统一资源名称"这东西叫什么?"——不管在哪都是它urn:isbn:9780132350884

一句话记住:URI 是大类,URL 和 URN 是它下面的两种,区别是 URL 告诉你"去哪拿",URN 只告诉你"它叫什么"。

打个比方,一本书的 ISBN 书号就是 URN——不管这本书在北京图书馆还是在你家书架上,书号永远是那一串,但书号本身不告诉你去哪儿能借到。而"市图书馆三楼 B 区第 7 排第 3 格"就是 URL——它告诉你确切位置,能照着去拿,代价是书一挪位置,这个地址就失效了

这个区别解释了网络上一个永恒的痛:链接失效。你收藏的网址过两年打不开了,因为 URL 绑定的是"位置"而不是"东西本身"。网站一改目录结构,所有旧链接全断。说白了,URL 记的是"东西放在哪个格子",格子一动就找不着了。这也是为什么正规的学术资料会用 DOI(数字对象唯一标识符)这类偏 URN 性质的编号,而不是直接给网址。

实践中该用哪个词?规范文档里一律用 URI(RFC 3986 的标题就是 URI),日常和代码里习惯说 URL。两者在讲"一个网址"这件事上,日常语境里可以当同义词用,不必纠结。

同源策略:为什么 URL 的前三段决定了"谁能访问谁"

URL 的前三段——协议 + 主机 + 端口——合在一起有个专门的名字叫(Origin)。这三段只要有一个不同,就算不同源。浏览器有一条铁律:不同源的页面之间,默认不许互相读对方的数据。这条规矩叫同源策略(Same-Origin Policy)。

https://a.com/x同源吗为什么
https://a.com/y/z只有路径不同,路径不参与判断
http://a.com/x协议不同(一个加密一个不加密)
https://b.com/x主机不同
https://sub.a.com/x主机不同——子域名也算不同主机,这点最常被误解
https://a.com:8443/x端口不同

为什么要这么严?想一个场景就明白了:你在一个标签页登录了网银,同时另一个标签页开着某个不认识的小网站。如果没有同源策略,那个小网站的脚本就能直接读取网银页面里的余额、账号,甚至替你发起转账。说白了,同源策略相当于办公楼里每家公司都锁自己的门——大家共用大楼(浏览器),但不许串门翻别人的抽屉。

子域名也算不同源这一条,实际影响很大。shop.example.compay.example.com 虽然是同一家公司的,在浏览器眼里也是两家。要让它们互通,得由服务器明确授权——这就是 CORS(Cross-Origin Resource Sharing,跨源资源共享)机制,服务器在响应头里点名说"我允许某某源来读我"。相当于一家公司主动给另一家发一张门禁卡,写清楚"此卡只能进会议室,不能进财务室"。

这里要澄清一个很常见的误解:同源策略限制的是"读取响应",不是"发出请求"。一个跨源的请求往往真的发出去了、服务器也真的处理了,只是浏览器不把结果交给发起它的脚本。换成大白话:信寄出去了、对方也回信了,但门口的保安把回信扣下不给你看。这个区别很要紧——它意味着"因为同源策略所以别人打不到我的接口"是错的想法,服务器端该有的校验一样都不能省。

整节小结:一次 URL 解析的完整时间线

把这一节讲的全部动作按真实顺序串起来,就是下面这张流水线。这也是本章的"第 0 和第 1 阶段"的全貌。

你按下回车(假设输入的是 shop.example.com/item/8848)
  │
  ├─① 意图判断:含点、末段是 com → 判定为网址,不去搜索
  │
  ├─② 补全 scheme:地址栏没写协议 → 补 https(现代浏览器倾向优先试 HTTPS)
  │
  ├─③ 查 HSTS:这个域名在名单上吗?在 → 强制 https,明文请求不产生
  │
  ├─④ 语法解析(按 RFC 3986 拆七段)
  │      scheme = https
  │      host   = shop.example.com
  │      port   = 443(默认值,不是从字符串里读的)
  │      path   = /item/8848
  │      query  = (空)
  │      fragment = (空)
  │
  ├─⑤ 规范化:host 转小写、点段展开、编码大写化
  │
  ├─⑥ 域名处理:含非 ASCII 吗?含 → Punycode 转成 xn-- 形式
  │
  ├─⑦ 查 HTTP 缓存:有新鲜副本吗?
  │      ├─ 有 → 直接进渲染(§ 4.6),整段网络流程全部跳过 ★
  │      └─ 没有 → 继续
  │
  ├─⑧ 查 Service Worker:有拦截脚本吗?有 → 交给它决定
  │
  ├─⑨ 查连接池:跟 shop.example.com:443 还有活着的连接吗?
  │      ├─ 有 → 跳过 § 4.3 和 § 4.4,直接发请求 ★
  │      └─ 没有 → 继续
  │
  └─⑩ 交出主机名,进入 DNS 查询 → 这就是 § 4.2 的起点

注意那两个 ★ 号。它们标出的是真实世界里最常走的两条捷径——你刷一个已经打开过的网站时,绝大多数请求都从这两个岔口就结束了。本质上就是:完整走七个阶段这件事,在你一天的上网行为里可能只发生几十次;剩下成千上万次请求,都是靠这些捷径省下来的。

动手验证:三条你现在就能试的命令

光看不练记不住。下面三件事在你自己的电脑上就能做,花不到两分钟,做完你对这一节的理解会牢固得多。

第一件:看浏览器怎么拆一个 URL。在任何网页上按 F12 打开开发者工具,切到 Console(控制台),粘这一行按回车:

new URL('https://alice@shop.example.com:8443/a/b/../c?q=%E4%B8%AD&n=1#top')

浏览器会把它拆成一个对象,你会看到:
  protocol : "https:"
  username : "alice"
  hostname : "shop.example.com"
  port     : "8443"
  pathname : "/a/c"          ← 注意 ".." 已经被解掉了
  search   : "?q=%E4%B8%AD&n=1"
  hash     : "#top"

这就是浏览器内部真实的解析结果——七段清清楚楚。

第二件:亲眼看 Punycode 转换。还在控制台,试这一行:

new URL('https://中国.cn/').host

输出会是 "xn--fiqs8s.cn" —— 你看到的中文,机器用的是这一串。

第三件:看缓存有没有命中。打开开发者工具的 Network(网络)面板,随便刷新一个网站,看 Size(大小)那一列:写着具体字节数的是真从网上拿的;写着 from memory cachefrom disk cache 的,就是压根没出门的;写着 304 的,是出了门但只问了一句"变了吗"。这三种在同一页上往往同时存在——一次页面加载里,有些资源走了完整旅程,有些一步没动。

做完这三件事,你对"URL 解析"的印象就从"一个术语"变成了"一件看得见的事"。这也是本章的读法:每一节都尽量给你一个能自己验证的动作,因为流程类的知识光记顺序最容易忘,动手看过一次就忘不掉。

相对 URL:网页里九成的链接都不是写全的

到目前为止我们看的都是绝对 URL(写全了协议和主机的那种)。但你打开任何网页的源码,会发现里面绝大多数链接都是这个样子:/css/main.css../images/logo.pngnext.html。这些叫相对 URL——它们不写全,靠"以当前页面为基准"来推算。

浏览器要把相对 URL 补成绝对 URL 才能发请求,这个过程叫解析基准(Resolution against a base URL),RFC 3986 专门有一节讲它的算法。规则不难,看一张表就懂:

写法叫什么https://a.com/blog/2026/post.html 为基准,补成
https://b.com/x绝对 URL就是它自己,跟基准无关
//b.com/x协议相对https://b.com/x——只借协议,主机另说
/css/main.css根相对https://a.com/css/main.css——从网站根开始
img/logo.png路径相对https://a.com/blog/2026/img/logo.png——从当前目录开始
../img/logo.png上级相对https://a.com/blog/img/logo.png——先退一级
?page=2只换查询串https://a.com/blog/2026/post.html?page=2
#top只换片段同一页面,只滚动位置——不发请求

换成大白话,相对 URL 就是"省略了共同前缀的说话方式"。你在办公室里跟同事说"去三楼会议室",不用说"某市某区某路 88 号写字楼 A 座三楼会议室"——因为你俩都在这栋楼里,前缀是共识。但一旦这句话被传到楼外的人耳朵里,就完全没法用了——他不知道是哪栋楼的三楼。

这正好解释了两个常见 bug:

这也是为什么老练的开发者偏好根相对写法/css/main.css):只要网站根不变,页面挪到哪一层都不影响。说白了就是把参照点从"我现在站的地方"换成"大楼门口"——参照点越稳定,说法越不容易失效。

常见的五种 URL 出错场景与它们的症状

这一节的知识最直接的用处,是让你看到一个打不开的链接时能先判断问题出在哪一段,而不是笼统地说"这链接坏了"。下面五种是最常遇到的,症状各不相同。

症状大概是哪一段出了问题为什么会这样怎么确认
浏览器直接去搜索了,没访问网站scheme / 意图判断输入被当成了搜索词(常见于单词主机名、含空格)手动补上 http:// 再试
提示"无法找到该服务器"或 DNS 相关错误host 主机名域名拼错、域名过期、或本机 DNS 有问题nslookup 域名 看能不能解析出 IP(§ 4.2 详讲)
页面一直转圈直到超时port 端口或防火墙端口写错、或对方端口没开、或被中间设备拦了换个网络试;确认端口号写对了
返回 404 找不到页面path 路径路径拼错、大小写错、或资源已被删除移动把路径逐段删掉往上试,看哪一层还在
页面打开了但内容不对/参数没生效query 查询串参数名写错、值没做百分号编码、或 & 被误写成 &逐个删参数试,定位是哪个参数的问题

这张表里最值得学的是第四行那个"逐段删路径往上试"的手法。比如 https://a.com/docs/2026/guide/intro.html 报 404,你依次试 /docs/2026/guide//docs/2026//docs/——哪一层能打开,问题就在下一层。本质上就是二分排查:相当于找不到某间办公室时,先确认楼对不对、再确认层对不对、最后才确认门牌对不对,而不是站在门口反复敲同一扇门。

另外补一条特别容易忽略的:从聊天软件或文档里复制来的链接,末尾常常粘上了标点或空格。中文输入法打出的句号、右括号、书名号跟在链接后面,会被一起复制进去,于是路径莫名多了个字符导致 404。相当于抄地址时把句号一起抄进了门牌号里。碰到"这链接明明是对的却打不开",先检查一下末尾有没有多余字符——这一招解决过无数次问题。

Recap · 收束

这一节讲的是整段旅程里唯一一个字节都还没出门的阶段——但它决定了后面所有阶段的走向。
URL 七段(RFC 3986):scheme 协议 · userinfo 用户信息 · host 主机 · port 端口 · path 路径 · query 查询串 · fragment 片段。只有 scheme 和 host 是必须的;端口有默认值(http 80 / https 443);片段永远不出门
浏览器在联网前做七件事:判断意图(网址还是搜索词)→ 补全与规范化 → 查 HSTS(可能直接改写成 https)→ 查 HTTP 缓存 → 查 Service Worker → 查 DNS 缓存 → 查连接池。其中四件事的目标都是"能不联网就不联网"——最快的请求永远是没发出去的那个。
三条最实用的细节:域名不分大小写、路径分大小写(这是 404 的常见来源);密码绝不能放查询串(它会进日志、历史、代理记录至少四个地方);域名里的中文走 Punycode(xn--),路径里的中文走百分号编码(%XX——两套完全不同的机制,别混。
前三段(协议+主机+端口)合起来叫"源",它决定了同源策略——子域名也算不同源,而且这条策略限制的是"读响应"不是"发请求"。
下一步交出什么:这一节的最终产物就是一个主机名 shop.example.com。它马上要被交给下一位——DNS,去换回一个真正能拨号的 IP 地址。§ 4.2 就从这里接着走。

☰ 主页
Xue Hai Wu Ya · Network · § 4.1 · 输入网址与 URL 解析