开发者 Xe Iaso 指出一个很小但很刺手的问题:Go 标准库 net/url 直接解析 http://[fe80::4%eth0]:80 会报错。
报错点不在 IPv6 地址本身,而在那个 %。在 URL 里,% 是 percent-encoding 的起始符。%eth0 不是合法转义,Go 会把它判成 invalid URL escape。正确写法要写成:http://[fe80::4%25eth0]:80。
我更在意的不是 Go 严不严格,而是这类地址到底适不适合被塞进 URL。它牵涉 IPv6 link-local 地址、接口范围、URL 转义、不同操作系统的接口标识。每一层单看都讲得通,合在一起就很别扭。
问题不是 IPv6,而是 link-local 地址需要接口范围
IPv6 link-local 地址通常位于 fe80::/10。它只在本地链路内有效,不是拿来在公网里到处跑的地址。
麻烦来自多网卡场景。同一台机器上,不同接口都可能有类似 fe80::4 的地址。系统必须知道你说的是哪块网卡,所以需要一个 zone,也常叫 scope。
Linux 上常见写法是接口名,比如 eth0。Windows 上更常见的是接口 ID。也就是说,fe80::4%eth0 这种表达从一开始就带着系统味道,不是一个天然跨平台、跨机器稳定流转的标识。
这也是它放进 URL 后不舒服的根源。URL 追求的是可传递、可解析、可比较;link-local zone 追求的是本机接口上的精确定位。两套目标并不一致。
| 场景 | 写法 | 关键问题 |
|---|---|---|
| 普通 IPv6 host:port | [fe80::4]:80 | 方括号用来区分地址和端口 |
| 带 Linux zone 的地址 | [fe80::4%eth0]:80 | 地址语义成立,但 % 进入 URL 会冲突 |
| URL 中的正确写法 | http://[fe80::4%25eth0]:80 | %25 解码后才是 zone 分隔符 % |
Go 里转义后再解析,Hostname() 会返回 fe80::4%eth0。这说明 Go 并不是完全不认识 zone,而是要求 URL 字面量先满足 percent-encoding 规则。
URL 的百分号规则,让“人能看懂”的地址变成“机器才认”的地址
[fe80::4%eth0]:80 对网络工程师不难理解。它看起来也很自然:地址是 fe80::4,接口是 eth0,端口是 80。
但 URL 解析器看到的不是这层意思。它先看到 %,然后期待后面跟两个十六进制字符。%et 不合法,于是报错。
这里有一个现实取舍:库要不要替用户“猜”这个 % 是 zone 分隔符?
如果宽松处理,用户体验更好。配置里直接写 http://[fe80::4%eth0]:80,程序也许能跑。但代价是 URL 解析器要为一个小众场景开例外,后续还可能影响编码、比较、转发和日志输出。
如果严格处理,规则更干净。代价也很直接:用户必须记住反直觉的 %25。很多人第一次遇到时,会以为是 Go、代理或 HTTP 客户端坏了。
这就不是“改一行代码”能解决的问题。原文提到的线索包括 Nginx、Python requests 以及 IETF 关于 link-local URI 的讨论,至少表明多个生态都碰到过这条缝。
浏览器这边更谨慎。原文的判断是,浏览器当前并不支持带 IPv6 zone 的 URL,而且问题会牵到 Web 的 origin 模型。一个带本机接口范围的地址,如何参与来源判断、缓存、安全隔离,并不是放行解析那么简单。
RFC 9844 给了 IPv6 zone 在用户界面中的处理指导,但它主要是 UI 指导,不能简单理解成“URL 场景已经被彻底解决”。Go 的 net/url 目前也不像是沿着这条思路做宽松处理。
边界之事,差之毫厘。这里的毫厘就是一个 %。
真正该调整的是库、配置和测试,不是普通用户习惯
这个问题很小众。它要求几个条件同时成立:使用 IPv6 link-local 地址,地址里带 zone,还要把它写进 URL。
普通上网用户基本碰不到。最相关的是两类人。
一类是后端与网络编程开发者。比如做设备发现、局域网服务、路由器管理、嵌入式设备控制、测试环境绑定地址。你要做的不是把 fe80::4%eth0 直接拼进 URL,而是在进入 URL 前明确转义,或者把“人类输入”和“机器 URL”分成两层处理。
另一类是维护 URL、HTTP、代理和配置相关库的工程师。你们要给出清楚策略:严格报错、自动修正,还是在上层 UI 里做转换。策略不清楚,用户就会在 Linux 接口名、Windows 接口 ID、标准库解析规则之间来回撞墙。
更稳妥的动作可以很具体:
| 对象 | 应该做什么 | 不做的成本 |
|---|---|---|
| 后端/网络开发者 | 文档示例写 http://[fe80::4%25eth0]:80,不要只写原始 % | 配置看似正确,运行时报 URL 解析错误 |
| 代理/HTTP/URL 库维护者 | 明确是否接受未转义 zone,并补跨平台测试 | 不同环境表现不一,问题会被误判成网络故障 |
| 配置系统维护者 | 输入层允许人写 %eth0,保存或请求前转成 %25eth0 | 用户体验差,错误信息也难懂 |
接下来最该看的不是哪门语言先“认错”。更关键的是两个变量:标准库会不会给出更明确的文档;上层工具会不会把 zone 当成人类输入处理,而不是让用户自己背 URL 转义规则。
我的判断偏保守:底层 URL 解析器不宜为了少数场景轻易放宽规则,但上层工具不该把复杂性全甩给用户。能在 UI、配置校验和错误提示里解决的事,不必全压到标准库里。
回到开头那个报错,invalid URL escape 说的是一个字符不合法。它真正暴露的是一组边界没有对齐:本机接口、跨平台标识、URL 编码、HTTP 工具链,各守其义,却让工程师在缝里补锅。
