前言
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编码"成为了一个混乱的领域。
如下:
- 1:地址栏输入和点击url访问浏览器时,chrome下无论请求地址和参数,均经过utf-8编码;非chrome浏览器,其中请求地址用utf-8编码,参数按操作系统编码进行编码(ie下的编码参数部分都不带%)。但参数中含特殊符号时,未确定何特殊符号会被编码。
例子:http://www.google.com/小明|=?param=小明|=
a.上面的url在中文xp的chrome下,发送的请求地址是
http://www.google.com/%E5%B0%8F%E6%98%8E%7C=?param=%E5%B0%8F%E6%98%8E|=
b.在中文xp的firefox下,发送的请求地址是
http://www.google.com/%E5%B0%8F%E6%98%8E%7C=?param=%D0%A1%C3%F7|=
c.在中文xp的ie下,发送的请求地址是
http://www.google.com/%E5%B0%8F%E6%98%8E%7C=?param=С??|=
- 2:超链接中的请求,请求地址用utf-8编码,参数用页面编码,但是在ie下的参数不带%号。
在上面的例子中,在页面指定utf-8编码下,参数部分会变成:param=%E5%B0%8F%E6%98%8E|=
。但ie会变成param=E5B08FE6988E|=
。
-
3:js进行超链接访问时,和超链接情况一致。
-
4:ajax异步请求时,ie总是用gbk来编码,包括网页路径和参数,非ie和超链接请求一致。
在上面例子中,ie下发送的请求会变成:http://www.google.com/%D0%A1%C3%F7%7C=?param=d0a1c3f7|=
form提交涉及到的编码
form 表单中有一个 accept-charset 属性,如果没有此属性会用页面编码来提交,如果页面编码没有就是 utf-8编码。
两个应用编码不同,一个是GBK编码,另一个是UTF-8编码。现在要在GBK编码的应用里使用表单向UTF-8编码的应用里提交数据,很显然,如果不做特殊处理的话,会出现乱码。
解决方案:
在GBK编码的页面里编写如下代码: <form method="post" action="..." accept-charset="utf-8"> ... </form>
如此的代码在Firefox等正常的浏览器下没有任何问题,但是遇到IE这个变态浏览器就不灵光了,我们还得用点不入流的手段Hack一下: <form method="post" action="..." accept-charset="utf-8" onsubmit="document.charset='utf-8';"> ... </form>
ajax 中涉及到的编码
ajax 提交到服务器的编码上面已经讨论过。但是获取后台内容也会有乱码的情况出现:
如果ajax 后台返回的不是utf-8的编码的二进制流,那么在xmlhttprequest 对象的responseText 方法中字符就会乱码,因为浏览器默认用utf-8来解码。
我在后台返回时的conent-type 中也明确书写了charset :gbk 。但是实测没起到作用。 这个等有时间在验证
注意:
无论何种方式提交的服务器,浏览器都会转化成ascll字符,然后以ascll对应的码点转成二进制进行传输。对于那么不能直接转成ascll字符的,比如汉字。也会先用以上的编码方式转成ascll字符。
比如:
春节 ==》%E6%98%A5%E8%8A%82 utf-8编码
==》 %B4%BA%BD%DA gbk 编码
在utf-8编码下,一个汉字 会有9个字节进行传输。
在一个html标签的属性中会有长度的限制。比如:
a 标签的href 的长度限制,计算这个长度时要注意以上的细节
由于各个浏览器处理起来的不一致,所以我们需要用js来处理编码,因为js的处理结果都是一致的,都是utf-8编码。三个函数:
1.escape()
复制代码
虽然这个函数现在已经不提倡使用了,但是由于历史原因,很多地方还在使用它,所以有必要先从它讲起。
实际上,escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。比如"春节"的返回结果是%u6625%u8282,也就是说在Unicode字符集中,"春"是第6625个(十六进制)字符,“节"是第8282个(十六进制)字符。
它的具体规则是,除了ASCII字母、数字、标点符号”@ * _ + - . /"以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()。
escape对0-255以外的unicode值进行编码时输出%u****格式,其它情况下escape,encodeURI,encodeURIComponent编码结果相同。
所以,“Hello World"的escape()编码就是"Hello%20World”。因为空格的Unicode值是20(十六进制)
首先,无论网页的原始编码是什么,一旦被Javascript编码,就都变为unicode字符。也就是说,Javascipt函数的输入和输出,默认都是Unicode字符。这一点对下面两个函数也适用。
其次,escape()不对"+“编码。但是我们知道,网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。所以,使用的时候要小心。可以这样处理下 str.replace(/+/g,”%u002B");
escape不编码字符有69个:*,+,-,.,/,@,_,0-9,a-z,A-Z
2.encodeURI()
encodeURI()是Javascript中真正用来对URL编码的函数。
它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号"; / ? : @ & = + $ , #",也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%。
它对应的解码函数是decodeURI()。 需要注意的是,它不对单引号’编码。
3.encodeURIComponent()
最后一个Javascript编码函数是encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。
因此,"; / ? : @ & = + $ , #",这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。
它对应的解码函数是decodeURIComponent()。
encodeURIComponent不编码字符有71个:!, ',(,),*,-,.,_,~,0-9,a-z,A-Z
上面说到form post提交的时候可以设置 charset。那ajax提交的时候怎么办?比如怎么把上面三个函数处理过的utf-8编码的字符发送到GBK编码的后台
首先,当我们使用提交时,浏览器会根据当前页面编码,encode一次,然后发送到服务端,服务端接收到表单,会自动dencode一次,通常这个过程是对程序是透明的。即,浏览器把请求翻译成ascll字符传输,服务端接受二进制并翻译成ascll进行dencode。
这个dencode 和前端的encode 的编码不一致问题就来了。
解决:
-
使用escape函数 把中文换成%uxxxx的形式提交。这样浏览器encode的时候不会在处理了。到了后台,dencode完也是这种形式。 之后再用utf-8转一下。
-
1.在A应用处用encodeURIComponent()2次编码参数数据,如k=中文(utf-8编码为%E4%B8%AD%E6%96%87),进行2次编码后,encodeURIComponent(encodeURIComponent(k))=%25E4%25B8%25AD%25E6%2596%2587
-
2.将请求发送到url2 = http://www.xxx.com/a.htm?k=%25E4%25B8%25AD%25E6%2596%2587&encoding=utf-8
-
3.url2对应应用收到这个请求后,web容器会对%25进行解码,变成%,对应request.getParameter(“k”)=%E4%B8%AD%E6%96%87
-
4.再URLDecoder.decode("%E4%B8%AD%E6%96%87", “UTF-8”)就能解码回原来的中文而不会乱码了。
从编码到页面呈现中涉及的编码
我们在开发过程中新建文件(js,css,html),都会有默认的编码方式存储在硬盘上。不过的开发工具新建文件时的编码方式不同。
源代码发布到web容器后,用户在浏览器访问我们的文件,web容器以二进制流发送我们的文件给浏览器。
浏览器要解析这个二进制流。那么问题来了,浏览器用什么编码去解析?
-
1.根据http协议中的 content-type 中的charset ,content-type中的charset的优先级最高,如果有charset选项浏览器将忽略以下规则
-
2.html页面中meta标签中的charset。(js,css 也有charset ,如果有将用此charset ,如果没有就用宿主html的charset)
-
3.浏览器默认charset
浏览器解析完这个二进制流之后,会把这些信息存在变量中。 变量在内存中存储的编码方式就是浏览器默认的了,跟以上的规则都没有关系了。 包括在html input 中输入字符都是浏览器以默认编码存储在内存中。
====================================================================
1.在前台两次用javascript的方法encodeURI对参数进行两次编码,如
$.ajax({
type: "POST",
url: $('#basePath').val()+"checkUserName?user.username="+encodeURI(encodeURI($("#usernameText").val())),
data: {},
success: function(data) {
alert(JSON.stringify(data));
}
});
在后台要进行解码:
System.out.println(URLDecoder.decode(user.getUsername(), "UTF-8"));
一、问题:
编码问题是JAVA初学者在web开发过程中经常会遇到问题,网上也有大量相关的文章介绍,但其中很多文章并没有对URL中使用了中文等非ASCII的字符造成服务器后台程序解析出现乱码的问题作出准确的解释和说明。本文将详细介绍由于在URL中使用了中文等非ASCII的字符造成乱码的问题。
- 在URL中中文字符通常出现在以下两个地方:
- (1)、Query String中的参数值,比如http://search.china.alibaba.com/search/offer_search.htm?keywords=中国
- (2)、servlet path,比如:http://search.china.alibaba.com/selloffer/中国.html
- 出现乱码问题的原因主要是以下几方面:
- (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
说明:
- ContextPath是在Servlet服务器的配置文件中指定的。
对于weblogic:contextPath是在应用的weblogic.xml中配置。
<context-root>/</context-root>
对于tomcat:contextPath是在server.xml中配置。
<Context path="/" docBase="D:/server/blog.war" debug="5" reloadable="true" crossContext="true"/>
对于jboos:contextPath是在应用的jboss-web.xml中配置。
<jboss-web>
<context-root>/</context-root>
</jboss-web>
-
- ServletPath是在应用的web.xml中配置。
<servlet-mapping>
<servlet-name>Example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
-
3、Servlet API
我们使用以下servlet API获得URL的值及参数。
request.getParameter("name");
// 获得queryString的参数值(来自于get和post),其值经过Servlet服务器URL Decode过的
request.getPathInfo();
// 注意:pathinfo返回的字符串是经过Servlet服务器URL Decode过的。
requestURI = request.getRequestURI();
// 内容为:contextPath/servletPath/pathinfo 浏览器提交过来的原始数据,未被Servlet服务器URL Decode过。 -
4、开发人员必须清楚的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。
3、下面我们分别从浏览器和应用服务器来举例说明:
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
-
2、我们建议URL中PathInfo和QueryString采用相同的编码,这样对服务器端处理的时候会更加简单。
-
3、还有一个问题,我发现很多程序员并不明白URL Encode是需要指定字符集的。不明白的人可以看看这篇文档:http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/net/URLEncoder.html
2、 POST提交
对于POST方式,表单中的参数值对是通过request body发送给服务器,此时浏览器会根据网页的ContentType(“text/html; charset=GBK”)中指定的编码进行对表单中的数据进行编码,然后发给服务器。
在服务器端的程序中我们可以通过Request.setCharacterEncoding() 设置编码,然后通过request.getParameter获得正确的数据。
解决方案:
- 从最简单,所需代价最小来看,我们对URL以及网页中的编码使用统一的编码对我们来说是比较合适的。
- 如果不使用统一编码的话,我们就需要在程序中做一些编码转换的事情。这也是我们为什么看到有网络上大量的资料介绍如何对乱码进行处理,其中很多解决方案都只是一时的权宜之计,没有从根本上解决问题。
(二)、Servlet服务器
Servlet服务器实现的Servlet遇到URL和POST提交的数据中含有%的字符串,它会按照指定的字符集解码。下面两个Servlet方法返回的结果都是经过解码的:
request.getParameter("name");
request.getPathInfo();
这里所说的"指定的字符集"是在应用服务器的配置文件中配置。
- (1) tomcat服务器
对于tomcat服务器,该文件是server.xml
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="150" connectionTimeout="20000"
redirectPort="8443" URIEncoding="GBK"/>
URIEncoding告诉服务器servlet解码URL时采用的编码。
<Connector port="8080" ... useBodyEncodingForURI="true" />
useBodyEncodingForURI告诉服务器解码URL时候需要采用request body指定的编码。
- (2) weblogic服务器
对于weblogic服务器,该文件是weblogic.xml
<input-charset>
<java-charset-name>GBK</java-charset-name>
</input-charset>
- (三)浏览器显示
浏览器根据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的字符集采用相同的字符集,这样程序的实现就很简单,不用做复杂的编码转换。
转载地址:https://blog.youkuaiyun.com/x_i_y_u_e/article/details/38168081