继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Javascript中的url编码与解码(详解)

Angel_Kitty
关注TA
已关注
手记 26
粉丝 9696
获赞 1658

Javascript中的url编码与解码(详解)

摘要

本文主要针对 URI 编解码的相关问题做了介绍,对 url 编码中哪些字符需要编码、为什么需要编码做了详细的说明,并对比分析了 Javascript 中和编解码相关的几对函数 escape/unescape,encodeURI/decodeURIencodeURIComponent/decodeURIComponent

预备知识

foo://example.com:8042/over/there?name=ferret#nose
_/ _____________/ // __/
​ | | | | |
scheme authority path query fragment

URI 是统一资源标识的意思,通常我们所说的 url 只是 URI 的一种。典型 url 的格式如上面所示。下面提到的 url 编码,实际上应该指的是 URI 编码。

为什么需要URL编码

通常如果一样东西需要编码,说明这样东西并不适合传输。原因多种多样,如 Size过大,包含隐私数据,对于 url来说,之所以要进行编码,是因为 url 中有些字符会引起歧义

例如 url 参数字符串中使用 key=value 键值对这样的形式来传参,键值对之间以 & 符号分隔,如 /s?q=abc&ie=utf-8 。如果你的 value 字符串中包含了 = 或者 & ,那么势必会造成接收 url 的服务器解析错误,因此必须将引起歧义的 &= 符号进行转义,也就是对其进行编码。

又如, url 的编码格式采用的是 ASCII 码,而不是 Unicode ,这也就是说你不能在 url 中包含任何非 ASCII 字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。

url 编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。

哪些字符需要编码

RFC3986 文档规定, url 中只允许包含英文字母(a-zA-Z)、数字(0-9)-_.~ 4个特殊字符以及所有保留字符。

RFC3986 文档对 url 的编解码问题做出了详细的建议,指出了哪些字符需要被编码才不会引起 url 语义的转变,以及对为什么这些字符需要编码做出了相应的解释。

US-ASCII字符集中没有对应的可打印字符

url 中只允许使用可打印字符。 US-ASCII 码中的 10-7F 字节全都表示控制字符,这些字符都不能直接出现在 url 中。同时,对于 80-FF 字节(ISO-8859-1),由于已经超出了 US-ASCII 定义的字节范围,因此也不可以放在 url 中。

保留字符

