关于javaWeb的编码问题


        用户从浏览器端发起一个 HTTP 请求,需要存在编码的地方是 URL、Cookie、Parameter。服务器端接受到 HTTP 请求后要解析 HTTP 协议,其中 URI、Cookie 和 POST 表单参数需要解码,服务器端可能还需要读取数据库中的数据,本地或网络中其它地方的文本文件,这些数据都可能存在编码问题,当 Servlet 处理完所有请求的数据后,需要将这些数据再编码通过 Socket 发送到用户请求的浏览器里,再经过浏览器解码成为文本。

一次 HTTP 请求的编码示例


URL 的几个组成部分


以下测试指定web服务器为Tomcat6.X,浏览器为IE8.X和chrome15.X

准备:
1.下面的汉字以“中”为例,分别对应的各种字符集的十六进制编码如下:

ISO-8859-1: 3f   (ISO-8859-1表示的西欧字母,其中并不包含汉字,这里的3f表示不存在的字符)
GB2312: d6d0
GBK: d6d0
UTF-16: feff4e2d
UTF-8: e4b8ad

2.关于Http请求头的查看
三大主流浏览器都有相应的工具
(1).IE下可以使用ieHTTPHeaders
    下载地址:http://www.blunck.info/iehttpheaders.html
(2).Firefox可以使用livehttpheaders
(3).chrome本身就自带开发人员工具,可以很方便的查看

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。

1.在浏览器地址栏里输入:
http://localhost:8080/FileUpload/servlet/FileDownLoadServlet中?filename=中.jpg
在path info和Query String中都指定了汉字

IE8.x  Http请求消息


Chrome15.x  Http请求消息


   我们发现path info中都采用的是UTF-8编码,而Query String确采用了不同的编码方式:IE8.x采用的是GBK编码(这边没有正确的显示,至于是什么原因还不清楚),Chrome15.x采用的是UTF-8编码。

浏览器报:


根据上面的错误我们可以很明显的知道,Tomcat服务器没有正确的进行解码,从而找不到Servlet。


了解Tomcat的URL解码:

1.URL 的 URI 部分进行解码的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。
2.GET 方式 HTTP 请求的 QueryString 与 POST 方式 HTTP 请求的表单参数都是作为 Parameters 保存,都是通过 request.getParameter 获取参数值。对它们的解码是在 request.getParameter 方法第一次被调用时进行的。request.getParameter 方法被调用时将会调用 org.apache.catalina.connector.Request 的 parseParameters 方法。这个方法将会对 GET 和 POST 方式传递的参数进行解码。

结果:
    我们在Servlet的doGet方法中System.out.println(request.getParameter("filename"))
通过chrome访问的Servlet可以正确的显示汉字,而通过IE访问显示乱码
所有我们这里可以知道Tomcat对QueryString采取的解码方式是UTF-8,下面我们要确认的是Tomcat默认对QueryString采取的编码方式是UTF-8还是因为我们设置的URIEncoding=”UTF-8”?

通过这个实验来说明:首先删除server.xml中指定的URIEncoding=”UTF-8”
在浏览器中输入:
http://localhost:8080/FileUpload/servlet/FileDownLoadServlet?filename=中.jpg
结果:
在两个浏览器中都显示乱码,我们在增加两条输出语句:
new String(request.getParameter("filename").getBytes("ISO-8859-1"), "UTF-8");
new String(request.getParameter("filename").getBytes("ISO-8859-1"), "GBK");

IE在第二条输出语句中显示正确的汉字
Chrome在第一条输出语句总显示正确的汉字
结论:
   1.在IE中QueryString部分采用的默认编码方式是GBK
   2.Tomcat对QueryString的默认解码方式是ISO-8859-1
   3.URIEncoding参数提供了对path info和Query String 部分的解码方式

