ServletとJSPにおける文字化けについて

このドキュメントでは,特にServletとJSPの開発を取り上げて,そこで発生する文字化けについて解説します.

ただし,このドキュメントで用いられている用語や概念がわからない場合は,たいていはあなたの知識不足が原因ですから,まずJavaHouse-Brewersのトピックスを見たり,過去記事を検索して,自分で勉強してください. すぐメーリングリストで聞いたりしてはいけません.

なお,このドキュメントの内容をしっかり理解したい場合には,拙著「CafeBabe Javaプログラミングノート - 国際化と日本語処理」を参考になるでしょう.


文字化けの原因

ServletとJSPの開発において,文字化けに遭遇することは非常に頻繁にあると思います. この文字化けは,だいたい次の3つの場所で発生します.

場所1と場所2は,Javaのプログラム内の問題です. Javaプログラムの特徴として,文字列はUnicodeで処理するために,入力時と出力時に文字エンコーディングの変換処理が入り,ここが問題になることが多いようです.

問題が,場所1にあるのか,場所2にあるのかを特定するためには,以下のようなことをおこなってください.

  1. 問題になっているStringやchar配列を16進数でダンプします. このダンプには,たとえばこのUnicodToolクラスを使ってみてください. 正しくUnicodeに変換されている場合には,問題はたいていは場所2にあります.
  2. 問題になっているStringやchar配列に,REPLACEMENT CHARACTER (U+FFFD)が含まれていないかを調べます. JavaのByteToCharConverterは,変換できない文字をUnicodeのREPLACEMENT CHARACTERに置換するからです.

場所3は,HTTPやHTMLのレベルの問題です. これらでは,通信やファイルの内容の文字エンコーディングをcharsetとして指定するのですが,Javaプログラムが正しい形式で出力しても,このcharset指定が一致しないと文字化けが発生します.

たとえば,WebサーバやWebブラウザでは,charsetは次のような優先順位で処理するようにHTML仕様 4.0で規定されています.

  1. Content-Typeのcharsetパラメータ (HTTP)
  2. http-equivのあるMETA宣言のContent-Typeやcharsetの値 (HTML)
  3. 要素のcharset属性 (HTML)

なお,ServletやJSPのContent-Typeで"text/html; charset=Shift_JIS"のようにcharsetを指定した場合は1に相当します.

さらに,charsetが付加されていない場合や間違っている場合を考慮して,charsetの自動判定やユーザによる指定が認められています.


文字化けの種類

文字化けは,だいたい次のようなパターンに分類できます. このドキュメントでは,このパターンを元に原因を探れるように構成します.

タイプ1: 日本語部分の大部分が'?'に化ける場合

このタイプの文字化けは,Javaプログラムの入力,または出力に使用しているコンバータが間違っている場合に発生します.(場所1 or 場所2)

タイプ2: 大部分は正常に判読できるが,一部の文字だけが'?'に化ける場合

このタイプの文字化けは,Javaプログラムの入力と出力のコンバータのマッチングが取れていない場合に発生します.(場所1 or 場所2)

タイプ3: 日本語部分が,さまざまな文字に化ける (英数字や記号で,日本語は含まれません)

たとえば,これは出力がISO-8859-1だと解釈されている場合に発生します.(場所3)

タイプ4: 日本語部分が,さまざまな文字に化ける (一部日本語を含みます)

たとえば,これは出力が異なる漢字コードだと間違って解釈されている場合に発生します.(場所3)


項目名 タイプ1 タイプ2 タイプ3 タイプ4
Servlet
setContentTypeメソッドとgetWriterメソッド - -
getParameterメソッド - -
JavaServer Pages
page指示子 - -
include指示子 - - -
jsp:includeアクション - - -
Java一般
JISAutoDetectの誤認識 - -
間違ったロケール設定 - - -
プロパティファイル中の日本語文字列 - - -
HTML
META要素 - -
Apacheサーバ
AddDefaultCharset指示子 - -
Redirect指示子 - - -

setContentTypeメソッドとgetWriterメソッド

症状

javax.servlet.ServletResponseクラスのsetContentTypeメソッドで,正しいcharsetを指定しない場合には,文字が化けます.

また,その後にgetOutputStreamメソッドを使った場合にも,文字が化けます.

解決策

