Servlet一次乱码排查后的总结

原创
2014/07/04 12:54
阅读数 9.9K

由来

在写一个小小的表单提交功能的时候,出现了乱码,很奇怪request上来的参数全部是乱码,而从数据库查询出来的中文显示到页面正常,锁定肯定是request对象那里出了问题。后来经过排查,发现是我封装的框架中出了问题,总结为在setCharacterEncoding方法之前,调用了getParameter方法,导致字符集改变失败。没看过Tomcat实现Servlet的源码,貌似是一旦调用getParameter方法Request的参数就会全部被解析,从而再调用setCharacterEncoding就无效了。

原理解析

其实编码问题本质还是两点:

  1. 浏览器在封装Http请求的时候的编码和服务器在解析Http请求编码不一致
  2. 服务器返回数据的时候编码和浏览器解析不同。

那么我们就从这两点入手解析。

浏览器请求

在点击提交表单的那一刻,浏览器把表单内容封装成一个Http请求,数据通过a=1&b=2这样的形式直接请求服务器,表单值会被浏览器最一次urlencode,对于不同的请求方式编码不同:

Get和Post请求

浏览器会读取页面的编码(页面编码会在Content-type头中体现),用此编码对表单值做urlencode,那么到服务器的编码方式就是你Content-Type里的编码。很多通过JS提交表单为了规避浏览器的urlencode带来的编码混淆,会对数据首先做一次urlencode,这样在服务器上做一次urldecode既可(因为js做完urlencode后内容为ASCII字符,所以这样的字符无论浏览器用什么编码解码出来都是一样的)

AJAX请求

在Jquery中AJAX请求全部使用utf8编码封装请求,如果你的页面和项目用的非utf8编码,一定会出现乱码

浏览器地址栏直接输入带参数的地址

这种情况就比较复杂,不同的浏览器编码也不相同。Chrome之类的浏览器默认使用utf8编码(urlencode),而IE则使用GBK(死变态IE!!!)。

服务器端解码

对于服务器端我在此只讨论Servlet。

Get请求

对于Get请求,有两种方式解码:

  1. 在Servlet容器中设置,例如Tomcat设置URIEncoding="UTF-8",就会对Get请求用utf8解码(貌似Tomcat7会报无效,具体解决请百度,反正我不同这种方法)
  2. String name = new String(request.getParameter("name").getBytes("iso-8859-1"),"GBK"));第一个编码就是你Servlet容器(例如Tomcat)里设置的编码,默认iso-8859-1,第二个参数就是你浏览器使用的编码格式。如果你用表单提交,那这个编码就是页面的编码(Content-Type里的charset=XXX),如果你直接用浏览器地址栏里敲,恭喜你,你得判断userAgent来使用不同编码了。这也是我为啥不提倡第一种方式,因为它遇到浏览器直接敲出来的参数就非常不灵活。

至于为什么要使用getBytes("iso-8859-1"),是因为在你浏览器用某种编码后,Servlet容器自作多情给你用iso-8859-1解码了一下,如果你设置了URIEncoding="UTF-8"它就会用utf8给你解码,运气好你浏览器用的也是这种编码,那解出来就直接用了,所以在ISO-8859-1的情况下你得再“原路返回”到二进制,重新用正确的编码解码一下。

Post请求和Ajax请求

Post请求就比较简单一点了,同样你可以使用Get请求中的方法2来解决,不过比较麻烦,这时候我们就可以使用Servlet里的方法request.setCharacterEncoding方法设置你的解码类型,例如你的页面编码是utf8,表单则urlencode成utf8了,那么你在调用getParameter方法之前(记住,一定要之前!!在第一次调用getParameter之前!)使用setCharacterEncoding方法。 Ajax请求同理。

响应请求

响应也是相同道理,这回轮到服务器做编码,浏览器做解码。只需要设置response.setCharacterEncoding,就会自动在响应头的Content-Type中加入charset=XXX,返回的内容就可以被正常解析啦~

我想我说的相对比较清楚了,网上很多解决乱码的帖子都只是讲你加上某句代码就会解决,这样是不科学的,一定也要知道原理,也要知道每句代码背后做了哪些工作。其实我们在操作HttpServlet对象的时候,本质上是对Http头的一些信息做修改。

如果有什么问题或者理解错误的地方,欢迎指正讨论。

