Charset in J2EE Web Application

运行环境:Win2K Pro日文版,IE 6.0SP1日文版, SUN J2SDK 1.4.2_04, Tomcat 4.1.27,JSPs

由于在Tomcat下,从request中(比如通过request.getParameter(String)方法)取得的数据都是“ISO8859_1”对应的Unicode字符串,

(我猜想整个过程应该是这样的:

  1. 假设HTML的Encoding为“Shift_JIS”,那么IE对form中各input控件的值(编码为“Shift_JIS”)进行URL Encoding后发送给Tomcat;
  2. 然后Tomcat对接收到的数据进行URL Decoding后转换成“ISO8859_1”对应的Unicode数据(比如日文中的全角波浪线“~”的“Shift_JIS”编码为0x8160(16进制,两个byte,高位在前),经过IE进行URL Encoding后发送给Tomcat,然后Tomcat进行URL Decoding得到了“/u0081/u0060”,也就是前面所说的““ISO8859_1”对应的Unicode数据”);JSPs完成了自己的处理后,将“Shift_JIS”对应的Unicode数据通过response对象传递给Tomcat,然后Tomcat再对这些数据进行URL Encoding(Tomcat采用通过类似reponse.setContentType(“text/html; charset=Shift_JIS“)设定的编码来进行URL Encoding,若JSPs中未设定contentType的话,Tomcat将采用OS默认编码来进行URL Encoding),接着传回给客户端的IE;
  3. IE进行URL Decoding后再将之转换成“Shift_JIS”编码的数据并最终显示成HTML页面。

所以我们在JSPs中取得请求数据后,一般要将该数据转换后才不会出现乱码情况。我们需要在JSPs中做类似下面的转换:

    String reqParamA = new String((request.getParameter(“txt_a“)).getBytes(“ISO8859_1“), “Shift_JIS“);

但是在向客户端输出数据时,则不需要再做转换。首先调用reponse.setContentType(“text/html; charset=Shift_JIS“);然后直接将“Shift_JIS”对应的Unicode字符串通过HttpServletResponse向客户端输出即可。

上面描述的是从客户端向服务器发出请求到服务器端向客户端发回响应,客户端接收到响应并最终显示HTML页面的典型流程,但是也有些例外需要注意:

  • 通过调用response.sendRedirect(str_url),在服务器端直接重定向到另一个URL时。假如str_url中包含了queryString(比如someUrl?paramA=“[包含双字节字符的字符串]“paramB=“[包含双字节字符的字符串]“...),那么必须对str_url做类似下面的转换,否则映射到目的URL的JSPs取得的请求数据就都是乱码(因为就像上面所说的,它们所期望的请求数据应该是“ISO8859_1”所对应的Unicode字符串):

          response.sendRediret(str_url.getBytes(“Shift_JIS“), “ISO8859_1“); //这种做法在Linux下有可能导致乱码,尚未验证。

  • 文件上传。此时服务器端的JSPs所获取的数据流的编码形式就是该文件的字符集所对应的Unicode数据流。比如在Win2K Pro日文版下,一个.csv文件的编码就是“MS932”,那么当它被上传到服务器端时,JSPs所获取的数据流就是“MS932”对应的Unicode数据流。
  • 假设此时的运行环境为Win2K Pro日文版 + VOBSEnhydra 5.1SE,当在客户端JavaScript里通过window.showModalDialog()(该ModalDialog内的HTML的Encoding为“Shift_JIS”)向服务器端发出请求时,服务器端接收到的请求数据却是“MS932”对应的Unicode字符串;若使用Tomcat 4.1.27,则服务器端接收到的就是正常的“ISO8859_1”对应的Unicode字符串。此外,在服务器端调用reponse.getHttpResponse.sendRedirect(targetUrl)来进行重定向时,如果targetUrl为“ISO8859_1”对应的Unicode字符串,目标PO接收到的请求数据竟然是“MS932”对应的Unicode字符串;但是在Tomcat 4.1.27下,如果targetUrl为“ISO8859_1”对应的Unicode字符串,则目标JSPs接收到的请求数据也是“ISO8859_1”对应的Unicode字符串。看来,VOBSEnhydra 5.1SE与Tomcat 4.1.27在请求或应答数据的编码处理方面还是有些不一样的。
  • 假设运行环境变为Miracle Linux 2.1(OS默认字符集为“EUC-JP-LINUX”),VOBSEnhydra 5.1SE。通过reponse.getHttpResponse.sendRedirect(targetUrl)来进行客户端重定向时,需要模拟HTTP客户端对targetUrl进行URL encoding(将targetUrl中的queryString变成类似“http://localhost:8002/Test.po?txt_firstName=%81%60%8BI%8D%81%81%60&txt_lastName=%93%A1%8C%B4&Submit=Submit”的样子,网页上实际输入的txt_lastName是“藤原”,而txt_firstName则是“紀香”。你肯定曾经在IE的地址栏里看到过类似这样的奇怪字符串。在Java程序中,可以通过调用java.net.URLEncoder.encode(String, String)方法来进行URL encoding;但是JavaScript中是否有类似的方法就不得而知了),因为如果targetUrl中的queryString里包含双字节字符,目标PO将无法得到正确的queryString:

          //此程序在Miracle Linux 2.1 + VOBSEnhydra 5.1SE和Win2K Pro日文版 + VOBSEnhyra 5.1SE下皆能正常运行

          0) String pageEncoding = “Shift_JIS“; //假设HTML页面使用“Shift_JIS”编码

          1) String param = comms.request.getParameter(“param“); //从请求中取得param的值

          2) param = new String(param.getBytes(“ISO8859_1“), “Shift_JIS“);

          3) param = URLEncoder.encode(param, pageEncoding); //进行URL encoding(最重要的一步!)

          4) String str_url = “Xxx.po?param=“ + param;

          5) comms.response.getHttpServletResponse().sendRedirect(str_url); //进行客户端重定向

  • 不过,如果是在Win2K Pro日文版,VOBSEnhydra 5.1SE环境下,假如将第2, 3行代码注释掉(也就是直接向sendRedirect()方法传递“ISO8859_1”对应的Unicode字符串),程序也能运行,不过目标PO从requet取得数据已经是“Shift_JIS“对应的Unicode字符串了,而不是通常情况的“ISO8859_1”对应的Unicode字符串。但是,这样的做法在Miracle Linux + VOBSEnhydra 5.1SE环境下会出乱码,原因尚在调查中。
  • 结论:在应用程序中(比如客户端JavaScript调用window.showModalDialog(target_url, ...)或是服务器端的response.sendRedirect(target_url))向服务器端发出GET请求前,一定要对target_url中的queryString的value部分进行URL Encoding,否则服务器端的目标程序可能无法获得正确的queryString(尤其是queryString中包含双字节字符的时候)。这种做法不论在Windows还是Unix/Linux下,Tomcat还是VOBSEnhydra或是别的什么Web服务器下,都是很保险的。