url 可以划分成若干个组件,协议、主机、路径等。有一些字符(:/?#[]@)是用作分隔不同组件的。例如 : 冒号用于分隔协议和主机, / 用于分隔主机和路径, ? 用于分隔路径和查询参数等等。还有一些字符(!$&'()*+,;=)用于在每个组件中起到分隔作用的,如 = 用于表示查询参数中的键值对, & 符号用于分隔查询多个键值对。当组件中的普通数据包含这些特殊字符时,需要对其进行编码。

RFC3986 中指定了以下字符为保留字符:

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

不安全字符

还有一些字符,当他们直接放在 url 中的时候,可能会引起解析程序的歧义。这些字符被视为不安全字符,原因有很多。

空格 Url在传输的过程,或者用户在排版的过程,或者文本处理程序在处理Url的过程,都有可能引入无关紧要的空格,或者将那些有意义的空格给去掉
引号以及<> 引号和尖括号通常用于在普通文本中起到分隔Url的作用
# 通常用于表示书签或者锚点
% 百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码
{} 1`~

需要注意的是,对于 url 中的合法字符,编码和不编码是等价的,但是对于上面提到的这些字符,如果不经过编码,那么它们有可能会造成Url语义的不同。因此对于 url 而言,只有普通英文字符和数字,特殊字符 $-_.+!*'() 还有保留字符,才能出现在未经编码的 url 之中。其他字符均需要经过编码之后才能出现在Url中。

但是由于历史原因,目前尚存在一些不标准的编码实现。例如对于~符号,虽然 RFC3986 文档规定,对于波浪符号 ~ ,不需要进行 url 编码,但是还是有很多老的网关或者传输代理会。

如何对url中的非法字符进行编码

url 编码通常也被称为百分号编码(Url Encoding,also known as percent-encoding),是因为它的编码方式非常简单,使用 % 百分号加上两位的字符 ——0123456789ABCDEF—— 代表一个字节的十六进制形式。Url编码默认使用的字符集是 US-ASCII 。例如 aUS-ASCII 码中对应的字节是 0x61 ,那么 url 编码之后得到的就是 %61 ,我们在地址栏上输入 http://g.cn/search?q=%61%62%63 ,实际上就等同于在 google 上搜索 abc 了。又如 @ 符号在 ASCII 字符集中对应的字节为 0x40 ,经过 url 编码之后得到的是 %40

常见字符的 url 编码列表:

! * " ( ) ; : @ &
%21 %2A %22 %27 %28 %29 %3B %3A %40 %26
= + $ , / ? % # [ ]
%3D %2B %24 %2C %2F %3F %25 %23 %5B %5D

对于非 ASCII 字符,需要使用 ASCII 字符集的超集进行编码得到相应的字节,然后对每个字节执行百分号编码。对于 Unicode 字符, RFC 文档建议使用 utf-8 对其进行编码得到相应的字节,然后对每个字节执行百分号编码。如“中文”使用 UTF-8 字符集得到的字节为 0xE4 0xB8 0xAD 0xE6 0x96 0x87 ,经过 url 编码之后得到 “%E4%B8%AD%E6%96%87”

如果某个字节对应着 ASCII 字符集中的某个非保留字符,则此字节无需使用百分号表示。例如“ url 编码”,使用 UTF-8 编码得到的字节是 0x55 0x72 0x6C 0xE7 0xBC 0x96 0xE7 0xA0 0x81 ,由于前三个字节对应着ASCII中的非保留字符 url ,因此这三个字节可以用非保留字符 url 表示。最终的 url 编码可以简化成 “Url%E7%BC%96%E7%A0%81” ,当然,如果你用 "%55%72%6C%E7%BC%96%E7%A0%81” 也是可以的。

由于历史的原因,有一些 url 编码实现并不完全遵循这样的原则,下面会提到。

Javascript中的escape,encodeURI和encodeURIComponent的区别

Javascript 中提供了 3 对函数用来对Url编码以得到合法的 url ,它们分别是 escape/ unescape,encodeURI/decodeURIencodeURIComponent/decodeURIComponent 。由于解码和编码的过程是可逆的,因此这里只解释编码的过程。

这三个编码的函数—— escapeencodeURIencodeURIComponent ——都是用于将不安全不合法的 url 字符转换为合法的Url字符表示,它们有以下几个不同点。

安全字符不同

下面的表格列出了这三个函数的安全字符(即函数不会对这些字符进行编码)

安全字符
escape(69个) */@±._0-9a-zA-Z
encodeURI(82个) !#$&’()*+,/:;=?@-._~0-9a-zA-Z
encodeURIComponent(71个) !’()*-._~0-9a-zA-Z

兼容性不同

escape 函数是从 Javascript1.0 的时候就存在了,其他两个函数是在 Javascript1.5 才引入的。但是由于Javascript1.5 已经非常普及了,所以实际上使用 encodeURIencodeURIComponent 并不会有什么兼容性问题。

对Unicode字符的编码方式不同

这三个函数对于 ASCII 字符的编码方式相同,均是使用百分号+两位十六进制字符来表示。但是对于 Unicode 字符, escape 的编码方式是 %uxxxx ,其中的 xxxx 是用来表示 unicode 字符的 4 位十六进制字符。这种方式已经被W3C废弃了。但是在ECMA-262标准中仍然保留着escape的这种编码语法。encodeURIencodeURIComponent 则使用 UTF-8 对非 ASCII 字符进行编码,然后再进行百分号编码。这是 RFC 推荐的。因此建议尽可能的使用这两个函数替代 escape 进行编码。

适用场合不同

encodeURI 被用作对一个完整的 URI 进行编码,而 encodeURIComponent 被用作对 URI 的一个组件进行编码。

从上面提到的安全字符范围表格来看,我们会发现, encodeURIComponent 编码的字符范围要比 encodeURI 的大。我们上面提到过,保留字符一般是用来分隔 URI 组件(一个 URI 可以被切割成多个组件,参考预备知识一节)或者子组件(如 URI 中查询参数的分隔符),如:号用于分隔 scheme 和主机, ? 号用于分隔主机和路径。由于 encodeURI 操纵的对象是一个完整的的 URI ,这些字符在 URI 中本来就有特殊用途,因此这些保留字符不会被 encodeURI 编码,否则意义就变了。

组件内部有自己的数据表示格式,但是这些数据内部不能包含有分隔组件的保留字符,否则就会导致整个 URI 中组件的分隔混乱。因此对于单个组件使用 encodeURIComponent ,需要编码的字符就更多了。

表单提交

Html 的表单被提交时,每个表单域都会被 url 编码之后才在被发送。由于历史的原因,表单使用的Url编码实现并不符合最新的标准。例如对于空格使用的编码并不是 %20 ,而是 + 号,如果表单使用的是 Post 方法提交的,我们可以在 HTTP 头中看到有一个 Content-Typeheader ,值为 application/x-www-form-urlencoded 。大部分应用程序均能处理这种非标准实现的 url 编码,但是在客户端 Javascript 中,并没有一个函数能够将 + 号解码成空格,只能自己写转换函数。还有,对于非 ASCII 字符,使用的编码字符集取决于当前文档使用的字符集。例如我们在 Html 头部加上

<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />

这样浏览器就会使用 gb2312 去渲染此文档(注意,当 HTML 文档中没有设置此 meta 标签,则浏览器会根据当前用户喜好去自动选择字符集,用户也可以强制当前网站使用某个指定的字符集)。当提交表单时, url 编码使用的字符集就是 gb2312

文档字符集会影响encodeURI吗?

之前在使用 Aptana(为什么专指 aptana 下面会提到)遇到一个很迷惑的问题,就是在使用 encodeURI 的时候,发现它编码得到的结果和我想的很不一样。下面是我的示例代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
    </head>
    <body>
        <script type="text/javascript">
            document.write(encodeURI("中文"));
        </script>
    </body>
</html> 

运行结果输出 %E6%B6%93%EE%85%9F%E6%9E%83 。显然这并不是使用 UTF-8 字符集进行 url 编码得到的结果(在 Google 上搜索“中文”, url 中显示的是 %E4%B8%AD%E6%96%87 )。

所以我当时就很质疑,难道 encodeURI 还跟页面编码有关,但是我发现,正常情况下,如果你使用 gb2312 进行 url 编码也不会得到这个结果的才是。后来终于被我发现,原来是页面文件存储使用的字符集和Meta标签中指定的字符集不一致导致的问题Aptana 的编辑器默认情况下使用 UTF-8 字符集。也就是说这个文件实际存储的时候使用的是 UTF-8 字符集。但是由于 Meta 标签中指定了 gb2312 ,这个时候,浏览器就会按照 gb2312 去解析这个文档,那么自然在“中文”这个字符串这里就会出错,因为“中文”字符串用 UTF-8 编码过后得到的字节是 0xE4 0xB8 0xAD 0xE6 0x96 0x87 ,这6个字节又被浏览器拿 gb2312 去解码,那么就会得到另外三个汉字“涓枃”( GBK 中一个汉字占两个字节),这三个汉字在传入 encodeURI 函数之后得到的结果就是 %E6%B6%93%EE%85%9F%E6%9E%83 。因此, encodeURI 使用的还是 UTF-8 ,并不会受到页面字符集的影响。

其他和url编码相关的问题

对于包含中文的 url 的处理问题,不同浏览器有不同的表现。例如对于 IE ,如果你勾选了高级设置“总是以 UTF-8 发送 url ”,那么 url 中的路径部分的中文会使用 UTF-8 进行 url 编码之后发送给服务端,而查询参数中的中文部分使用系统默认字符集进行 url 编码。为了保证最大互操作性,建议所有放到 url 中的组件全部显式指定某个字符集进行 url 编码,而不依赖于浏览器的默认实现。

另外,很多 HTTP 监视工具或者浏览器地址栏等在显示 url 的时候会自动将 url 进行一次解码(使用 UTF-8 字符集),这就是为什么当你在 Firefox 中访问 Google 搜索中文的时候,地址栏显示的 url 包含中文的缘故。但实际上发送给服务端的原始 url 还是经过编码的。你可以在地址栏上使用 Javascript 访问 location.href 就可以看出来了。在研究 url 编解码的时候千万别被这些假象给迷惑了。

参考文献

打开App,阅读手记
11人推荐
发表评论
随时随地看视频慕课网APP