展开阅读全文
打赏
14
134 收藏
分享
加载中
路小磊博主

引用来自“Sub”的评论

看来楼主对 Java/浏览器 乱码问题还处在略懂一二的阶段。
虽然看似解决了问题,实则不是很理想的解决之道。
请认真考虑下面的问题:
1. 浏览器按什么编码显示页面(操作系统? Response Header?)
2. 浏览器按什么编码向服务器提交数据(URL编码方式,Request Body编码方式) (操作系统? Last Response Header)
3. Servlet按什么编码从浏览器接受数据(URL编码方式,Request Body编码方式)
4. Servlet按什么编码向浏览器输出HTML/JSON(Response Body编码方式)
5. Java 本身的 Unicode 编码规则
6. 数据库自身的编码方式,JDBC 驱动连接的编码规则 (读写数据)

引用来自“路小磊”的评论

嘿嘿,十分感谢提问。确实只是略懂一二,毕竟是一次排查中总结的。现在回答下问题: 1、我没有提及页面显示的编码。其实页面编码两个控制:页面内容本身传输到浏览器的编码和Content-Type头中的charset 2、这个问题我在浏览器部分说的,你说的URL编码方式应该和我说的GET方式是一个意思,Request Body就是我说的Post方式吧。 3、这个我在服务器端解码部分做了说明,对应同上。同时强调了Servlet容器在解码中的作用。 4、这个同1,其实服务器端response.setCharacterEncoding本质就是操作Conten-Type头中的charset。 5、这一点确实忽略了,其实我下意识的说所谓解码,解码产物就是Unicode。 6、数据库部分未涉及,因为确实平时涉及的只有MySQL数据库,所以没有说明。 再次感谢如此认真对待这个问题,我需要学习的还很多,希望指导和共同交流~~

引用来自“Sub”的评论

讲的不错。 其实我想说的是,既然楼主碰到了乱码问题,那就顺便好好研究一下乱码的本质。因为每个 Java 程序员在初期基本上都会碰到乱码问题,并且尝试解决了。通常的新手都是这么干的: String name = new String(xxx.getBytes("iso-8859-1"),"GBK")); 而这个解决之道就属于只解决表面问题,因为 xxx 变量已经是错误的编码了,已经埋下了乱码的隐患。在过一段时间,保证又会发生了乱码问题。而正确的解决方法是完全不应该出现类似这样的代码的,而是直接让 xxx 的输入编码正确,而不是解码在编码。 毕竟乱码只是表象,只有了解的 IO 的编码规则之后,基本上以后就不存会在乱码问题了。
现实确实如此~~嘿嘿。以后继续深入学习~~
2014/07/08 11:55
回复
举报
Sub

引用来自“Sub”的评论

看来楼主对 Java/浏览器 乱码问题还处在略懂一二的阶段。
虽然看似解决了问题,实则不是很理想的解决之道。
请认真考虑下面的问题:
1. 浏览器按什么编码显示页面(操作系统? Response Header?)
2. 浏览器按什么编码向服务器提交数据(URL编码方式,Request Body编码方式) (操作系统? Last Response Header)
3. Servlet按什么编码从浏览器接受数据(URL编码方式,Request Body编码方式)
4. Servlet按什么编码向浏览器输出HTML/JSON(Response Body编码方式)
5. Java 本身的 Unicode 编码规则
6. 数据库自身的编码方式,JDBC 驱动连接的编码规则 (读写数据)

引用来自“路小磊”的评论

嘿嘿,十分感谢提问。确实只是略懂一二,毕竟是一次排查中总结的。现在回答下问题: 1、我没有提及页面显示的编码。其实页面编码两个控制:页面内容本身传输到浏览器的编码和Content-Type头中的charset 2、这个问题我在浏览器部分说的,你说的URL编码方式应该和我说的GET方式是一个意思,Request Body就是我说的Post方式吧。 3、这个我在服务器端解码部分做了说明,对应同上。同时强调了Servlet容器在解码中的作用。 4、这个同1,其实服务器端response.setCharacterEncoding本质就是操作Conten-Type头中的charset。 5、这一点确实忽略了,其实我下意识的说所谓解码,解码产物就是Unicode。 6、数据库部分未涉及,因为确实平时涉及的只有MySQL数据库,所以没有说明。 再次感谢如此认真对待这个问题,我需要学习的还很多,希望指导和共同交流~~
讲的不错。 其实我想说的是,既然楼主碰到了乱码问题,那就顺便好好研究一下乱码的本质。因为每个 Java 程序员在初期基本上都会碰到乱码问题,并且尝试解决了。通常的新手都是这么干的: String name = new String(xxx.getBytes("iso-8859-1"),"GBK")); 而这个解决之道就属于只解决表面问题,因为 xxx 变量已经是错误的编码了,已经埋下了乱码的隐患。在过一段时间,保证又会发生了乱码问题。而正确的解决方法是完全不应该出现类似这样的代码的,而是直接让 xxx 的输入编码正确,而不是解码在编码。 毕竟乱码只是表象,只有了解的 IO 的编码规则之后,基本上以后就不存会在乱码问题了。
2014/07/08 09:54
回复
举报
路小磊博主

