简体中文

开发者工具 · URL 编码器和解码器

WHATWG URL 标准与 RFC 3986:为什么浏览器和库不同意

· 背景

url 编码 标准 开发者工具

浏览器和库在 URL 解析上的分歧
原始 ToolAcre 矢量图

URL 有两种现行定义,但它们的目的不同。这篇文章解释了为什么 WHATWG 编写了自己的标准,两者在编码和解析方面有所不同,以及您的代码遵循哪一个。

严格库拒绝但浏览器愉快加载的 URL — 一个字符串,两个结论

JavaScript 中带有反斜杠的字符串可能会被浏览器顺利地解释为 URL 路径的一部分。相同的字符串到达​​ Python 库后端,但它拒绝解析它,因为不允许使用反斜杠。一个 URL,两种不同的结果。两者都没有错——它们遵循不同的标准。 WHATWG 标准描述了浏览器对现实世界 URL 的实际操作,包括它们如何处理格式错误的输入。 RFC 3986 定义了 URL 理想情况下应遵循的正式语法。许多基于 RFC 3986 构建的后端库严格执行该语法并拒绝其之外的任何内容。

当您在环境之间移动数据时,这种差异很重要。浏览器接受的 URL 可能会在后端工具中验证失败。了解代码实现的标准可以防止调试幻象问题——URL 在一个地方工作正常,但在其他地方却莫名其妙地失败,没有明显的原因。

为什么 WHATWG 重新开始 — 描述浏览器对格式错误的输入真正做了什么,而不是有效的输入

WHATWG 工作组于 2004 成立,旨在标准化浏览器在实践中实际处理 URL 的方式,而不是定义浏览器不遵循的更严格的正式规则。 RFC 2396 描述了正式的语法规范,但浏览器实际上从未完全正确地遵循它。现实世界的浏览器开发了实用的规则来容忍空格、处理转义字符以及从 RFC 未预料到或期望的格式错误的输入中恢复。

RFC 3986 到达 2005,具有格式正确的 URL 和严格要求的正式语法。浏览器实现 WHATWG;后端库通常实现 RFC 3986。

编码集与保留字符 — URL 标准的每个组件列表如何与 RFC 3986 的类别相关

RFC 3986 将字符分为三类:保留的、未保留的以及必须编码的所有其他内容。冒号、斜杠、问号和哈希等保留字符在 URL 中具有结构意义。非保留字符包括字母、数字、连字符、下划线、句点和波形符;这些总是安全的。其他所有内容都以百分比编码为字节。该标准提供了一条明确的规则:知道你的角色属于哪一类。

WHATWG URL 标准采用基于组件的方法。它为scheme、authority、path、query和fragment分别指定不同的编码规则,而不是使用全局类别。 “&”符号可能会编码在路径中,但会单独保留在查询字符串中。空间总是被编码的,但确切的表示因上下文而异。这种按组件设计可以更好地匹配浏览器行为,但需要知道您正在编码 URL 的哪一部分。

容错:空格、反斜杠和制表符 — 输入一个标准拒绝而另一个修复

空格必须变成 %20,但浏览器会默默地转换文字空格。这两个标准都禁止使用反斜杠,但某些浏览器将它们视为路径分隔符。禁止使用制表符、换行符和控制字符。 WHATWG 指定宽松的解析器行为:转换或忽略它们。

非 ASCII 字符(例如 é 或 中)必须使用 UTF-8 编码进行百分比编码。 RFC 3986 实际上并未指定字符编码步骤本身;它假设字节存在,但没有说明如何从文本中获取它们。 WHATWG 标准明确要求 UTF-8:首先将字符串转换为 UTF-8 字节,然后对它们进行百分比编码。两种标准都达到相同的编码结果,但它们从不同的基本假设出发,并且对于相同的事情并不明确。

工作示例:在两个模型中解析带有反斜杠和空格的 URL — 输出比较

以字符串“https://example.com/café\ search”为例。浏览器遇到反斜杠并将其视为路径字符;它看到空格并将其编码为 %20,生成类似 https://example.com/café%5C%20search. 的内容 RFC 3986 解析器立即拒绝整个 URL,因为禁止反斜杠和空格。浏览器继续解析;严格解析器完全停止。试试另一个例子:“https://user@example.com:80/path?q=a&b=c". 两个标准都清楚地标识了用户信息、主机、端口、路径和查询。它们完全同意这个结构化 URL。只有在异常或格式错误的输入上才会出现分歧。

打开 URL 编码器和解码器,并将 RFC 3986 模式与浏览器行为进行比较。粘贴带有空格、反斜杠或其他边缘情况的字符串。该工具准确地向您展示了每个标准如何以不同的方式转换相同的输入。您可以立即看到哪一个更严格以及每个都做什么。

您的环境使用哪一种 - 浏览器和节点遵循 URL 标准;许多服务器库都遵循 RFC,一般描述

在浏览器中,JavaScript 默认使用 WHATWG URL 标准。 URL API 准确地实现了它。 Node.js 也使用 WHATWG。 Python 库倾向于实现 RFC 3986; urllib 紧随其后。 Java 库各不相同; java.net.URL 倾向于 RFC 3986。 Rust 的 url 箱遵循 WHATWG。 Go 的 net/url 受到 WHATWG 的影响。这是一般模式,而不是绝对规则。

当您以编程方式构建 URL 并在浏览器和后端之间移动时,请选择一个标准并坚持使用。使用浏览器的 URL API 获取 WHATWG。如果你的后端库更严格,这不是矛盾,而是设计选择。

这不包括什么 - 主机名解析、IPv6 文字和 IDNA 处理

主机名解析涉及 IDNA、punycode 和注册商规则,完全超出了 URL 解析本身。 IPv6 地址、特殊方案(例如 mailto: 或 data:)以及空组件是与百分比编码完全不同的单独主题。域名长度限制和主机名有效性因注册商而异,与本讨论无关。还排除:相对引用和特定于方案的解析规则。这篇文章仅关注编码和解析差异。

此讨论重点关注区分这些标准的编码和解析差异。排除主机名规则、DNS 规则和特定于方案的行为可防止对百分比编码规则产生混淆。

要点:相同的 URL 在一个世界中有效,而在另一个世界中则错误 — URL 编码器和解码器如何为您提供纯 RFC 3986 编码,以便您可以看到浏览器规范化的内容

相同的 URL 字符串可以在一种标准下有效,而在另一种标准下无效。两者在各自的设计目标内都是正确的。以编程方式编码 URL 组件时,请使用适合您环境的工具。 WHATWG 描述了浏览器实际执行的操作; RFC 3986 定义形式语法。 URL 编码器和解码器显示 RFC 3986 规则以及浏览器行为,以便您可以看到确切的差异并选择适合您情况的选项。

当 URL 跨越浏览器到后端边界时,最常出现问题。理解这种差异意味着有意识地处理这种交叉,而不是意外或错误。