setContentTypeメソッドで,適切なcharsetを指定し,さらにgetWriterメソッドを使ってPrintWriterオブジェクトを取得して,使用します. なお,ここではIANAに登録済みの文字集合名を指定してください. たとえば,Shift_JIS, ISO-2022-JP, EUC-JP,UTF-8などが該当します. 大文字・小文字の区別はしません.

例:

        res.setContentType("text/html; charset=iso-2022-jp");
        PrintWriter out = res.getWriter();
        // 省略
        out.println("こんにちは");

ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください. "Shift-JIS" (JDK 1.1.8以降ではMS932です)で出力したいが,MS932とは内部マッピングが異なる場合には,自分でマッピングの変換をおこなうか,以下のように別のコンバータを指定してください.

例:

    res.setContentType("text/html; charset=Shift_JIS");
    PrintWriter out =
        new PrintWriter(new OutputStreamWriter(res.getOutputStream(), "SJIS"));


getParameterメソッド

症状

javax.servlet.ServletRequestクラスのgetParameterメソッドを使用して,日本語の値を取得しようとすると,文字が化けることがあります. これは,URLエンコードして渡されたパラメタの中身をISO-8859-1だと誤解してURLデコードしてしまうためです.

ただし,化けるかどうかは,使用しているServletコンテナに依存していて, たとえば,Apache JServやJakarta Tomcatは化けますが,IBMのWebSphereは化けないなどの違いが出てきます. しかし,化けない場合でも,潜在的な問題があるアドホックな実装であることが多いようです.

解決策

化ける場合には,以下のようなコードで,一度オリジナルのバイト列に逆変換してから,正しい文字エンコーディングを指定して,Unicodeに変換するようにします.

例:

    if ((s = req.getParameter("s")) != null) {
       try {
	   s = new String(s.getBytes("iso-8859-1"), "Shift_JIS");
       } catch (UnsupportedEncodingException e) {
         // エラー処理を記述する
       }
    }

Servlet API 2.3では,setCharacterEncoding(String enc)が追加されるので,今後はこれを用いて文字エンコーディングを指定することになります. 指定しない場合のデフォルト値はISO-8859-1です.

ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください.


page指示子

症状

JSPでは,出力の文字エンコーディングをpage指示子を用いて設定しておかなければいけません.

解決策

以下のようにcharsetを設定します. なお,ここではIANAに登録済みの文字集合名を指定してください. たとえば,Shift_JIS, ISO-2022-JP, EUC-JP,UTF-8などが該当します. 大文字・小文字の区別はしません.

<%@ page contentType="text/html; charset=shift_jis" %>

include指示子

症状

現在のTomcatのJasper (JSPのコンパイラ)では,ISO-8859-1の入力しか扱えないという制約があります.

例:
<%@ page contentType="text/html; charset=euc-jp" %>
<%@ include file="euc.html" %>

解決策

日本語がJavaプログラム内でしか使用されていなければ,あらかじめ取り込むファイルをnative2asciiで,Unicodeエスケープに変換しておきます.

<%@ page contentType="text/html; charset=euc-jp" %>
<%@ include file="euc.html" encoding="euc-jp" %>

JSP 1.2仕様では,pageEncoding属性が追加され,これで取り込むファイルの文字エンコーディングを指定できるようです. Tomcat 3.2はJSP 1.1なのですが,鰈崎義之さんが作成したパッチを使用して,一足先にpageEncodingオプションを使うこともできます.

ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください.


jsp:includeアクション

症状

Tomcat 3.2では,jsp:includeで取り込む静的なファイルの文字エンコーディングは,Javaのデフォルトエンコーディングと一致していないと正しく処理できないバグがあります.

<jsp:include page="test.html" flush="true"/>

解決策

暫定的な回避策としては,取り込むファイルを,Javaのデフォルトエンコーディングで保存してください. ただし,これが不可能な場合には,私が作成したパッチを使用してorg.apache.tomcat.request.StaticIntercepterクラスを修正すれば,指定したファイルの文字エンコーディングが変換されずに,そのまま取り込まれるようになります.

なお,JSPファイルを指定した場合には,JSPの出力がそのまま取り込まれますので,この限りではありません. これが化ける場合は,Page指示子でcharsetを指定していないのが原因ですから,page指示子の項目を読んでください.


JISAutoDetectの誤認識

症状

