URL编码来源及详解

URL 编码会将字符转换为可通过因特网传输的格式

 

【URL - 统一资源定位器】

Web 浏览器通过 URL 从 web 服务器请求页面。

URL 是网页的地址,比如: http://www.runoob.com

 

【URL 编码】

在URL编码的过程中,经常看到这样的东西,如%2B,%25,这是哪种编码呢?原来就是ASCII码表( American Standard Code for Information Interchange, ASCII ) 。

URL 只能使用 ASCII 字符集来通过因特网进行发送。

由于 URL 常常会包含 ASCII 集合之外的字符,URL 必须转换为有效的 ASCII 格式。

URL 编码使用 "%" 其后跟随两位的十六进制数来替换非 ASCII 字符。

URL 不能包含空格。URL 编码通常使用 + 来替换空格。

例如:

点击"提交"按钮,浏览器会在发送输入之前对其进行 URL 编码。服务器上的页面会显示出接收到的输入(这里我输入的是“hello  world”),提交后URL后面跟着hello++world,比如http://www.runoob.com/try/html_form_submit.php?text=hello++world

 

【URL编码原因】

 一般开发中,如果一个东西需要被编码,其原因有多种:由于私密信息传输需要被编码、由于传输时内容过大需要编码压缩等等;

而URL编码则是为了解决url中可能存在的字符歧义。

 

【编码函数】

JavaScript、PHP、ASP 都提供了对字符串进行URL编码的函数。

JavaScript 中使用 encodeURI() 函数,PHP 中使用 rawurlencode() 函数,ASP 中使用 Server.URLEncode() 函数

 

【URL 编码参考手册】

举几个简单例子:

ASCII 字符 URL-编码
space %20
! %21
" %22
# %23
$ %24

 

【url编码】

        url编码是一种浏览器用来打包表单输入的格式。浏览器从表单中获取所有的name和其中的值 ,将它们以name/value参数编码(移去那些不能传送的字符,将数据排行等等)作为URL的一部分或者分离地发给服务器。

       任何特殊的字符(就是那些不是简单的七位ASCII,如汉字)将以百分符%用十六进制编码,当然也包括象 =,&;,和 % 这些特殊的字符。其实url编码就是一个字符ascii码的十六进制。不过稍微有些变动,需要在前面加上“%”(以上来自百度百科)

【举一个使用场景】

比如页面跳转时的参数,?a=中国人+ ,若直接重定向到此链接,+特殊字符会被过滤掉,此时就需要进行url编码了

js中自带了url编码方法,encodeURI()、encodeURIComponent()

       对应的url解码方法,decodeURI()、decodeURIComponent()

【1】.url路径中存在中文参数时,如下面的url路径:

www.baidu.com?id=123&name=中文
 对于Unicode字符,RFC文档建议使用utf-8对其进行编码得到相应的字节,然后对每个字节执行百分号编码。如”中文”使用UTF-8字符集得到的字节为0xE4 0xB8 0xAD 0xE6 0x96 0x87,经过Url编码之后得到”%E4%B8%AD%E6%96%87”。或是在程序中使用专门的UrlEncode编码;

正确的带中文内容路径的访问方式如下:

www.baidu.com?id=123&name=%E4%B8%AD%E6%96%87
 【2】.url路径中带有特殊符号

某些特殊符号在url中会产生歧义

序号 特殊字符 含义 十六进制值
1 + URL 中+号表示空格 %2B
2 空格 URL中的空格可以用+号或者编码 %20
3 / 分隔目录和子目录 %2F
4 ? 分隔实际的 URL 和参数 %3F
5 % 指定特殊字符 %25
6 # 表示书签 %23
7 & URL 中指定的参数间的分隔符 %26
8 = URL 中指定参数的值 %3D

如下情况:

我们都知道Http协议中参数的传输是”key=value”这种简直对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割。如”?name1=value1&name2=value2”,这样在服务端在收到这种字符串的时候,会用“&”分割出每一个参数,然后再用“=”来分割出参数值。

比如说“name1=value1”,其中value1的值是“va&lu=e1”字符串,那么实际在传输过程中就会变成这样“name1=va&lu=e1”。我们的本意是就只有一个键值对,但是服务端会解析成两个键值对,这样就产生了奇异。

【总结:】

上述的情况就是url中特殊字符锁产生的歧义;针对上述情况,我们通常在发送http请求之前都会进行url编码,将特殊字符转换成为十六进制带百分号的形式,或者对中文字符进行url编码再进行传输。

另外一个问题,就是为什么我们要用ASCII传输,可不可以用别的编码? 

    当然可以用别的编码,你自己可以开发一套编码,然后自己解析。就像大部分国家都有自己的语言一样。那国家之间要交流,怎么办?  用英语把,英语的使用范围最广。

