正文:首先这篇文章是在博客园看到的,抄了她的一些东西,也删了它的一点东西,然后再补充一些自己的,文章下面的例子有一个你会发现在她文章下面有个留言,例子和我这里的一样,那个是我发的,因为我博客园也有一个账号。
原文地址:作者:降瑞雪
一、为什么要编码解码
我们都知道Http协议中参数的传输是"key=value"这种简直对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割。
如"?name1=value1&name2=value2",这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出参数值。
针对“name1=value1&name2=value2”我们来说一下客户端到服务端的概念上解析过程:
上述字符串在计算机中用ASCII吗表示为:
text
6E616D6531 3D 76616C756531 26 6E616D6532 3D 76616C756532。
6E616D6531:name1
3D:=
76616C756531:value1
26:&
6E616D6532:name2
3D:=
76616C756532:value2
服务端在接收到该数据后就可以遍历该字节流,首先一个字节一个字节的吃,当吃到3D这字节后,服务端就知道前面吃得字节表示一个key,再想后吃,如果遇到26,说明从刚才吃的3D到26子节之间的是上一个key的value,以此类推就可以解析出客户端传过来的参数。
现在有这样一个问题,如果我的参数值中就包含=或&这种特殊字符的时候该怎么办?
比如说“name1=value1”,其中value1的值是“va&lu=e1”字符串,那么实际在传输过程中就会变成这样“name1=va&lu=e1”。我们的本意是就只有一个键值对,但是服务端会解析成两个键值对,这样就产生了奇异。
如何解决上述问题带来的歧义呢?解决的办法就是对参数进行URL编码
URL编码只是简单的在特殊字符的各个字节前加上%,例如,我们对上述会产生奇异的字符进行URL编码后结果:“name1=va%26lu%3D”,这样服务端会把紧跟在“%”后的字节当成普通的字节,就是不会把它当成各个参数或键值对的分隔符。
另外一个问题,就是为什么我们要用ASCII传输,可不可以用别的编码?
当然可以用别的编码,你自己可以开发一套编码,然后自己解析。就像大部分国家都有自己的语言一样。那国家之间要交流,怎么办? 用英语把,英语的使用范围最广。
通常如果一样东西需要编码,说明这样东西并不适合传输。原因多种多样,如Size过大,包含隐私数据,对于Url来说,之所以要进行编码,是因为Url中有些字符会引起歧义。
例如,Url参数字符串中使用key=value键值对这样的形式来传参,键值对之间以&符号分隔,如/s?q=abc&ie=utf-8。如果你的value字符串中包含了=或者&,那么势必会造成接收Url的服务器解析错误,因此必须将引起歧义的&和=符号进行转义,也就是对其进行编码。
又如,Url的编码格式采用的是ASCII码,而不是Unicode,这也就是说你不能在Url中包含任何非ASCII字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。
Url编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
预备知识:URI是统一资源标识的意思,通常我们所说的URL只是URI的一种。典型URL的格式如下所示。下面提到的URL编码,实际上应该指的是URI编码。
> `foo:``//example.com:8042/over/there?name=ferret#nose`
>
> `_/ ______________/ ________/_________/ __/`
>
> `| | | | |`
>
> scheme authority path query fragment
二、哪些字符需要编码
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-ACII定义的字节范围,因此也不可以放在Url中。
保留字符:Url可以划分成若干个组件,协议、主机、路径等。有一些字符(:/?#[]@)是用作分隔不同组件的。例如:冒号用于分隔协议和主机,/用于分隔主机和路径,?用于分隔路径和查询参数,等等。
还有一些字符(!$&'()*+,;=)用于在每个组件中起到分隔作用的,如=用于表示查询参数中的键值对,&符号用于分隔查询多个键值对。当组件中的普通数据包含这些特殊字符时,需要对其进行编码。
RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]
不安全字符:还有一些字符,当他们直接放在Url中的时候,可能会引起解析程序的歧义。这些字符被视为不安全字符,原因有很多。
- 空格:Url在传输的过程,或者用户在排版的过程,或者文本处理程序在处理Url的过程,都有可能引入无关紧要的空格,或者将那些有意义的空格给去掉。
- 引号以及<>:引号和尖括号通常用于在普通文本中起到分隔Url的作用
- [#]:通常用于表示书签或者锚点
- %:百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码
- {}|^[]`~:某一些网关或者传输代理会篡改这些字符
需要注意的是:对于Url中的合法字符,编码和不编码是等价的,但是对于上面提到的这些字符,如果不经过编码,那么它们有可能会造成Url语义的不同。因此对于Url而言,只有普通英文字符和数字,特殊字符%$-_.+!*'()还有保留字符,才能出现在未经编码的Url之中。其他字符均需要经过编码之后才能出现在Url中。
这里打个比方:
http://baidu.com/ques='JS的编码解码'
上面除了中文,都是url认可出现在里面的,所以我们需要对中文进行翻译变为url认可的方式:
let a = "http://baidu.com/ques='JS的编码解码'"
console.log(encodeURI(a))
打印结果:
http://baidu.com/ques='JS%E7%9A%84%E7%BC%96%E7%A0%81%E8%A7%A3%E7%A0%81'
此时地址符没有中文,有的只是数字英文,这些是合法字符,''单引号,.小点,百分号%属于特殊字符, : / 属于保留字符都是url认可的。
其实不止中文需要编码,因为最初搞这些技术的,或者说技术最初技术牛逼的大多都是那些说英文的,所以除了英文和那些与英文一起常常出现的特殊符号被url认可,其他的什么韩语、日语还有一些其他国家与英文不同根源的字符都需要编码成url认可的,也可以说编码成老外(讲英文的老外)看的懂的东西才是url认可的,但是实际应用中一些老外看的懂的也是需要编码的,一些url认可的字符仍然需要编码,防止服务器解析的时候出现错误
例子还是上面那个:
name1=va&lu=e1
其中key:为name1; value值为:va&lu=e1
如果我们不进行编码的话,那么服务器解析的时候就变成了:
key:name1 --> value:va
key:lu --> value:e1
所以具体例子:
应该是这样:name1=va&lu=e1,此时我们只对后面的编码,就是:name1= encodeURIComponent(va&lu=e1);
然后把 name1= encodeURIComponent(va&lu=e1)发给后台,后台使用 let name1 = req.query.name1查询找到接收的值,然后再解码:
name1=decodeURIComponent(va&lu=e1);
整个流程:
// 传输地址 ===> 'http://baidu.com/?name1=va&lu=e1' 键为:name1,值为:va&lu=e1
// 1.定义value字段变量,并赋值
let Value1 = 'va&lu=e1'
//2.前台编码后传输数据
a = 'http://baidu.com/?name1='+encodeURIComponent(Value1)
console.log("前台编码数据为:"+a);
//3.后台拿到数据
let b = decodeURIComponent(a);
console.log("后台解码数据为:"+b);
打印结果:
前台编码数据为:http://baidu.com/?name1=va%26lu%3De1
后台解码数据为:http://baidu.com/?name1=va&lu=e1
所以我们需要把所有老外看不懂的视为非法字符,再加上可能存在一些产生歧义的字符视为非法字符,这些非法字符都需要编码。
三、如何对Url中的非法字符进行编码
Url编码通常也被称为百分号编码(Url Encoding,also known as percent-encoding),是因为它的编码方式非常简单,使用%百分号加上两位的字符——0123456789ABCDEF——代表一个字节的十六进制形式。
Url编码默认使用的字符集是US-ASCII。例如a在US-ASCII码中对应的字节是0x61,那么Url编码之后得到的就是%61,我们在地址栏上输入http://g.cn/search?q=%61%62%63,实际上就等同于在google上搜索abc了。又如@符号在ASCII字符集中对应的字节为0x40,经过Url编码之后得到的是%40。
对于非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编码实现并不完全遵循这样的原则,下面会提到。
1.Javascript中的escape, encodeURI和encodeURIComponent的区别
JavaScript中提供了3对函数用来对Url编码以得到合法的Url,它们分别是escape / unescape, encodeURI / decodeURI和encodeURIComponent / decodeURIComponent。由于解码和编码的过程是可逆的,因此这里只解释编码的过程。
这三个编码的函数——escape,encodeURI,encodeURIComponent——都是用于将不安全不合法的Url字符转换为合法的Url字符表示,它们有以下几个不同点。
安全字符不同:
下面列出了这三个函数的安全字符(即函数不会对这些字符进行编码)
- escape(69个):*/@+-._0-9a-zA-Z
- encodeURI(82个):!#$&'()*+,/:;=?@-._~0-9a-zA-Z
- encodeURIComponent(71个):!'()*-._~0-9a-zA-Z
兼容性不同:
escape函数是从Javascript 1.0的时候就存在了,其他两个函数是在Javascript 1.5才引入的。但是由于Javascript 1.5已经非常普及了,所以实际上使用encodeURI和encodeURIComponent并不会有什么兼容性问题。
对Unicode字符的编码方式不同:
这三个函数对于ASCII字符的编码方式相同,均是使用百分号+两位十六进制字符来表示。但是对于Unicode字符,escape的编码方式是%uxxxx,其中的xxxx是用来表示unicode字符的4位十六进制字符。这种方式已经被ECMAScript v3 已从标准中删除了 unescape() 函数,并反对使用它,因此应该用 decodeURI() 和 decodeURIComponent() 取而代之。encodeURI和encodeURIComponent则使用UTF-8对非ASCII字符进行编码,然后再进行百分号编码。这是RFC推荐的。因此建议尽可能的使用这两个函数替代escape进行编码。
但是这并不代表escape()函数失去作用:
举个例子,他可以解码编码,字符:
//b为cookie里面的字符数据,cookie存储的是用了utf-8进行编码,其实不止cookie存储,无论网页的原始编码是什么,一旦被Javascript编码,就都变为unicode字符。也就是说,Javascipt函数的输入和输出,默认都是Unicode字符。这个可以看看下面我推荐的阮一峰的笔记。在第三节最后我会附上。
let b = '%u664F';
console.log("后台解码数据为:"+unescape(b));
适用场合不同:
1. encodeURI被用作对一个完整的URI进行编码。
2.encodeURIComponent被用作对URI的一个组件进行编码。从上面提到的安全字符范围表格来看,我们会发现,encodeURIComponent编码的字符范围要比encodeURI的大。
3.我们上面提到过,保留字符一般是用来分隔URI组件(一个URI可以被切割成多个组件,参考预备知识一节)或者子组件(如URI中查询参数的分隔符),如:号用于分隔scheme和主机,?号用于分隔主机和路径。
3.1由于encodeURI操纵的对象是一个完整的的URI,这些字符在URI中本来就有特殊用途,因此这些保留字符不会被encodeURI编码,否则意义就变了。
3.2组件内部有自己的数据表示格式,但是这些数据内部不能包含有分隔组件的保留字符,否则就会导致整个URI中组件的分隔混乱。因此对于单个组件使用encodeURIComponent。
关于url编码的,什么时候网页浏览器会采用什么编码,可以看看这一篇:
关于URL编码【作者: 阮一峰】
四、何时编码
对,这个取决于你的url地址规不规范,再考虑是否分别对值编码。
文中内容也有说到了,如果你的url地址符存在非法字符,那么就需要编码,如何辨别是否存在非法字符呢?
我的理解是:
(1)所有非英文的字符语言,比如汉语,韩语,日语,及所有其他一些国家的语言,只要和英语不同根源的都属于非法字符,都需要编码,因为url地址符本身就是一套由编码识别的文字,你输入了非英文字符,url地址符不认识这些,所以需要把它转成它认识的文字,这个时候都过编码,就转化成了数字,比如16进制,8进制这些,文章谈到的%ld,这些都是url可以认识的。
(2)你的数据存在一些会让后台解析出现谬误的字符,就是我刚刚说到的那个例子:http://baidu.com/?name1=va&lu=e1' 键为:name1,值为:va&lu=e1。这种情况为了服务器可以正常解析,那么需要分别对值编码。
最后:如果是普通的:http://sengmentfault.com/?query='sengmentfault is something'
都是普通字符,url合法字符,且没有存在会让服务器产生谬误的字符,那么就无需编码。