tomcat编码问题根源-2
??? 3.2. 转换过程的参数设置
??? 在以上的转换过程中可以看到,new String(code, "GB2312");这一处的第二个参数非常重要,它告诉java,这个字符串的原始编码是什么,如果写成其他的编码,必然会发生错误。
??? 因此,转换过程中的参数,java必须有途径得知正确的参数。?
????举例说,让java从文本文件里面读取一段中文。Java最终得到的字符串肯定是Unicode的,但是它需要程序员告诉它,这个文本文件到底是GB2312的,还是UTF-8的。如果程序员不告诉它,它就会贸然选择一种编码,进行解释,其结果可能就是乱码了
又举例说,如果一个Servlet得到了一个http的请求(request),需要程序员告之,这个请求里面的字符串是UTF-8,GB2312,还是纯粹的英文?当然,在得不到信息的时候,它又会自己胡乱猜一个。
??? 怎么?你不记得自己在代码里面指定过?对了,也许这就是中文问题的原因。?
????多数应用平台(应用服务器或其他)都不是关心中文问题的人编写的,所以不同的应用平台之间,设置参数的方式并不一样,可以说千奇百怪,甚至根本错误。
??? 在最新版本的tomcat上,推荐的处理方法涉及4个参数
??? 1. 网页必须制定http content type
??? 2. JSP必须制定JSP content type
??? 3. servlet/jsp的请求,必须设定字符集request.setCharacterEncoding.
??? 4. 修改tomcat的server.xml文件中URIEncoding的设置
??? 正如文章开头提到的,不同应用服务器的方式并不一致。前3个参数都是规范规定的,而第4的参数并没有规定,所以不同的服务器要用不同的方法。比如tomcat4不需要这个参数,它会直接使用参数3。
??? 有人认为修改tomcat的配置文件麻烦,而自己写转码的函数进行处理,这不是一个很好的方法,如果转换服务器,因为服务器本身的处理方式变化了,这段“转码”函数很有可能得到面目全非的结果,比如在tomcat4上。
??? 这里面似乎存在很多重复,但是没有办法。有时候给一段字符给java,java确实没有办法猜测其字符集。如参数3本来是没有的,J2EE规范认为浏览器提交的数据会描述数据的字符集,但是实际上没有浏览器遵守,所以才不得不引入这个参数。?
????4. 中文问题的非技术原因
??? 以上分析了中文问题出现的技术原因,除此以外,还存在两个不是纯技术方面的因素
?
??? 4.1. 错误做法的信心从何而来
??? 早在几年前的tomcat3年代,网上流传的种种解决方法中,就有一种声称不能给JSP指定字符集,否则会造成乱码。这是一个很奇怪的说法,似乎SUN设计的机制完全错误一样,但是何以有不少人相信并且深受其害呢?
因为中文编码本身的特性,有时候几个错误的操作,反而会得到似乎正确的结果。
比如,“中”的GB2312编码有两个字节D6 D0
??? 1. 如果要把它作为ISO8859-1来处理,实际上是对应两个字符的,即? 和D。
??? 2. 如果不指定用户提交数据的字符集,tomcat会以为提交的是这两个字符。
??? 3. 经过tomcat处理之后,再次输出到相应页面,tomcat当然仍然以ISO8859-1的方式输出两个字符? 和D
?? ?4. 这样,客户端得到了ISO8859-1的?D,也就是和GB2312的“中”一样的两个字节。因此他会觉得,自己提交的数据被正确的处理了。
??? 实际上这样的做法是非常危险的
??? 首先,这个时候在tomcat的内部,代表这个数据的String的值不是Unicode的“中”,而是Unicode的?和D也就是4个字节(不要忘记,java的字符串既不是ISO8859-1,也不是GB2312),尽管把这两个错误的字符转为ISO8859-1的字节输出后得到了似乎正确的结果,但是如果系统中还有其他不是来自于用户输入的字符串呢?比如说来自于数据库?tomcat如果把正确的字符串按照同样的方法处理,自然就会造成乱码。
其次,因为String的内容并不是应用程序要处理的内容本身,甚至连长度都变了,所以很多字符串操作都会出现莫名其妙的错误。
??? 最简单的例子:
??? 下载最新的tomcat,运行
??? 1. 访问 http://localhost/servlets-examples/servlet/RequestParamExample
??? 2. 点击右键把字符集改成GB2312
??? 3. 分别在两个框输入中 文
??? 4. 提交
??? 5. 得到一个显示乱码的页面(左)
??? 6. 再点击右键把字符集改成GB2312
??? 7. “中文”两个字出现了(右)
??? 结果似乎正确,除了要手工改浏览器字符集以外(某些版本的浏览器并不需要)。但是只要使用调试器,或者直接在代码中打印用户输入的字符,就可以看到,得到的用户输入不是“中文”,而是?D ??。
这就是为什么有些应用程序“界面显示正确,但是控制台打印的都是乱码”的原因。当你知道自己看起来正确的系统中正在运行着这些奇怪的字符的时候,该考虑放弃这些错误的“捷径”了
??? 4.2. 并不可靠的工具和服务器
??? 习惯上,我们总是认为我们使用的开源工具或者商业服务器是正确的,起码是没有大问题的。这个假设在很多时候正确,但是在中文问题方面,并非如此。别忘了多数软件的作者都是欧美人,他们中的很多并不懂也不关心中文或者其他双字节字符集的问题。因此有些工具或者应用服务器在中文处理方法上模糊不清,或者根本错误(真正可怕的是某些国产商业软件都是错误的)。
??? 这给我们的工作增加了很多难度,因为你的代码在别人的应用服务器上运行,有时候你必须适应他们代码里面的错误。
有很多工具有提供所谓的转码功能,在你弄清楚它到底是做什么之前,不要贸然使用,起码要做一个备份。已经有不少人使用这种“电子碎纸机”杀死了自己的中文数据。
就连java的规范本身,也存在漏洞,比如String.getBytes()这个函数。其设计就是根本错误的,尽管新版的jdk已经声明废止这个函数,但是很多人还在使用。又如URI的编码方式,servlet规范并没有指出应该如何设置,这才导致了tomcat实用自定义的URIEncoding参数。
??? 5. 小结
??? 作为一般的应用开发者,没有必要去深究中文字节的每个转换过程。但是,我们需要记住,中文存在很多种形式,而Java如何处理这些形式,需要程序员告知。
简单的说,最可靠的处理办法,就是保证中文在每一个环节都正确。这并不是废话。如果程序员严格按照规范的推荐,设置所需要的各种参数,最后即使遗漏了什么,也是很容易找到原因的。相反,如果走所谓的捷径,有意的疏漏或者错误设置字符集,或用大串的getBytes()进行可疑的转换,也许可以得到似乎正确的结果,但是当问题出现的时候,就很可能成为死结了。