我有两个建议,一是在进行字符集转换时,最好明确地写出源字符集和目的字符集,因为如果省略不写的话,Java会采用系统默认字符集来处理,而不同的OS的默认字符集通常都是不一样的,这样就很可能会出现同一个J2EE Web App在WinNT/2K/XP下运行正常,但是一移植到Unix/Linux下就出乱码的问题。

    推荐的写法   :String str = new String(str.getBytes(“GB2312“), “ISO8859_1“);  //假设str的原始编码就是GB2312,与OS无关   

    不推荐的写法:String str = new String(str.getBytes(), “ISO8859_1“); //假设str的原始编码就是GB2312,与OS无关 

二是不要忘了调用response.setContentType(String);否则Web Server会使用OS默认编码方式进行URL Encoding,然后向客户端发送响应,很有可能导致乱码。比如HTML页面的Encoding为“Shift_JIS”,那么就应该在Servlet向客户端发出响应之前调用response.setContentType(“text/html; charset=Shift_JIS“);如果是JSP的话,则应确保页面中有“<%@ page contentType=”text/html; charset=Shift_JIS” %>”这样的语句)。

愿仍在被日文环境下的J2EE Web Application乱码问题折磨的朋友们能从这篇文章中得到一些启示。

※本文中的“编码”和“字符集”意义相同

你可能感兴趣的:(application,web,encoding,tomcat,string,url,Java)