备注:
new String(request.getParameter("filename").getBytes("ISO-8859-1"), "UTF-8")语句可以获取正确的汉字,原因?


   ISO-8859-1 字符集的编码范围是 0000-00FF,正好和一个字节的编码范围相对应。这种特性保证了使用 ISO-8859-1 进行编码和解码可以保持编码数值“不变”。虽然中文字符在经过网络传输时,被错误地“拆”成了两个欧洲字符,但由于输出时也是用 ISO-8859-1,结果被“拆”开的中文字的两半又被合并在一起,从而又刚好组成了一个正确的汉字。虽然最终能取得正确的汉字,但是还是不建议用这种不正常的方式取得参数值,因为这中间增加了一次额外的编码与解码。



2.在已打开的网页上,直接用Get或Post方法发出HTTP请求

以html页面为例
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
作用1:告诉浏览器网页中数据是什么编码;
     2: 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。

IE8.X  http请求


Chrome15.x  Http请求



可以看到QueryString部分都采用了charset设置的UTF-8字符集进行编码
我们可以看看主流搜索引擎百度和Google

可以查看其html源码指定了charset为gb2312
<meta http-equiv="Content-Type" content="text/html;charset=gb2312">

<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
可以知道,百度默认使用的编码方式是gb2312,而Google默认是UTF-8

在jsp页面中还可以通过其他方式指定编码,和html页面charset效果一样的设置方式还有
contentType,response.setCharacterEncoding(),response.setContentType(),response.setHeader();
response.setContentType(),response.setHeader();优先级最好,其次是response.setCharacterEncoding();再者是<%@page contentType="text/html; chareset=gbk"%>,最后是<meta http-equiv="content-type"content="text/html; charset=gb2312" />


我们经常设置一个Filter,(此时我们设置前台页面的编码为UTF-8),
(1.并加上如下代码:
request.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);

这样我们在Servlet输出参数:
1.System.out.println(request.getParameter("aa");
2.new String(request.getParameter("aa").getBytes("ISO-8859-1"), "UTF-8");
3.new String(request.getParameter("aa").getBytes("ISO-8859-1"), "GBK");

结果:
1正确显示中文,2、3显示乱码,原因:request.setCharacterEncoding对前台的数据进行了解码,所有我们在Servlet中获取参数时不需要再进行解码。
(2.加上如下代码:
request.getParameter("aa");
request.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);

此时的结果是:
2正确显示中文,1、3显示乱码。
原因:前面已经提到” 解码是在 request.getParameter 方法第一次被调用时进行的”,并且此时我们没有在Tomcat中设置URIEncoding属性,默认的解码方式是ISO-8859-1.所以,在request.getParameter("aa");的时候是对UTF-8进行编码的中文用ISO-8859-1进行解码,所有是乱码,此时request.setCharacterEncoding("UTF-8")又对通过ISO-8859-1解码的字符用UTF-8进行解码,所有1结果是乱码。而2刚好可以正确的显示中文,其原因已经在上面解释过了。

总结:出现乱码问题主要是因为编码与解码的字符集不统一造成的,所以对于编码我们自己完全可以指定的我们应该尽量去指定,不要使用默认值,对于浏览器默认设置的我们完全可以动手查看它使用的默认字符集。
UTF-8 在编码效率上和编码安全性上做了平衡,是理想的中文编码方式


补充1:
我们发现不同的浏览器对QueryString采用了不同的编码方式,这就照成了我们服务器端需要对不同的情况进行讨论,其实我们这里可以采取另外一种方式,保证客户端只用一种编码方法向服务器发出请求。
使用Javascript先对URL编码,然后再向服务器提交,不要给浏览器插手的机会。因为Javascript的输出总是一致的,所以就保证了服务器得到的数据是格式统一的

Javascript语言用于编码的函数,一共有三个:
1.escape()虽然这个函数现在已经不提倡使用了,但是由于历史原因,很多地方还在使用它
escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值,对应的解码函数是unescape()
2. encodeURI()是Javascript中真正用来对URL编码的函数。
它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号“; / ? : @ & = + $ , #”,也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%它对应的解码函数是decodeURI()
3. encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。因此,“; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

对应的在服务器端我们可以通过:
分别是:
URLDecoder.decode(encodeStr,"UTF-8")
URLEncoder.encode(rawStr, "GBK")


参考: http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/

猜你喜欢

转载自zhaohuiopensource.iteye.com/blog/1397868