如何编码和解码 URL
如果您曾经在URL中看到%20而应该是空格,或者%C3%A9而应该是带重音的字符,那么您遇到了URL编码。这是网络工作方式的基本部分,理解它有助于您调试损坏的链接、API问题和表单提交。基于浏览器的编码器在本地处理整个工作,而无需将您的数据上传到服务器。
URL编码的作用
URL只能安全地包含一组有限的字符:字母(A-Z, a-z)、数字(0-9)和一些特殊字符(-, _, ., ~)。其他所有内容(空格、带重音的字符、表情符号和如&、=、#、?等符号)都必须转换为安全格式。
URL编码(也称为百分号编码)将不安全的字符替换为%后跟其十六进制字节值:
| 字符 | 编码 |
|---|---|
| 空格 | %20 |
| & | %26 |
| = | %3D |
| # | %23 |
| ? | %3F |
| / | %2F |
| @ | %40 |
| : | %3A |
| + | %2B |
| , | %2C |
| ; | %3B |
| (换行) | %0A |
| (制表符) | %09 |
何时需要URL编码
- 带特殊字符的查询参数:像
价格 > 100 & 类别 = 鞋子这样的搜索查询需要编码才能在URL中工作 - URL中的非英文字符:其他语言中的名称、城市或内容必须进行编码
- API请求:手动构建API调用时,参数值通常需要编码
- 调试:当URL无法正常工作时,解码它会显示实际值
- 电子邮件链接(mailto:):mailto链接中的主题行和正文需要编码
- OAuth重定向URI:传递给OAuth提供商的redirect_uri参数必须完全编码
- Webhook负载:Stripe或Slack等服务交付的webhook URL中的查询字符串
- 深度链接到移动应用程序:iOS/Android应用的自定义URL方案需要编码以安全处理
- GraphQL持久化查询:作为URL参数附加的哈希查询需要编码
- PostgreSQL连接字符串:DATABASE_URL值中的密码和其他特殊字符
如何编码和解码
- 选择编码或解码:选择方向。对查询参数选择encodeURIComponent,对完整URL选择encodeURI。
- 粘贴您的输入:输入文本或URL。结果会立即更新。
- 复制输出:在您的代码、API请求或浏览器中使用结果。
URL编码的简史
URL编码由1994年12月的RFC 1738与原始URL规范一起定义。该RFC由蒂姆·伯纳斯-李(网络发明者)在IETF URI工作组的输入下编写。原始编码方案使用ASCII字节值:每个保留或不安全的字符被编码为%后跟两个十六进制数字。
编码经过多次更新:
- RFC 1738(1994):原始URL规范,仅ASCII
- RFC 2396(1998):更严格的语法,将「保留」字符与「未保留」字符分开
- RFC 3986(2005):当前URI规范,定义两种编码模式(路径与查询),非ASCII的UTF-8字节序列
- WHATWG URL标准(持续中):浏览器标准的活规范,所有现代浏览器使用,为了向后兼容,规则与RFC 3986略有不同
最大的变化是RFC 3986中转向UTF-8。在那之前,编码的URL仅为ASCII,非拉丁字符需要变通方法(域名的Punycode、国际地址的IDN)。今天,URL中带重音的「é」编码为%C3%A9(其两个UTF-8字节),而不是旧系统会生成的Latin-1字节%E9。
encodeURI vs encodeURIComponent vs encodeURIFull
JavaScript有三种编码函数,行为略有不同:
| 函数 | 编码内容 | 保留内容 | 用于 |
|---|---|---|---|
| encodeURI() | 所有不安全字符 | URL语法::/ ? & = # | 编码整个URL |
| encodeURIComponent() | 所有不安全字符,包括URL语法 | 仅A-Z a-z 0-9 - _ . ~ ! * ' ( ) | 查询参数值 |
| escape()(已弃用) | 大多数不安全字符 | 仅Latin-1 | 请勿使用 |
在Python中:
urllib.parse.quote()类似encodeURI(保留/,但不保留:)urllib.parse.quote_plus()类似encodeURIComponent但对空格使用+urllib.parse.urlencode(dict)编码整个查询字符串
在其他语言中:
| 语言 | 组件编码 | 完整URI编码 |
|---|---|---|
| Java | URLEncoder.encode()(对+有注意事项) | URI.toASCIIString() |
| C# | Uri.EscapeDataString | Uri.EscapeUriString |
| Ruby | CGI.escape() | URI.encode_www_form_component |
| PHP | rawurlencode() | urlencode()(注意:%2B vs +) |
| Go | url.QueryEscape() | url.PathEscape() |
| Rust | percent_encoding crate | percent_encoding crate |
常见陷阱
- 编码整个URL:如果您编码
https://example.com/search?q=hello,您会得到https%3A%2F%2Fexample.com%2Fsearch%3Fq%3Dhello,它不再是工作的URL。仅编码值,不要编码结构字符。 - 双重编码:编码已编码的字符串会产生像
%2520这样的内容(%被编码为%25)。如果您的URL看起来不对,请检查是否有内容被编码两次。 - 空格作为+还是%20:在
application/x-www-form-urlencoded(表单POST正文)中,空格变成+。在URL中,空格变成%20。大多数服务器都接受两者,但某些严格的解析器不接受。 - 错误编码保留字符:
?、#、&、=在URL语法中具有特殊含义。如果它们出现在值中,必须编码;如果它们作为语法出现,则不应该编码。 - 忘记在接收时解码:如果您编码一个值,发送它,然后您的服务器字面地读取
?q=hello%20world而不解码,您的应用程序看到的是hello%20world而不是hello world。大多数框架自动解码,但在自定义代码中要验证。 - 加号混淆:
+在路径段中是字面加号,在查询字符串中是空格。在查询值中将实际加号编码为%2B以避免歧义。 - UTF-8与其他编码:如果您的URL包含「résumé」而服务器期望Latin-1而不是UTF-8,您可能会得到乱码。现代Web是UTF-8;遗留系统不是。
- URL长度限制:虽然规范中没有严格限制,但浏览器和服务器通常将URL限制在2048-8192个字符。重度编码的数据可能比预期更快达到限制。
- Cookies和Referer头:URL在Referer头中传递并可能被记录。URL中的敏感数据(密码、令牌)泄漏到日志和分析中。对敏感数据使用POST正文。
- 非ASCII域名:域名使用Punycode(RFC 3492),而不是百分号编码。「münchen.de」在DNS查找中变成「xn--mnchen-3ya.de」,而不是「m%C3%BCnchen.de」。
工作示例
| 输入 | encodeURI | encodeURIComponent |
|---|---|---|
hello world | hello%20world | hello%20world |
q=test&page=1 | q=test&page=1 | q%3Dtest%26page%3D1 |
https://x.com/path | https://x.com/path | https%3A%2F%2Fx.com%2Fpath |
caf é | caf%20%C3%A9 | caf%20%C3%A9 |
中文 | %E4%B8%AD%E6%96%87 | %E4%B8%AD%E6%96%87 |
100% | 100%25 | 100%25 |
email@test.com | email@test.com | email%40test.com |
提示
- 编码值,而不是整个URL:如果您编码整个URL,构造URL的斜杠和冒号也将被编码,从而破坏它。仅编码查询参数中的值。
- 双重编码:编码已编码的字符串会产生像
%2520这样的内容(%被编码为%25)。如果您的URL看起来不对,请检查是否有内容被编码两次。 - 为调试解码:当API请求失败或URL看起来杂乱时,解码它以查看实际的参数值。这通常会立即揭示问题。
- 使用您语言的内置函数:在生产代码中,始终使用
encodeURIComponent()(JavaScript)、urllib.parse.quote()(Python)或URLEncoder.encode()(Java),而不是手动编码。 - 用边缘情况测试:尝试带有空格、重音、表情符号和特殊字符的输入。如果您的编码对所有这些都有效,它就有效。
- 在浏览器地址栏中验证:将您编码的URL粘贴到浏览器中。如果页面加载,URL格式正确。如果不加载,您的编码有错误。
- 对复杂情况使用查询字符串库:从字典或对象(
?a=1&b=2&c=3)构建查询字符串使用库函数(Python中的urlencode,JavaScript中的URLSearchParams)比手动组装更容易、更安全。 - 了解路径和查询编码之间的区别:路径段中的正斜杠
/是结构性的;在查询值中,必须进行编码。RFC 3986对每个都有不同的规则。
隐私和机密URL
URL编码器和解码器完全在您的浏览器中运行。您粘贴的URL、中间处理和编码/解码的输出都保留在您的设备上。没有任何内容上传到服务器、记录或与任何人共享。
这很重要,因为URL经常包含极其敏感的数据:查询参数中的API密钥和令牌、授予帐户访问权限的OAuth授权代码、会话ID、带嵌入凭据的私有S3桶的签名URL、魔法链接登录令牌、密码重置URL、揭示产品结构的内部管理URL、取消订阅链接中的客户电子邮件地址、表单提交中的个人数据。云URL编码器记录每次粘贴,有时为「服务改进」保留它们,并参与了真正的泄露,其中粘贴的身份验证令牌被监控日志的攻击者提取。基于浏览器的编码器没有任何暴露:URL永远不会离开您的机器。
基于浏览器的编码在页面加载后也可以离线工作,对于在飞机上、在没有互联网访问的安全环境中或在任何您不能或不应将带身份验证的URL粘贴到第三方服务的地方编码URL很有用。
常见问题
encodeURI 和 encodeURIComponent 有什么区别?
encodeURI 保留 URL 结构中的有效字符(斜杠、冒号、问号)。encodeURIComponent 对除字母、数字和少数安全字符外的所有内容进行编码。查询参数值使用 encodeURIComponent,完整 URL 使用 encodeURI。
为什么空格变成 %20 或 +?
在 URL 编码中,空格变为 %20。在表单数据(application/x-www-form-urlencoded)中,空格变为 +。两者在其上下文中都有效,但 %20 是 URL 的通用标准。
我需要手动编码 URL 吗?
大多数情况下,您的语言或框架会自动处理编码。手动编码在手工构建 URL、调试 API 请求或处理含特殊字符的查询字符串时很有用。
我的数据会发送到服务器吗?
不会。所有编码和解码都在您的浏览器中进行。