alwaystest / Blog

24 stars 2 forks source link

Tomcat解析URL中文乱码问题 #8

Open alwaystest opened 8 years ago

alwaystest commented 8 years ago

Tomcat解析URL中文乱码问题

标签(空格分隔): JSP


不是第一次遇到这个问题。当时使用了重组String的方法来解决,也知道产生乱码的原因是编码和解码的时候使用的字符集不一致,但是时间久了,难免忘记当时的心得,而且限于眼界,当时对这个问题也没有进行深究,今天又折腾了半天,记录一下,如果将来有更深入的研究,再来完善。

首先,Java的Class文件内部编码采用UTF-16,特殊情况下编译时才会需要手动指定源文件编码格式,所以源文件的编码方式不会导致中文乱码出现,比如

//fileEncoding = GBK
String data = "你好";
printWriter.write(data);

服务器端接收到的并不是GBK编码的字节流。

使用Post方式访问Tomcat服务器时,服务器默认使用ISO-8859-1来解析收到的数据。设置request.setCharacterEncoding("UTF-8")可以使服务器强制使用"UTF-8"编码集对收到的数据进行解码。而正常情况下应该在发送Post请求的时候在HTTP头中通过Content-Type指定字符串的编码方式。这样Tomcat就可以根据HTTP头中的信息指定编码集进行解码,此时中文解析正常。

使用Get方式访问Tomcat服务器时,默认采用ISO-8859-1来解析收到的数据,即使设定了request.setCharacterEncoding("UTF-8"),也不会生效,是因为Tomcat服务器的conf/service.xml 中设定<Connector>中的useBodyEncodingForURI=false(默认)。设定为useBodyEncodingForURI=true就可以使request.setCharacterEncoding("UTF-8")生效。或者在<Connector>中通过设定URIEncoding="UTF-8"指定Get方式默认使用UTF-8来解码。

经过这样的设定之后,就不用使用new String(bytes, encoding)的方式来解析中文了。我不喜欢用重建String的方式,要打的代码太多了。

IE对URL中的中文默认好像不是使用UTF-8来编码的,所以这个时候IE访问带中文的URL还是乱码。嗯,我不打算兼容IE。

参考:

从Tomcat的中文乱码问题谈Java编码机制

Tomcat关于encoding编码的默认设置以及乱码产生的原因

又进行了测试:

String data = "username=你&password=1";
LogUtil.d("Bytes", Arrays.toString(data.getBytes("iso-8859-1")));
LogUtil.d("Bytes", Arrays.toString(data.getBytes("utf-8")));
LogUtil.d("Bytes", Arrays.toString(data.getBytes("GBK")));
/*
[117, 115, 101, 114, 110, 97, 109, 101, 61, 63, 38, 112, 97, 115, 115, 119, 111, 114, 100, 61, 49]
[117, 115, 101, 114, 110, 97, 109, 101, 61, -28, -67, -96, 38, 112, 97, 115, 115, 119, 111, 114, 100, 61, 49]
[117, 115, 101, 114, 110, 97, 109, 101, 61, -60, -29, 38, 112, 97, 115, 115, 119, 111, 114, 100, 61, 49]
*/
System.out.println(Arrays.toString("你".getBytes("iso-8859-1")));
System.out.println(Arrays.toString("你".getBytes("utf-8")));
System.out.println(Arrays.toString("你".getBytes("GBK")));
System.out.println(Arrays.toString(request.getParameter("username").getBytes()));
System.out.println(Arrays.toString(request.getParameter("username").getBytes("ISO-8859-1")));
System.out.println(Arrays.toString(request.getParameter("username").getBytes("UTF-8")));
System.out.println(Arrays.toString(request.getParameter("username").getBytes("GBK")));
/*
[63]    //你 iso-8859-1
[-28, -67, -96] //你 utf-8
[-60, -29]  //你 gbk
[63, 63, 63]    //无参
[-28, -67, -96] //iso-8859-1
[-61, -92, -62, -67, -62, -96]  //utf-8
[63, 63, 63]    //gbk
*/

方便对比,只把"你"的Bytes输出来。

//Android
[63]    //iso-8859-1
[-28, -67, -96] //utf-8
[-60, -29]  //gbk
//server
[63]    //你 iso-8859-1
[-28, -67, -96] //你 utf-8
[-60, -29]  //你 gbk
//服务器接收到的
[63, 63, 63]    //无参
[-28, -67, -96] //iso-8859-1
[-61, -92, -62, -67, -62, -96]  //utf-8
[63, 63, 63]    //gbk

发现我的服务器getBytes()是用的GBK。(数据使用post发送)。

使用iso-8859-1解码收到的数据,正是"你"的utf-8编码格式。

所以使用new String(data.getBytes("iso-8859-1"),"utf-8");可以正确的解析出原来的汉字。

使用Chrome的开发者工具查看发送的Get请求。发现"你"的编码是%E4%BD%A0,转换为Byte数据,刚好是[-28,-67,-96]

所以使用Android的HttpURLConnection的Post方法,发送的数据默认就是UTF-8编码的。