分析并修补 resin 的中文处理

分析并修补 resin 的中文处理

请留意:以下说明基于 resin-2.1.10 ,其他版本可能并不相同。

resin 小巧,快速,稳定,广泛应用于工业强度的 web 应用。通常,大家认为是中文问题较少的一个 servlet container 。通常的解决办法是这样的,在 resin.conf 中加上:


<jsp precompile='true' static-encoding='true' recompile-on-error='true'/>
此外,在页面上,要避免使用: <%@ page contentType="text/html; charset=GBK"%> 指定字符集

应该说,这个配置能解决大部分的中文显示问题(能解决问题,但并非真正解决了问题,细节容后再叙)。直到前不久,因为在项目中应用一个 mvc 框架,中文问题才再次不可调和的浮现出来。深入代码,发现 resin 在处理中文时,其实是存在问题的。

我们来看一下“static-encoding='true'”和“不作字符集指定”是如何让中文正常显示的。在 WEB-INF 的 work 目录下,通常可以找到 resin 根据 jsp 生成的 java 代码。JSP中的字符串:"中文",在生成 java 文件的时候,这个字符串的转换会有这么几种情况:

static-encoding='false' (缺省值)(设置字符集没有影响)


_jsp_string1 = "\u00D6\u00D0\u00CE\u00C4".toCharArray();

static-encoding='true' (不设置字符集)


_jsp_string1 = "\u00D6\u00D0\u00CE\u00C4".getBytes();

static-encoding='true' (设置字符集GBK)


_jsp_string1 = "\u00D6\u00D0\u00CE\u00C4".getBytes("GBK");

我们知道,字符串"中文"在正确的转换应该是"\u4e2d\u6587"长度为2(可以运行native2ascii 输入"中文" 进行检查),在JSP处理的第一个环节,上面各种情况下生成的 Java 代码,"中文"字符串都是被错误转换的。也就是说,在 jsp 被转换成为 java 文件的时候,出现了编码错误。那么为什么,在“static-encoding='true' (不设置字符集)”的情况下,它能正常显示呢?

在“static-encoding='true' (不设置字符集)”的情况下,采用默认的ISO-8859-1编码方式,两个双字节编码的GBK中文字符被当作四个字节处理,必须注意到,此时,一个中文字符被拆为两个“字节字符”来处理,字符串的长度是错误的。在显示的时候,按照默认的ISO-8859-1方式编码返回给浏览器。此时,单字节流被浏览器自动识别,认出是GBK格式,从而可以正常显示。也就是说,此时的正常显示实际上是利用了ISO-8859-1和UNICODE相互转换时按单字节处理的特点(不进行转码处理),将错就错的实现了中文的正常显示。

我们定位了问题,是在 jsp 被转换成为 java 文件的时候出现了编码错误。那么如何使JSP中的中文能正确的生成java文件?我跟踪了 resin 的源代码,找到了一个可行,但也许不是最好的解决办法。

JspParser.java of resin-2.1.10


///// added by jackyz
private static String sysEncoding = System.getProperty("file.encoding");
Page parse(Path path, String uri, String className,
String uriPwd, CauchoRequest req, CauchoApplication app)
throws Exception
{
ReadStream stream = path.openRead();
///// added by jackyz
stream.setEncoding(sysEncoding);
try {
return parse(stream, uri, className, uriPwd,
path.getParent(), req, app);
} catch (FileNotFoundException e) {
throw e;
} catch (IOException e) {
if (path.isDirectory())
throw new FileNotFoundException(path.toString());
else
throw e;
} finally {
stream.close();
}
}

新修改下,加上页面的GBK编码,Java程序中定义的中文字符可以正常显示,URL和FORM传递的GBK中文也能正常处理。

记得修改之后编译并更新resin.jar使这个修改生效。

