这两天在处理应用从Jetty迁移至Tomcat容器出现的乱码问题时,我理了一下编码相关的问题,整理出来备忘,也顺便给大家分享。
1.先普及一下编码知识:
如大家所知互联网或者本地数据都是基于二进制来进行传输和存储,而在双方相互通讯时具体哪些二进制数代表哪些字符,那就需要一个大家都达成统一共识的编码规则。
üASCII: 最早的计算机编码是由ANSI定制的ASCII编码,该编码是单字节编码,使用7位二进制数表示128个可能的字符,适用于当时的所有拉丁文字字符。
üISO-8859-1: 同样使用8位单字节来储存,是ASCII码的升级,除了兼容7位ASCII码以外,增加了其他语言符号的支持。由于是单字节编码,通过ISO8859-1编码的字符在解码的时候都不会被丢失,换言之,所有编码的字节流都可以通过ISO8859-1来传输或者存储,这一点对于网关实现商户字符集兼容非常关键,后面会讲到!
üGB2312/GBK: GB2312是专门用来表示中文汉字的编码,是16位双字节编码,其中英文字母部分和ASCII和ISO-8859-1一致。另外GBK是兼容GB2312的繁体字编码。
üUNICODE: UNICODE的出现之目的是要解决不同编码之间的统一存储和处理问题,UNICODE用定长8位双字节来编码,是最统一的编码,可以表示所有语言的字符。所以很多软件内部都是基于UNICODE来处理的,包括JAVA。
üUTF: UTF几乎可以支持所有语言的编码,考虑到UNICODE是定长双字节很占用空间,UTF采用不定长编码,占用1-6个字节,对于英文字母采用单字节编码,而中文采用三个字节来编码。总体来说汉字的UTF网页还是要比Unicode的节省空间,因为包含了很多英文字符。但是相比较如果大部分是汉字的话,采用双字节的GB2312要比UTF-8更节省空间。
2.浏览器和Web Server之间的交互:
POST方式
浏览器端发送:
提交数据首先按照页面设定的Chaset字符集来进行编码传输。
WebServer端接收:
所有的数据转码基本上在不同容器的request.getParameter()和request.getParameteMap()等过程中进行,该过程首先会对提交过来的数据流按照一定编码方式来进行解码,最终变成Java的Unicode。
服务器会通过以下几种方式来获取编码方式:
1) Http Header里面的Context-Type(<%@ pagecontentType="text/html;charset=UTF-8"%>)
2) Http Header里面的Accept-Charset(form attribute设置)
3) Request.setCharacterEncoding手工设定的编码方式(注意这个方法必须是在获取第一个参数之前指定,否则不会生效。)
4) 通过各种Charset Filter来指定编码,其原理同第二点,即改变每个Request的Charset。
如果都没有指定,则按照容器默认编码进行解码
注:Tomcat默认编码是ISO-8859-1, 而Jetty的默认编码是UTF-8
GET方式
GET方式和POST方式不同的是所有提交数据都是包含在Http Header里面的QueryString(http://XXXX/a.htm?a=1&b=2)里面,而不是Body里面。
浏览器端发送:
QueryString的编码方式基本是取决于系统的编码方式,不同浏览器的处理方式也都不一样。所以GET方式的编码是最难把控的,如果要彻底兼容的话可以在生成QueyString之前通过后端程序或者JS把字符按照指定字符集预先URLEncoding。
WebServer端接收:
Get方式的服务端解码编码方式完全取决于容器的配置,Request.setCharacterEncoding对GET方式不会生效。
Jetty:
1) 启动时指定JVM参数:-Dorg.eclipse.jetty.util.URI.charset=UTF-8
2) Servlet里面手工指定:request.setAttribute("org.mortbay.jetty.Request.queryEncoding","UTF-8");
不指定的话默认用UTF-8
Tomcat:
1) Server.xml <connector>节点增加URIEncoding="UTF-8"
2) Server.xml <connector>节点增加useBodyEncodingForURI="true"指定按照POST方式来获取编码
不指定的话默认用ISO-8859-1
衍生出来的另外两种交互方式:
Redirect:
在返回Response的Header里面增加了浏览器重定向命令,后续的流程同GET方式。
Forward:
不会经过浏览器跳转,在服务器内部实现了跳转,第一个Request的Attribute和Parameter都会带入下一个Request,最后做Merge操作。
由于Forward是容器内部的跳转,所有不同的容器处理字符集的实现有所差别。
Jetty:
QueryString的编码方式取决于容器的配置,默认为UTF-8
Tomcat:
QueryString的编码方式可以被POST方式的Request.setCharacterEncoding来指定。
*正是由于这个差别,导致了应用这次上线出现的乱码和签名出错问题。
3.兼容商户编码的解决方案