为什么网址里会有一堆%?URL 编码的工作原理与应用场景

在我们日常使用互联网的过程中,当点击链接、提交表单或调用 API 时,一个看不见的过程正在默默保障着信息的准确传递 —— 这就是 URL 编码。这个看似简单的技术细节,却是维持 HTTP 协议正常运转的核心机制之一。理解 URL 编码的原理与实践,对于开发者而言不仅能避免常见的技术陷阱,更能深刻把握互联网通信的底层逻辑。

1. URL 编码

1.1. 定义

URL 编码,又称百分号编码(Percent-Encoding),是一种将 URL 中不符合规范的字符转换为特定格式的编码方式。其诞生的直接原因是 URL 对字符集的严格限制 —— 早期的 URL 设计仅支持 ASCII 字符集中的部分字符,这就导致非 ASCII 字符(如中文、日文)和部分特殊符号无法直接在 URL 中使用。
1994 年发布的 RFC 1738 首次规范了 URL 的格式,明确规定了哪些字符可以直接使用,哪些需要编码。随着互联网的发展,2005 年发布的 RFC 3986 对 URL 编码规则进行了更新,成为目前遵循的主要标准。

1.2. 编码规范

下面是一个在参数值中存在特殊字符的报错信息:

1
Invalid character found in the request target [getInfo?geom=117.054824,36.665425|117.054824,36.66542]. The valid characters are defined in RFC 7230 and RFC 3986

报错信息中提到了当前使用的两套规范RFC 3986和RFC 7230,这是互联网中关于URL和HTTP协议的两个核心标准,直接影响URL的格式规范和HTTP请求的处理方式。

1.2.1. RFC 3986:统一资源标识符(URI)规范

发布时间:2005年1月
核心作用:定义URI的语法结构和编码规则,是所有URL必须遵循的基础标准。
URI的基本组成

1
scheme:[//[user:password@]host[:port]][/path][?query][#fragment]

例如:

1
https://user:pass@example.com:8080/api/users?name=john#section1

1.2.2. RFC 7230:HTTP/1.1消息语法和路由

发布时间:2014年6月
核心作用:定义HTTP消息的格式、URI解析规则,以及服务器对请求的处理方式。
HTTP请求行格式

1
METHOD SP request-target SP HTTP-version CRLF

例如:

1
GET /api/users?name=john HTTP/1.1

2. 字符分类

2.1. 未保留字符(Unreserved)

无需编码,包括:

1
A-Z a-z 0-9 - . _ ~

2.2. 保留字符(Reserved Characters)

这些字符在URL中有特殊用途,作为分隔符时不需要编码,但作为参数值时必须编码:

1
: / ? # [ ] @ ! $ & ' ( ) * + , ; =

字符 编码后 说明
: %3A 用于分隔方案(如https:)、用户信息(如user:pass)、端口(如:8080
/ %2F 路径分隔符(如/api/users
? %3F 查询字符串开始标记(如?param=value
# %23 片段标识符(如#section
[ %5B 方括号(用于IPv6地址等)
] %5D 方括号
@ %40 用户信息分隔符(如user@domain.com
& %26 查询参数分隔符(如param1=v1&param2=v2
= %3D 参数名与值的分隔符(如key=value
+ %2B URL中加号通常表示空格,但作为普通字符时需编码
$ %24 用于金融或特殊协议
, %2C 用于参数列表分隔

2.3. 不安全字符

这些字符可能在传输或解析时导致问题,必须编码:

字符 编码后 说明
空格 %20 空格在URL中非法,通常编码为%20+(表单编码中)
" %22 引号可能导致解析歧义
< %3C HTML标签起始符,可能导致XSS攻击
> %3E HTML标签结束符
# %23 片段标识符,若出现在参数中需编码
% %25 百分比符号本身需要编码(因用于编码其他字符)
{ %7B 花括号
} %7D 花括号
` ` %7C
\ %5C 反斜杠
^ %5E 脱字符
~ %7E 波浪号(通常安全,但某些系统可能需要编码)

2.4. 非ASCII字符

任何不在 US-ASCII字符集(即0-127范围外的字符)都必须编码:

  • 中文:如你好%E4%BD%A0%E5%A5%BD
  • 其他语言:如é%C3%A9
  • 特殊符号:如©%C2%A9

3. 编码规则与应用

URL 编码是可逆的过程:

  • 编码:字符 → 字节 → %XX 格式。
  • 解码:%XX 格式 → 字节 → 字符(根据相同字符集还原)。
    1
    2
    3
    4
    5
    6
    const encoder = new TextEncoder()
    const bytes = encoder.encode('中')
    console.log(bytes, 'bbbb')
    for (const byte of bytes) {
    console.log(byte.toString(16)) // e4 b8 ad
    }
    以上示例是在js中将汉字 “中” 字的字节转换为十六进制,每个字节编码前加%后就变为%E4%B8%AD

在不同的编程语言中,实现 URL 编码的方式略有差异,但核心逻辑一致;

3.1. JS编码

JavaScript 中则提供了encodeURIComponent()和encodeURI()两个函数,前者用于编码参数值,后者用于编码完整 URL(不会编码协议、域名等部分的特殊字符):
encodeURI编码:
仅编码 URL 中的特殊字符(如:、/、?、#等),保留 URL 结构字符。

1
2
encodeURI('https://restapi.amap.com/v3/distance?origins=116.481028,39.989643|114.481028,39.989643|115.481028,39.989643&destination=114.465302,40.004717')
// https://restapi.amap.com/v3/distance?origins=116.481028,39.989643%7C114.481028,39.989643%7C115.481028,39.989643&destination=114.465302,40.004717

encodeURIComponent编码
会编码所有非安全字符(包括 URL 结构字符),适用于编码完整组件。

1
2
encodeURIComponent('https://restapi.amap.com/v3/distance?origins=116.481028,39.989643|114.481028,39.989643|115.481028,39.989643&destination=114.465302,40.004717')
// https%3A%2F%2Frestapi.amap.com%2Fv3%2Fdistance%3Forigins%3D116.481028%2C39.989643%7C114.481028%2C39.989643%7C115.481028%2C39.989643%26destination%3D114.465302%2C40.004717

3.2. Java编码

在 Java 中进行 URL 编码主要使用java.net.URLEncoderjava.net.URLDecoder类。

1
2
3
String url = "https://restapi.amap.com/v3/distance?origins=116.481028,39.989643%7C114.481028,39.989643%7C115.481028,39.989643&destination=114.465302,40.004717";
String urlEncoded = URLEncoder.encode(url, StandardCharsets.UTF_8.name());
// urlEncoded为:https%3A%2F%2Frestapi.amap.com%2Fv3%2Fdistance%3Forigins%3D116.481028%2C39.989643%257C114.481028%2C39.989643%257C115.481028%2C39.989643%26destination%3D114.465302%2C40.004717