参考方法:
先获取 resin-2.1.10 的源代码
cd $RESIN_HOME/src
javac -classpath ../lib/resin.jar;../lib/jsdk23.jar com/caucho/jsp/JspParser.java
jar uvf ../lib/resin.jar com/caucho/jsp/*.class

此 bug 和修改已经提交到Resin的BugTrace


*******************************************************************************
作者:jackyz
Email: jackyz[AT]163.net
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
*******************************************************************************

在这个问题上花费了不少时间,特此整理出来,希望对大家有用。

很不错!

假如 jsp 文件本身使用的编码不是 file.encoding Property 那怎么办呢?能正常读出吗?
如,jsp 文件以 UTF8 编码存放,而系统的 encoding 是 GBK.

> 假如 jsp 文件本身使用的编码不是 file.encoding
> Property 那怎么办呢?能正常读出吗?
> 如,jsp 文件以 UTF8 编码存放,而系统的 encoding 是
> GBK.

不能正常读出。

原则上,要将发布环境的语言和开发环境的语言设置成一致的。比如,在GBK语言环境下开发的WEB/WAR,应在GBK语言环境下发布。

排除 file.encoding 识别的因素。单就处理而言,以UTF8编码存放的JSP中如果包含中文,在GBK编码环境下发布,将会因为解码错误将不能正常生成Java文件(中文的UTF8编码通常是3字节码,而GBK编码是2字节码)。

我猜想 Tomcat 在编译 jsp 的时候做了些处理。
当你在 jsp 上申明该jsp 文件的 charset 时, jsp 编译器会以给定的charset 来读取 jsp 文件,并生成相应的 java 文件,然后又按给定的 charset 来编译生成的 java 文件。

这样的处理我认为比较合理

我也跟踪过 jboss/jetty (它在 jsp 部分也采用了 org.apache.jasper 组件),它的处理和你说的大致相同。年代久远,具体细节已记不清了。 Tomcat 的确有优越的地方,不过做商业应用似乎很多人认为它性能不好以及不够稳定。

继续分析,得到一个不修改任何文件情况下的可行办法。

1.所有的JSP都要设定contentType="text/html; charset=GBK",注意,include中设的不起效,必须每个文件都设
2.使用request.setEncoding("GBK")预处理Request

好处:
1.不要求设定系统编码方式
2.不需要修改resin的文件

似乎这是更好的解决办法。

用:httpd.exe -J-Dfile.encoding=ISO-8859-1
从来没有什么问题!

注意java classes也要用ISO-8859-1编译

如果都统一成UTF-8,这样中英文平台都可以使用了。每个应用系统都必须自己设计解决。

utf8好像在together和eclipse中都不支持

一直在用 jackyz 所说的这种方法,运行于win2k(中文)和RH6.2+gbk包/RH8.0下,中文一直没问题,所以刚开始看的时候都不知道楼主要说什么

哦,这篇旧帖居然被翻新到首页上了。

关于用UTF8编码来写源代码,似乎在 ultraedit 里面可以转换的,但,默认情况下,编译器似乎不认。也许可以通过设定编码来解决吧,我没做更多的尝试,在这点上没有发言权了。

这里更详细的解释一下我所强调的两点:

1.所有的JSP都要设定 contentType="text/html; charset=GBK", include 中设定的不起效,必须每个文件都设。

深入分析,我发现,和 tomcat 的 jasper 一样,resin 的 jsp 解析程序(JspGenerator.java)也是通过这个标志来判断源代码的编码方式,用 GBK 编码写的 jsp 文件,设定 charset=GBK 之后,即使在其他的编码方式下也能正确的解析编码格式,无需在其他地方特别设定。此时,在处理内部已经正确的转换成了 unicode 编码,一个中文被当作一个字符来对待,字符串的长度是正确的。

resin jsp 编译程序默认的编码是 iso_8859_1 ,如果处理的 jsp 文件包含中文而没有指定编码的,在处理的第一个步骤(根据 jsp 生成 .java 文件)中就已经是错误的了。虽说可以通过 iso_8859_1 的方式输出,依赖客户端的自动识别正确显示。但在处理的内部,所有的中文是以错误的方式处理的,无法直接拿来比较和运算(一个 GBK 的中文字符被当作是两个 byte 的 iso_8859_1 字符来对待)。

resin 中 jsp 标记 @page include 并不像 shtml 一样静态处理,而是用程序方式动态包含的。所以,即使在被 include 的文件中指明编码方式,它的作用域也仅限于该文件本身。主文件中包含的中文,因为没有正确的编码指定,仍会被错误解析。这就是在 include 中指定 charset 并不解决问题的原因。


2.使用 request.setEncoding("GBK") 预处理 Request 。

中文的另外一个问题是 GET 和 POST 的提交,它们常常会被错误解析,这个问题的简单解决办法就是在取得任何数据之前设定 request.setEncoding("GBK") 这样就无需手动转换每个参数。

<jsp precompile='true' static-encoding='true' recompile-on-error='true'/>
<%@ page contentType="text/html; charset=GBK"%>

我在开发中,这两句都用了,没有发现乱码.