前段时间某功能在测试机器上出现乱码,情况如下:
现象:
调试搜索功能时,通过doGet方法提交到后台的中文参数在本地和开发测试机器上为乱码(Action层),在测试人员测试机器上为中文.(Action层)
推断:
怀疑是两台机器(开发人员测试机器,测试人员测试机器)环境不同:1. 先从tomcat查起,在他们各自的tomcat的配置文件server.xml中的Connector标签,有句指定URL编码的配置:URIEncoding="UTF-8" ,开发测试机器上没有配置(默认是IDO-8859-1),测试人员测试机器上配置为GBK,两个配置不同,改成一致(默认),再验证,还是存在乱码;2. 怀疑是系统字符集的问题,用locale命令和查看/etc/sysconfig/i18n 文件查看两台机器的字符集,查看后发现两台机器系统字符集一致,都为UTF-83. 怀疑是apache上有不同的设置,再到apache的httpd.conf上查看,两天的设置基本一致,没有对编码有特殊的设置.4. 怀疑是JVM运行参数的原因.java有一个运行时参数叫做:file.edcoding.这个编码是保存java文件的编码字符集,在调用javac.exe时,JDK用file.edcoding将.java文件读进来,转化为UNICODE放到内存中,所以,这个参数对乱码不会产生影响.从以上可以看出,常规的排查手段已经不能找到原因,所以,从HTTP通信开始下手,从请求发起开始一步步排查,首先,在本地,用到了3个工具,分别是:1. httpFox , Firefox上的一个插件,用来查看所有发起的http请求,内容非常详细.2. membrane-monitor 也是查看http通信的客户端,不依赖浏览器.3. Wireshark 同样是http的抓包工具,他不仅仅可以自己通过各种表达式抓取特定http包,还可以解析tcpdump(后面会说到)抓取的信息(好像tcpdump抓取的是16进制的数据)这次排查三种工具都用到,最简单的是httpFox 使用简单,该有的功能都有,其次是Wireshark,还有很多功能没用到,感觉很强大,而且关键是和浏览器无关,是个很好的分析工具.排查在两个浏览器中进行,IE9和Firefox17,在Firefox下可以看到用DoGET的方式带中文参数请求:
Wireshark 抓取到的信息如下,是UTF-8编码的信息,这步是浏览器自己做的:
在IE9下可以看到用DoGET的方式带中文参数请求:
Wireshark 抓取到的信息如下,是UTF-8编码的信息,这步是浏览器自己做的:
从这里可以看到两个浏览发送中文参数请求的编码方式是不同的.请注意IE里面的高级设置中有这么一个选项:经过试验这的URF8编码,是对URL中的中文例如:www.baidu.com/中文/index.html 中的中文二字进行编码
发送请求后用Wireshark 可以看到,如下图,编码后的部分URL
为了验证,我在服务器上(开发人员测试机器)上又装上了服务器端的http抓包工具,tcpdump,安装过程如下:1、打开网址:www.tcpdump.org/ 下载 libpcap-1.0.0.tar.gz (595.0KB) 软件包,通过命令 tar zxvf libpcap-1.0.0.tar.gz 解压文件,并将其放入自定义的安装目录。2、打开网址:www.tcpdump.org/ 下载tcpdump-4.3.0.tar.gz (867.0KB) 软件包,通过命令 tar zxvf tcpdump-4.3.0.tar.gz 解压文件,并将其放入自定义的安装目录。3.一次到解压目录下:./configure
make
make install即可安装.接着使用命令,将抓取到的信息放到write.log中,tcpdump -X -s 0 -w write.log host xxx.xxx.xxx.xxx and port 80说明下参数:-x 以16 进制数形式显示每一个报文
-s 重定义截取报文大小,默认为96(或68),如果定义为0,则表示获取完整报文
-w 将抓取到的报文放到文件中
host 指定抓取报文的目的地址
port 指定报文目的端口,因为该机器上是apache转发,填apache的端口就可以.
在服务端执行命令后用IE,Firefox下发送请求,将抓取到的文件放到Wireshark中解析,可以看到:
Firefox发送请求时服务端截取的文件:
IE9发送请求时服务端截取的文件:
可以看到和浏览器提交的编码是一致的,所以,可以推断,在浏览器端是编码传输的,并且没有产生所谓的乱码.然后,我在服务端开启远程debug,可以看到在调用以下方法的时候产生了乱码:
但是仔细查看,发现request对象中有个specialAttribute属性,里面以键值对的方式存放了keyword参数,这里的keyword是上图中浏览器编码后的值,所以,在这里是没有乱码的。为什么在调用request.getParameterMap()方法后会产生乱码呢?其实原因和tomcat有关系,在我们调用getParameterMap方法时,request对象会去找WEB容器中的URL编码去解码URL,在Tomcat中指的是在server.xml的以下配置中配置的编码
而开发人员测试机器上没有改配置,即默认使用ISO-8859-1去解码,测试人员测试机器上配置为GBK,即用GBK去解码URL,如果浏览器的编码和tomcat配置文件中配置的编码不一致,就会产生乱码 ,
为了证明我的猜想,我简单加密解密了一段中文,运行结果如下:
这里与我在debug过程中看到的乱码是一致,从而得出结论,乱码的根音来自于tomcat配置文件中设置的URLEncoding参数。
最后,再次总结下:
1. 采用DoGet方式传值,浏览器会帮我们编码,但是各个浏览器的编码方式不一致。
2. 传输过程中,编码后的值不会再被编码。
3. 通过Request对象取参数值时,Request对象会去Web容器中获取URL的编码方式,并用同样的编码区解码URL,如果浏览器的编码和Web容器中的编码不一致,就会产生乱码。
4. 解决方法,在浏览器提交请求前,将中文参数编码两次,浏览器不会再对其进行编码,传到服务端后解码一次即可。
为什么客户端编码两次,服务端解码一次?请看下图:
先声明一个被两次编码以后的字符串,模拟我在浏览器端手动编码的结果,
然后用三种方法对该字符串解码,为什么用三种? 因为这次解码是在调用request.getParameter时
request对象的解码服务端tomcat容器的URLEcoding可能是三种中的一种,可以看到无论是
什么编码方式的解码结果都是一样的,因为这里的配置可能是不一样的,所以要考虑到各种编码方式的可能,这个时候,我们再用代码手动解码一次,就可还原回中文了!
乱码问题就这么解决了,虽然需要手动的解码一次,不过我觉得这个值得,因为乱码后的情况奇奇怪怪,
若觉得每次接么复杂,只需在拦截器端都统一解码就可以,以后不用再担心这个问题.