1

在我们的生产环境中,我们时常会遇到一个非常奇怪的 Tomcat 编码问题。

我还不能准确指出问题发生在代码中的哪个位置,但它涉及将非 ascii 字符替换为近似的 ascii 字符。

例如,将字符 'å' 替换为 'a'。由于该站点是瑞典语,因此字符“å”、“ä”和“ö”很常见。但是由于某种原因,替换 'ö' 字符总是有效的,所以像“Köp inte grisen i säcken”这样的字符串变成了“Kop inte grisen i säcken”,即 'ä' 没有被替换,而 ' ö' 字符是。

关于这个问题的一些快速事实:

  • 它很少发生(我们已经注意到它 3-4 次,第一次可能是 1-2 年前)。

  • 重新启动有问题的服务器会使问题消失(直到下一次)。

  • 它从来没有同时发生在一个以上的前端服务器上。

  • 它并不总是发生在同一个前端服务器上。

  • 不涉及前端的用户输入。

  • 所有前端服务器都连接到相同的 CMS 和 DB,相关配置相同。

  • 所有前端服务器都具有相同的相关配置(linux 配置、tomcat 配置、java 环境配置,如“file.encoding”等),并使用相同的脚本启动(全部根据托管/服务提供商)。

  • 所有前端服务器都使用相同的站点战争文件和相同的 jar 文件。

  • 发生此字符替换问题时,在站点上看不到其他编码问题。

  • 我们从未能够在任何其他环境中重现该问题。

由于 CMS 要求,我们使用 Tomcat 5.5 和 Java 5。

对于这种行为,我只能想到两个可能的原因:

  1. 托管服务提供商有时会以不同的方式启动/重新启动前端服务器,可能是使用具有其他环境变量或其他文件访问权限的另一个用户帐户,或者可能使用不同于正常脚本的其他脚本。

  2. 在 Tomcat 或 webapp 启动期间运行的某些进程依赖于其他进程,有时(间歇但很少)这两个(或更多)进程碰巧以导致此编码缺陷的顺序运行。

但即使上面的 1 或 2 是这种情况,它仍然不能完全解释真正发生的事情。有什么确切的区别可以解释这一点?由于所有“file.encoding”、“file.encoding.pkg”、“sun.io.unicode.encoding”、“sun.jnu.encoding”和所有其他相关环境变量在所有前端机器上都匹配(使用视觉验证调试页面,而问题正在发生)。

有人可以为这种奇怪的间歇性行为想出一些合理的解释吗?简单地升级 Tomcat 和/或 Java 版本并不是一个真正相关的答案,因为我们真的不知道这是否能解决问题,它仍然不能解释问题是什么。我更感兴趣的是确切地了解问题是由什么引起的。

问候/吉米

更新:

我想我找到了执行字符替换的代码。在初始化时(由第一次调用来进行替换)它构建一个 HashMap<Character, String>,并像这样填充它:

lookup.put(new Character('å'), "a");  

然后,当它应该替换字符串的字符时,它会遍历每个字符,并以字符为键在哈希映射中查找每个字符,如果找到替换字符串,则使用它,否则使用原始字符.

这部分代码已经有 3 年多的历史了,并且是由一个早已不复存在的开发人员编写的。如果我今天重写这段代码,我会做一些完全不同的事情,这甚至可以解决问题。但它仍然无法准确解释发生了什么。有人可以看到一些可能的解释吗?

4

1 回答 1

1

在进行替换之前,将输入规范化为正常的 Form C。

例如,ä可以只有 1 个字符U+00E4,也可以是两个字符a( U+0061 ) 和组合分音符U+0308

如果您的替换只是寻找组合形式,那么分解的形式仍将保持不变,\u0061\u0308因为这些都不匹配\u00e4

public static void main(String args[]) {
    String decomposed = "\u0061\u0308";
    String composed = "\u00e4";

    System.out.println(decomposed);
    System.out.println(composed);
    System.out.println(composed.equals(decomposed));
    System.out.println(Normalizer
            .normalize(decomposed, Normalizer.Form.NFC).equals(composed));

}

输出

ä
ä
false
true
于 2012-12-21T12:58:35.500 回答