#circle { background-color: #8fcbec; border: 3px }
概述
开发Web项目的过程中,经常遇到浏览器中显示的内容乱码,或者服务器获取浏览器请求参数时乱码的问题,很多同学基本都是在遇到乱码的时候去网上一顿搜索,然后看哪篇文章比较靠谱就照着上面的内容去配后乱码成功消失了,然后就没然后了...
最后基本只是停留在知道怎么样设置能避免常见的乱码问题,而不知道具体的原理,一旦遇到了网上查不到的乱码场景就不知道如何解决了~
本文会深入的让你了解针对于HTTP请求时,这一去一回(Request,Response)之间,到底做了怎样的事情,让你彻底告别Web项目中的乱码烦恼。本文的内容是基于Tomcat 8.0.23版本的,其他容器也可以参考本文的内容,毕竟理论都是通的~
Request乱码
在Request过程中我们需要注意2个步骤,第一个是请求发送时所使用的编码,第二个是应用收到请求后解码时所用的编码,只有保证这两步中使用相同的编码即可有效防止乱码的发生。那么请求时使用的是什么编码呢?这个主要取决于请求时的客户端。
下面我们做个测试,客户端分别使用浏览器和curl来请求,服务端使用Tomcat 8.0.23来处理。
请求地址:http://localhost:8080/ccj/楚楚街?query=买的漂亮
我们先来分析下上面的地址:
url:http://localhost:8080/楚楚街
uri:/ccj/楚楚街
queryString:query=买的漂亮
Tomcat使用默认配置,使用下面代码来接收请求:
Firefox中请求 http://localhost:8080/ccj/楚楚街?query=买的漂亮 结果如下:
上面信息可以看出,在发送请求之前Firefox先对请求地址中的中文使用UTF-8编码进行了百分号编码,关于百分号编码的内容可以自行Google查阅相关内容,这里不在赘述。
这里我们发现获取的内容居然没有乱码,这是因为我是用的是Tomcat8的缘故,看下官方说明:
在不指定URIEncoding的情况下,并且也没指定系统属性org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true时,默认使用UTF-8进行解码。这个变化是从Tomcat8开始的,Tomcat8之前的的处理方式是,如果没指定
URIEncoding的情况下,那么会直接使用ISO-8859-1作为默认编码的。
下面我们加上系统属性org.apache.catalina.STRICT_SERVLET_COMPLIANCE=true来模拟Tomcat8之前的情况,发起同样的请求 http://localhost:8080/ccj/楚楚街?query=买的漂亮 结果如下:
看结果发现,果然出现了乱码问题,那么我们已经知道了Firefox发送过来的请求是经过了UTF-8编码的百分号编码,那么我们可以对query进行ISO-8859-1编码的百分号解码来重现下乱码,看结果:
我靠~什么鬼为什么乱码跟我们预想的结果不一样,这个乱码跟之前访问Sevlet中出现的乱码不一样???难道之前的结论都是错的么???其实并不是~这里出现2次乱码显示的不一致的情况是控制台编码不一致导致的,
我是用的IDEA,IDEA的控制台编码是受到-Dfile.encoding这个虚拟机启动参数影响的,这里送上之前写的一篇文章供参考(Java虚拟机(JVM)默认字符集详解)。
================================================华丽的分割线之IDEA乱码问题开始================================================
既然遇到了控制台编码乱码问题,接下来我就顺便讲一下IDEA控制台乱码的解决方法吧~首先我们看上面2附图,先看第一个图请求Servlet时控制台打印的是 file.encoding=GBK,而第二个图中使用main方法运行后
控制台打印的是 file.encoding=UTF-8。2次运行使用的都是同一个IDEA实例,为什么编码会不一样呢?我们先关掉IDEA,然后杀死所有当前java进程,然后重新启动IDEA,看一下进程启动参数,因为是windows系统,所以cmd中使用命令:
wmic process where caption="java.exe" get caption,commandline /value
Linux下使用命令:ps -aux | grep java
结果如下图:
看见了吧,-Dfile.encoding=GBK这个是当前IDEA实例启动时候的JVM虚拟参数,所以当前IDEA的控制台编码是GBK,但为什么我们跑main方法时打印的信息是-Dfile.encoding=UTF-8呢,这是因为我修改了Settings
中的File Encodings导致的,直接看图:
这个地方的配置会影响运行main方法时的控制台编码,那么既然知道了原因,我们就开始解决问题吧~首先我们需要更改IDEA启动时的参数-Dfile.encoding=UTF-8,修改的方式为:
windows x64环境修改 C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2017.1\bin\idea64.exe.vmoptions
windows x86环境修改 C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2017.1\bin\idea.exe.vmoptions
分不清楚的可以将上面2个文件全部修改~修改内容为在文件末尾加上红框中的内容:
MacOS系统修改文件 /Applications/IntelliJ IDEA 2017.app/Contents/Info.plist,修改完之后重启IDEA才能生效~
重启后我们再次访问 http://localhost:8080/ccj/楚楚街?query=买的漂亮 结果如下:
结果发现依然是乱码,并且乱码跟最开始访问的时候不一样了...这是什么鬼...我们继续来分析,因为是运行在Tomcat中,所以我们在打印的时候会根据启动Tomcat的那个JVM的file.encoding进行编码,
通过查看线程启动参数发现Tomcat启动时的file.encoding=GBK,那么问题就迎刃而解了,原理简述如下:
1.System.out.println("query: " + query);时,首先根据启动Tomcat的那个JVM的file.encoding进行编码(也就是GBK),然后将字节流传给控制台。
2.控制台收到字节流,然后根据启动IDEA的那个JVM的file.encoding进行解码(也就是UTF-8),然后显示在控制台。
由于上面的编码和解码的不同,导致了最终的乱码,针对上面的结论我们可以使用main方法来模拟下,如下图:
看见了吧,乱码跟之前的乱码相同了~到此原理已经懂了,那么就直接解决问题吧,给Tomcat的启动参数加上-Dfile.encoding=utf-8后,再次访问 http://localhost:8080/ccj/楚楚街?query=买的漂亮 结果如下:
这次OK了,乱码的内容跟我们在main方法里面模拟的内容一致了~至此,由于IDEA引起的乱码问题解决了,我们继续讨论Request的乱码问题~
================================================华丽的分割线之IDEA乱码问题结束================================================
根据上面的分析后,我们知道了Request乱码产生的原因,就是因为请求发送时的编码和接收请求时解码时的编码不一致导致的,那么我们需要做的就是保证他们都使用相同的编码即可。
总结:
解决Request请求地址中中文乱码问题如下:
1.Tomcat8以及之后,我们只需要使用Tomcat默认的配置即可在收到请求时默认使用UTF-8编码进行解码。
2.Tomcat8之前,我们需要修改 C:\Program Files\apache-tomcat-8.0.23\conf\server.xml 文件,参考下图:
在图中位置,添加红框中的内容即可~
到此为止,Request请求地址中中文乱码问题解决了~接下来我们来看看Request的body中的乱码问题。
我们直接将刚才的GET请求 http://localhost:8080/ccj/楚楚街?query=买的漂亮 转换成POST请求 http://localhost:8080/ccj/楚楚街 并将请求参数(query=买的漂亮)放入请求体中,如下图:
然后我们看下Servlet中打印的结果:
发现获取参数query的时候依然是乱码,此时我们只需要在所有Request的访问之前设置一下req.setCharacterEncoding("UTF-8");即可,设置重新访问结果如下:
乱码成功解决,至此浏览器发起Request请求乱码问题解决~
下面我们在看看通过工具发起Request的情况会是什么样~
我们这里使用Fiddler进行模拟GET请求:
然后我们看控制台结果:
结果发现,居然又乱码了...这次的原因是,因为我们使用的是工具而不是浏览器,浏览器是遵循HTTP规范的,而工具则不会,导致本次测试最后乱码的根本原因就是没有进行百分号编码。
这里简单说下原理:
1.Fiddler在请求的时候对请求地址做了UTF-8编码,拿query=买的漂亮来举例,在发送Request之前会对URL进行编码(这里是UTF-8)为字节流后发送给服务端,因为Fiddler没有做百分号编码,所以在对URL进行编码发送的时候,
URL中的query内容就是 买的漂亮 这四个汉字,买的漂亮 进行UTF-8编码后为e4b9b0(买)e79a84(的)e6bc82(漂)e4baae(亮)。
2.服务端在收到Request后,会将URL地址的字节流存入ByteChunk中,而我们只有在获取url、uri、queryString的时候出现了乱码的情况,这是因为在获取这3个值的时候,是不会对字节流进行解码操作的,而ByteChunk中有个默认
的Charset(即ISO-8859-1),在不进行解码的情况下(即没显式的调用ByteChunk中的setCharset(Charset charset)方法的时候),默认会使用这个默认值ISO-8859-1对获取的字节流直接解码,因此在解码
e4b9b0(买)e79a84(的)e6bc82(漂)e4baae(亮)的时候就乱码了,因为ISO-8859-1无法表示汉字。
3.所以对URL进行百分号编码是非常有必要的~
如果对上面的e4b9b0(买)e79a84(的)e6bc82(漂)e4baae(亮)进行百分号编码的话, 那么在到达URL字节流编码这一步之前,URL中的 买的漂亮 就会被替换为 "%E4%B9%B0%E7%9A%84%E6%BC%82%E4%BA%AE",
之后URL进行字节流编码时,是基于字符串"%E4%B9%B0%E7%9A%84%E6%BC%82%E4%BA%AE"进行的,因为里面都是ISO-8859-1表示范围内的字符,所以在服务端对百分号编码后的字节流进行直接获取的时候(也就是不对字节流
进行解码,而使用ByteChunk中默认的编码ISO-8859-1进行解码),就不会出现乱码了,其返回的内容就是百分号编码后的内容~
浏览器直接请求:http://localhost:8080/ccj/楚楚街?query=买的漂亮 结果如下:
至此,Request乱码问题全部解决~
总结:
解决Request请求地址中中文乱码问题如下:
1.Tomcat8以及之后,我们只需要使用Tomcat默认的配置即可在收到请求时默认使用UTF-8编码进行解码。
2.Tomcat8之前,我们需要修改 C:\Program Files\apache-tomcat-8.0.23\conf\server.xml 文件,在Connector中添加URIEncoding="UTF-8"。
解决Request请求体中的中文乱码问题如下:
1.在所有Request的访问之前设置一下req.setCharacterEncoding("UTF-8");即可。
解决使用工具请求导致对原始字节流进行获取时的乱码问题(如:req.getRequestURL()、req.getRequestURI()、req.getQueryString()等方法):
1.对URL进行指定字符集的百分号编码即可。
写了大半天时间...希望能对遇到乱码的同学有所帮助~其实我也挺懒的...很久之前就想写这篇文章了一直拖到现在才写~o(∩_∩)o ~
下一篇内容会针对Resopnse的乱码问题展开讲解,这里附上链接方便跳转:一文让你从此告别HTTP乱码(二)Response篇