【拓展1:浏览器URL编码】

页面中通过请求另一个页面并通过url传递了带有中文的参数,结果在接收端获取参数时乱码了

经检查乱码现象指出新在IE浏览器中,其他浏览器火狐、chrome等不会有问题

【最后的解决方式是:】

手动将此中文进行编码:encodeURI(url)即可

先来看看三个url:

url1. http://hi.baidu.com/爱宝的妍
url2. http://hi.baidu.com/%E7%88%B1%E5%AE%9D%E7%9A%84%E5%A6%8D (UTF-8 编码)
url3. http://hi.baidu.com/%B0%AE%B1%A6%B5%C4%E5%FB (GBK 编码)

 这三个url指向同一个网页,而且都能够访问。其实,如果你的浏览器是中文环境下的,IE设置“以UTF8发送URL”,那么在IE里输入url1,服务器端得到的是url2;在firefox里输入url1,服务器端得到的是url3。这是为什么呢?听下文分解(分析部分内容转自http://blog.csdn.net/yzhz/archive/2007/07/03/1676796.aspx)。

一、问题:

编码问题是学者在web开发过程中经常会遇到问题,网上也有大量相关的文章介绍,但其中很多文章并没有对URL中使用了中文等非ASCII的字符造成服务器后台程序解析出现乱码的问题作出准确的解释和说明。本文将详细介绍由于在URL中使用了中文等非ASCII的字符造成乱码的问题。

1、在URL中中文字符通常出现在以下两个地方:

(1)、Query String中的参数值,比如http://search.china.alibaba.com/search/offer_search.htm?keywords=中国

(2)、servlet path,比如:http://search.china.alibaba.com/selloffer/中国.html

2、出现乱码问题的原因主要是以下几方面:

(1)、浏览器:我们的客户端(浏览器)本身并没有遵循URI编码的规范(http://www.w3.org/International/O-URL-code.html)。

(2)、Servlet服务器:Servlet服务器的没有正确配置。

(3)、开发人员并不了解Servlet的规范和API的含义。

二、基础知识:

1、一个http请求经过的几个环节:

浏览器(ie firefox)【get/post】------------>Servlet服务器------------------------------->浏览器显示

                                 编码                   解码成unicode,然后将显示的内容编码          解码

(1) 浏览器把URL(以及post提交的内容)经过编码后发送给服务器。

(2) 这里的Servlet服务器实际上指的是由Servlet服务器提供的servlet实现ServletRequestWrapper,不同应用服务器的servlet实现不同,这些servlet的实现把这些内容解码转换为unicode,处理完毕后,然后再把结果(即网页)编码返回给浏览器。

(3) 浏览器按照指定的编码显示该网页。当对字符串进行编码和解码的时候都涉及到字符集,通常使用的字符集为ISO8859-1、GBK、UTF-8、UNICODE。

2、URL的组成:

域名:端口/contextPath/servletPath/pathInfo?queryString

3、开发人员必须清楚的servlet规范:

(1) HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没有说明这一点。

(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。

(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。

(4) POST提交的数据是作为request body的一部分。

(5) 网页的Http头中ContentType("text/html; charset=GBK")的作用:

    (a) 告诉浏览器网页中数据是什么编码;

    (b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。

这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。

三、下面我们分别从浏览器和应用服务器来举例说明:

URL:http://localhost:8080/example/中国?name=中国

汉字     编码        二进制表示

中国     UTF-8       0xe4 0xb8 0xad 0xe5 0x9b 0xbd[-28, -72, -83, -27, -101, -67]

中国     GBK         0xd6 0xd0 0xb9 0xfa[-42, -48, -71, -6]

中国     ISO8859-1 0x3f,0x3f[63, 63]信息失去

(一)、浏览器

1、GET方式提交,浏览器会对URL进行URL encode,然后发送给服务器。

(1) 对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo是URL Encode是按照UTF-8编码,QueryString是按照GBK编码。

http://localhost:8080/example/中国?name=中国

实际上提交是:GET /example/%E4%B8%AD%E5%9B%BD?name=%D6%D0%B9%FA

(2) 对于中文IE,如果在高级选项中取消总以UTF-8发送,则PathInfo和QueryString是URL encode按照GBK编码。

实际上提交是:GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

(3) 对于中文firefox,则pathInfo和queryString都是URL encode按照GBK编码。实际上提交是:

GET /example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。对于中文的IE和FIREFOX都是采用GBK编码QueryString。

【小结:解决方案】

1、URL中如果含有中文等非ASCII字符,则浏览器会对它们进行URLEncode。为了避免浏览器采用了我们不希望的编码,所以最好不要在URL中直接使用非ASCII字符,而采用URL Encode编码过的字符串%.

比如:URL:http://localhost:8080/example/中国?name=中国

建议:URL:http://localhost:8080/example/%D6%D0%B9%FA?name=%D6%D0%B9%FA

我们建议URL中PathInfo和QueryString采用相同的编码,这样对服务器端处理的时候会更加简单。

2、还有一个问题,我发现很多程序员并不明白URL Encode是需要指定字符集的。不明白的人可以看看这篇文档:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html

3、 POST提交

对于POST方式,表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType("text/html; charset=GBK")中指定的编码进行对表单中的数据进行编码,然后发给服务器。在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。

解决方案:

1、从最简单,所需代价最小来看,我们对URL以及网页中的编码使用统一的编码对我们来说是比较合适的。

如果不使用统一编码的话,我们就需要在程序中做一些编码转换的事情。这也是我们为什么看到有网络上大量的资料介绍如何对乱码进行处理,其中很多解决方案都只是一时的权宜之计,没有从根本上解决问题。

(二)、Servlet服务器

          Servlet服务器实现的Servlet遇到URL和POST提交的数据中含有%的字符串,它会按照指定的字符集解码。下面两个Servlet方法返回的结果都是经过解码的:

request.getParameter("name"); 
request.getPathInfo();

这里所说的"指定的字符集"是在应用服务器的配置文件中配置。

(三)浏览器显示

浏览器根据http头中的ContentType("text/html; charset=GBK"),指定的字符集来解码服务器发送过来的字节流。我们可以调用HttpServletResponse.setContentType()设置http头的ContentType。

【总结:】

1、URL中的PathInfo和QueryString字符串的编码和解码是由浏览器和应用服务器的配置决定的,我们的程序不能设置,不要期望用request.setCharacterEncoding()方法能设置URL中参数值解码时的字符集。所以我们建议URL中不要使用中文等非ASCII字符,如果含有非ASCII字符的话要使用URLEncode编码一下,比如:

http://localhost:8080/example1/example/中国

正确的写法:

http://localhost:8080/example1/example/%E4%B8%AD%E5%9B%BD

并且我们建议URL中不要在PathInfo和QueryString同时使用非ASCII字符,比如:http://localhost:8080/example1/example/中国?name=中国

原因很简单:不同浏览器对URL中PathInfo和QueryString编码时采用的字符集不同,但应用服务器对URL通常会采用相同的字符集来解码。

2、我们建议URL中的URL Encode编码的字符集和网页的contentType的字符集采用相同的字符集,这样程序的实现就很简单,不用做复杂的编码转换。

到这里大家该明白url编码的原理了吧。再回过头来看看本文一开始给出的三个url。http://hi.baidu.com/爱宝的妍 属于PathInfo,所以根据IE以及Firefox默认设置,ie用utf8 encode了该url,而firefox用gbk encode的,随之服务器端得到了url2和url3不同的url。

【拓展2:URL编码中加入%原因】

一、问题的由来

URL就是网址,只要上网,就一定会用到。

一般来说,URL只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。比如,世界上有英文字母的网址“http://www.abc.com”,但是没有希腊字母的网址“http://www.aβγ.com”(读作阿尔法-贝塔-伽玛.com)。这是因为网络标准RFC 1738做了硬性规定:

"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'(),"
 [not including the quotes - ed], and reserved characters used for their
 reserved purposes may be used unencoded within a URL."
“只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”
[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”

 这意味着,如果URL中有汉字,就必须编码后使用。但是麻烦的是,RFC 1738没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定。这导致“URL编码”成为了一个混乱的领域。

下面就让我们看看,“URL编码”到底有多混乱。我会依次分析四种不同的情况,在每一种情况中,浏览器的URL编码方法都不一样。把它们的差异解释清楚之后,我再说如何用Javascript找到一个统一的编码方法。

二、情况1:网址路径中包含汉字

打开IE(我用的是8.0版),输入网址“http://zh.wikipedia.org/wiki/春节”。注意,“春节”这两个字此时是网址路径的一部分。

bg2010021102.jpg

查看HTTP请求的头信息,会发现IE实际查询的网址是“http://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82”。也就是说,IE自动将“春节”编码成了“%E6%98%A5%E8%8A%82”。

bg2010021103.png

我们知道,“春”和“节”的utf-8编码分别是“E6 98 A5”和“E8 8A 82”,因此,“%E6%98%A5%E8%8A%82”就是按照顺序,在每个字节前加上%而得到的。(具体的转码方法,请参考我写的《字符编码笔记》。)

在Firefox中测试,也得到了同样的结果。所以,结论1就是,网址路径的编码,用的是utf-8编码。

三、情况2:查询字符串包含汉字

在IE中输入网址“http://www.baidu.com/s?wd=春节”。注意,“春节”这两个字此时属于查询字符串,不属于网址路径,不要与情况1混淆。

bg2010021104.jpg

查看HTTP请求的头信息,会发现IE将“春节”转化成了一个乱码。

bg2010021105.png

切换到十六进制方式,才能清楚地看到,“春节”被转成了“B4 BA BD DA”。

bg2010021106.png

我们知道,“春”和“节”的GB2312编码(我的操作系统“Windows XP”中文版的默认编码)分别是“B4 BA”和“BD DA”。因此,IE实际上就是将查询字符串,以GB2312编码的格式发送出去。

Firefox的处理方法,略有不同。它发送的HTTP Head是“wd=%B4%BA%BD%DA”。也就是说,同样采用GB2312编码,但是在每个字节前加上了%。

bg2010021107.png

所以,结论2就是,查询字符串的编码,用的是操作系统的默认编码。

四、情况3:Get方法生成的URL包含汉字

前面说的是直接输入网址的情况,但是更常见的情况是,在已打开的网页上,直接用Get或Post方法发出HTTP请求。

根据台湾中兴大学吕瑞麟老师的试验,这时的编码方法由网页的编码决定,也就是由HTML源码中字符集的设定决定。

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

 如果上面这一行最后的charset是UTF-8,则URL就以UTF-8编码;如果是GB2312,URL就以GB2312编码。

举例来说,百度是GB2312编码,Google是UTF-8编码。因此,从它们的搜索框中搜索同一个词“春节”,生成的查询字符串是不一样的。

百度生成的是%B4%BA%BD%DA,这是GB2312编码。

bg2010021109.jpg

Google生成的是%E6%98%A5%E8%8A%82,这是UTF-8编码。

bg2010021108.jpg

所以,结论3就是,GET和POST方法的编码,用的是网页的编码。

五、情况4:Ajax调用的URL包含汉字

前面三种情况都是由浏览器发出HTTP请求,最后一种情况则是由Javascript生成HTTP请求,也就是Ajax调用。还是根据吕瑞麟老师的文章,在这种情况下,IE和Firefox的处理方式完全不一样。

举例来说,有这样两行代码:

url = url + "?q=" +document.myform.elements[0].value; 
// 假定用户在表单中提交的值是“春节”这两个字
  http_request.open('GET', url, true);

 那么,无论网页使用什么字符集,IE传送给服务器的总是“q=%B4%BA%BD%DA”,而Firefox传送给服务器的总是“q=%E6%98%A5%E8%8A%82”。也就是说,在Ajax调用中,IE总是采用GB2312编码(操作系统的默认编码),而Firefox总是采用utf-8编码。这就是我们的结论4。

六、Javascript函数:escape()

好了,到此为止,四种情况都说完了。

假定前面你都看懂了,那么此时你应该会感到很头痛。因为,实在太混乱了。不同的操作系统、不同的浏览器、不同的网页字符集,将导致完全不同的编码结果。如果程序员要把每一种结果都考虑进去,是不是太恐怖了?有没有办法,能够保证客户端只用一种编码方法向服务器发出请求?

回答是有的,就是使用Javascript先对URL编码,然后再向服务器提交,不要给浏览器插手的机会。因为Javascript的输出总是一致的,所以就保证了服务器得到的数据是格式统一的。

Javascript语言用于编码的函数,一共有三个,最古老的一个就是escape()。虽然这个函数现在已经不提倡使用了,但是由于历史原因,很多地方还在使用它,所以有必要先从它讲起。

实际上,escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。比如“春节”的返回结果是%u6625%u8282,也就是说在Unicode字符集中,“春”是第6625个(十六进制)字符,“节”是第8282个(十六进制)字符。

bg2010021110.png

它的具体规则是,除了ASCII字母、数字、标点符号“@ * _ + - . /”以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()。

所以,“Hello World”的escape()编码就是“Hello%20World”。因为空格的Unicode值是20(十六进制)。

bg2010021111.png

还有两个地方需要注意。

首先,无论网页的原始编码是什么,一旦被Javascript编码,就都变为unicode字符。也就是说,Javascipt函数的输入和输出,默认都是Unicode字符。这一点对下面两个函数也适用。

bg2010021112.png

其次,escape()不对“+”编码。但是我们知道,网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。所以,使用的时候要小心。

七、Javascript函数:encodeURI()

encodeURI()是Javascript中真正用来对URL编码的函数。

它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号“; / ? : @ & = + $ , #”,也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%。

bg2010021113.png

它对应的解码函数是decodeURI()。

bg2010021114.png

需要注意的是,它不对单引号'编码。

八、Javascript函数:encodeURIComponent()

最后一个Javascript编码函数是encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。

因此,“; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

bg2010021115.png

它对应的解码函数是decodeURIComponent()。

 

 

.

 

猜你喜欢

转载自570109268.iteye.com/blog/2409563
今日推荐