采用DoGet方式提交中文,乱码产生原因分析及解决办法

前段时间某功能在测试机器上出现乱码,情况如下:

 
现象:
          调试搜索功能时,通过doGet方法提交到后台的中文参数在本地和开发测试机器上为乱码(Action层),在测试人员测试机器上为中文.(Action层)
推断:
怀疑是两台机器(开发人员测试机器,测试人员测试机器)环境不同:
1. 先从tomcat查起,在他们各自的tomcat的配置文件server.xml中的Connector标签,有句指定URL编码的配置:URIEncoding="UTF-8" ,
    开发测试机器上没有配置(默认是IDO-8859-1),测试人员测试机器上配置为GBK,两个配置不同,改成一致(默认),再验证,还是存在乱码;
 
2. 怀疑是系统字符集的问题,用locale命令和查看/etc/sysconfig/i18n 文件查看两台机器的字符集,查看后发现两台机器系统字符集一致,都为UTF-8
 
3. 怀疑是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的方式带中文参数请求:
采用DoGet方式提交中文,乱码产生原因分析及解决办法
 
 Wireshark 抓取到的信息如下,是UTF-8编码的信息,这步是浏览器自己做的:
采用DoGet方式提交中文,乱码产生原因分析及解决办法
 
 在IE9下可以看到用DoGET的方式带中文参数请求:
采用DoGet方式提交中文,乱码产生原因分析及解决办法
 
 Wireshark 抓取到的信息如下,是UTF-8编码的信息,这步是浏览器自己做的:
采用DoGet方式提交中文,乱码产生原因分析及解决办法
  
从这里可以看到两个浏览发送中文参数请求的编码方式是不同的.请注意IE里面的高级设置中有这么一个选项:
采用DoGet方式提交中文,乱码产生原因分析及解决办法
经过试验这的URF8编码,是对URL中的中文例如:

www.baidu.com/中文/index.html 中的中文二字进行编码

发送请求后用Wireshark 可以看到,如下图,编码后的部分URL

采用DoGet方式提交中文,乱码产生原因分析及解决办法

采用DoGet方式提交中文,乱码产生原因分析及解决办法
 
为了验证,我在服务器上(开发人员测试机器)上又装上了服务器端的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发送请求时服务端截取的文件:

采用DoGet方式提交中文,乱码产生原因分析及解决办法

IE9发送请求时服务端截取的文件:

采用DoGet方式提交中文,乱码产生原因分析及解决办法
 
可以看到和浏览器提交的编码是一致的,所以,可以推断,在浏览器端是编码传输的,并且没有产生所谓的乱码.
 
然后,我在服务端开启远程debug,可以看到在调用以下方法的时候产生了乱码:
采用DoGet方式提交中文,乱码产生原因分析及解决办法
 
采用DoGet方式提交中文,乱码产生原因分析及解决办法
但是仔细查看,发现request对象中有个specialAttribute属性,里面以键值对的方式存放了keyword参数,这里的keyword是上图中浏览器编码后的值,所以,在这里是没有乱码的。
 
为什么在调用request.getParameterMap()方法后会产生乱码呢? 
其实原因和tomcat有关系,在我们调用getParameterMap方法时,request对象会去找WEB容器中的URL编码去解码URL,在Tomcat中指的是在server.xml的以下配置中配置的编码

采用DoGet方式提交中文,乱码产生原因分析及解决办法

而开发人员测试机器上没有改配置,即默认使用ISO-8859-1去解码,测试人员测试机器上配置为GBK,即用GBK去解码URL,如果浏览器的编码和tomcat配置文件中配置的编码不一致,就会产生乱码 ,

为了证明我的猜想,我简单加密解密了一段中文,运行结果如下:

采用DoGet方式提交中文,乱码产生原因分析及解决办法

这里与我在debug过程中看到的乱码是一致,从而得出结论,乱码的根音来自于tomcat配置文件中设置的URLEncoding参数。

最后,再次总结下:

1. 采用DoGet方式传值,浏览器会帮我们编码,但是各个浏览器的编码方式不一致。

2. 传输过程中,编码后的值不会再被编码。

3. 通过Request对象取参数值时,Request对象会去Web容器中获取URL的编码方式,并用同样的编码区解码URL,如果浏览器的编码和Web容器中的编码不一致,就会产生乱码。

4. 解决方法,在浏览器提交请求前,将中文参数编码两次,浏览器不会再对其进行编码,传到服务端后解码一次即可。

为什么客户端编码两次,服务端解码一次?请看下图:

采用DoGet方式提交中文,乱码产生原因分析及解决办法

先声明一个被两次编码以后的字符串,模拟我在浏览器端手动编码的结果,

然后用三种方法对该字符串解码,为什么用三种? 因为这次解码是在调用request.getParameter时

request对象的解码服务端tomcat容器的URLEcoding可能是三种中的一种,可以看到无论是

什么编码方式的解码结果都是一样的,因为这里的配置可能是不一样的,所以要考虑到各种编码方式的可能,这个时候,我们再用代码手动解码一次,就可还原回中文了!

乱码问题就这么解决了,虽然需要手动的解码一次,不过我觉得这个值得,因为乱码后的情况奇奇怪怪,

若觉得每次接么复杂,只需在拦截器端都统一解码就可以,以后不用再担心这个问题.

 
上一篇:react-native 项目实战 -- 新闻客户端(4) -- 请求网络数据


下一篇:Android 手机卫士--构建服务端json、请求网络数据