引用来自“卜祥龙”的评论

怒赞,学习了~
2014/07/08 09:06
回复
举报
怒赞,学习了~
2014/07/07 23:38
回复
举报
路小磊博主

引用来自“静风流云”的评论

一般项目规定,不允许get请求带中文。
如果一定要带,自己先用js的encodeURI。
嗯。就是为了绕开浏览器的不同差异~~
2014/07/07 22:31
回复
举报
路小磊博主

引用来自“Binny”的评论

不错,把J2EE项目中出新乱码的根本问题将清楚了,收藏一下!
谢谢~~
2014/07/07 22:31
回复
举报
路小磊博主

引用来自“kidbei”的评论

我觉得8楼扯远了,博主解析的是servlet请求和返回乱码的问题。你提出的问题不管怎样,结果都是博主说的解决办法,给Content-Type加入编码信息,浏览器按照指定的编码渲染页面。也许博主对编码原理不是很懂,但是对servlet数据交换的编码问题还是基本懂了的。
谢谢~~欢迎多多交流~~
2014/07/07 22:31
回复
举报
路小磊博主

引用来自“lwei”的评论

乱码问题表象上比较混乱,本质上,它只会发生在因数据传输而需要编码解码的时候,比如跨网络的传输、应用程序和数据库系统间的传输、或应用程序和文件系统间的传输等等。把握住了这个,解决乱码问题就是按图索骥了,不会没有思路。
嗯。还有就是注意这过程中容器、框架帮我们做了的事情,从而知道问题出在哪里。十分感谢~
2014/07/07 22:30
回复
举报
路小磊博主

引用来自“Sub”的评论

看来楼主对 Java/浏览器 乱码问题还处在略懂一二的阶段。
虽然看似解决了问题,实则不是很理想的解决之道。
请认真考虑下面的问题:
1. 浏览器按什么编码显示页面(操作系统? Response Header?)
2. 浏览器按什么编码向服务器提交数据(URL编码方式,Request Body编码方式) (操作系统? Last Response Header)
3. Servlet按什么编码从浏览器接受数据(URL编码方式,Request Body编码方式)
4. Servlet按什么编码向浏览器输出HTML/JSON(Response Body编码方式)
5. Java 本身的 Unicode 编码规则
6. 数据库自身的编码方式,JDBC 驱动连接的编码规则 (读写数据)
嘿嘿,十分感谢提问。确实只是略懂一二,毕竟是一次排查中总结的。现在回答下问题: 1、我没有提及页面显示的编码。其实页面编码两个控制:页面内容本身传输到浏览器的编码和Content-Type头中的charset 2、这个问题我在浏览器部分说的,你说的URL编码方式应该和我说的GET方式是一个意思,Request Body就是我说的Post方式吧。 3、这个我在服务器端解码部分做了说明,对应同上。同时强调了Servlet容器在解码中的作用。 4、这个同1,其实服务器端response.setCharacterEncoding本质就是操作Conten-Type头中的charset。 5、这一点确实忽略了,其实我下意识的说所谓解码,解码产物就是Unicode。 6、数据库部分未涉及,因为确实平时涉及的只有MySQL数据库,所以没有说明。 再次感谢如此认真对待这个问题,我需要学习的还很多,希望指导和共同交流~~
2014/07/07 22:28
回复
举报
路小磊博主

引用来自“GreatCoder”的评论

我也想吐槽下IE
哈哈~~一起吐槽
2014/07/07 22:09
回复
举报
更多评论
打赏
24 评论
134 收藏
14
分享
返回顶部
顶部