JISAutoDetectコンバータは,短い日本語文字列を与えられただけでは,必ずしも正しく判断できません. どちらか判断できない場合には,Shift-JISとして処理してしまうことが多いようですが,逆の場合もあります.

たとえば,日本語EUCの文字をShift-JISの半角カタカナだと誤認識した場合には,半角カタカナを多く含む判読不可能な文字列になります.

解決策

必要がない限り,JISAutoDetectを使わないようにしてください.

半角カタカナを処理しないのなら,JDKのソースコードを入手して, その中のJISAutoDetectコンバータのソースコード (ext/i18n/src/share/sun/io/ByteToCharJISAutoDetect.java)のマッピングテーブル部分を変更して使用すれば,より正確な自動認識が可能です.

ただし,タイプ2の文字化けの場合には,Unicodeの内部マッピングが異なるコンバータを組み合わせて使っている可能性がありますので,Javaの日本語関連コンバータにおけるマッピングの違いを見て,適切なコンバータを使用しているかどうかを確認してください.

特に,デフォルトエンコーディングが"MS932"であるWindows環境では,JISと日本語EUCにはISO2022JPとEUC_JPを用いるのに,Shift-JISにはMS932を用いるので,漢字コードが混在すると,問題が発生することに注意してください.


間違ったロケール設定

症状

Java VM, javac, native2asciiなどのツールは,デフォルトではロケールの設定に基づいて動作します. Unixのロケールの設定は,"printenv LANG"を実行すれば確認できます. 日本語は,ja,ja_JP.PCK, japaneseなどです.

しかし,ロケールの設定が正しくない場合には,ソースコードやプロパティファイル中に直接書き込んだ日本語文字列を正しく変換できません.

解決策

このような場合には,ロケールを正しく設定しているか,そしてそれらのファイルを記述した文字エンコーディングが,そのロケールの文字エンコーディングと一致するかどうかを確認してください. たとえば,Solarisでは,jaロケールなら日本語EUC,ja_JP.PCKロケールならShift-JISです.

英語環境で正しく処理したい場合には,-encodingオプションを用いて,入力ファイルの文字エンコーディングを指定してください.


プロパティファイル中の日本語文字列

症状

プロパティファイル内に日本語文字列を定義して,それをjava.util.Propertyクラスやjava.util.ResourceBundleクラスを使って取得すると文字化けします.

解決策

プロパティファイル内に,次のように直接日本語文字列を書き込むことはできません.

例:

    hello: こんにちは

native2asciiなどのツールや,Java Internationalization and Localization Toolkitを用いて,Unicodeエスケープ形式に変換してから使用してください.

例:

    hello: \u3053\u3093\u306b\u3061\u306f


META要素

症状

出力したHTMLファイルの先頭に,以下のような宣言をしていることがありますが,これが出力と一致していない場合には,文字化けを起こします. この文字化けは,リロードしても解決されないことがあります.

<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">

解決策

Servlet及びJSPでは,プログラムレベルでcharsetを指定しないと正しく動作しませんし,それがHTTPプロトコルレベルでWebブラウザに伝えられますので,META要素を用いる必要はありません. かえって,文字化けの原因になることが多いので,使わないようにしましょう.


AddDefaultCharset指示子

症状

ApacheのAddDefaultCharset指示子を用いると,その影響範囲のServletの出力には,指定されたcharsetが付加されてしまいます. 当然,リダイレクトをおこなった場合にも付加されるのですが,Netscape Navigatorにはリダイレクトのリクエスト付加されているcharsetを,リダイレクト先のコンテンツを表示するために使用するというバグがあり,文字化けの原因になります. この文字化けは,リロードすると解決することがあります.

解決策

AddDefaultCharsetを使用して一括指定しないで,必要に応じて指定します.


Redirect指示子

症状

Netscape Navigatorにはリダイレクトのリクエスト付加されているcharsetを,リダイレクト先のコンテンツを表示するために使用するというバグがあります. Apache 1.3.12以降では,リダイレクトする時に"iso-8859-1"を指定しているので,リダイレクト先のコンテンツが異なる文字エンコーディングの場合には文字化けが起こります. この文字化けは,リロードすると解決することがあります.

解決策

該当部分をcharsetを付加しないように,apacheメーリングリストに投稿されたパッチを元に修正します.


(風間 一洋 NTT未来ねっと研究所)