Developerサイトに移行します。見た目やトピックエリアなど更新されますが、開発者に必要とされるコンテンツを引き続き提供していきます。N君:「Vさん、昨日依頼されていた機能の実装が終わりましたので、リポジトリにコミットしておきました。Vさん:「お、早いね。どれどれ・・・うん、一通り動いているようだね。」N君:「ありがとうございます。今回はファイルを読み込む処理があったのですが、今までファイルI/OのAPIは使ったことがなかったので、ちょっと苦労しました。」Vさん:「なるほど。おや、N君、ちょっとここを見てくれるかい?」N君:「あれ?表示がおかしいですね。ここはファイルから読み込んだ日本語データが表示されるはずですが・・・」Vさん:「ちゃんとテストはしてあるんだよね?」N君:「はい。自分のWindows環境ではテスト済みです。」Vさん:「ああ、それで分かった。テスト環境はUnixマシンだから文字コードが違うんだよね。」N君:「文字コードって、SJISとかUnicodeとかいうアレですか?」Vさん:「そうそう。テスト環境の文字コードはEUCなんだ。いうときはどうすればいいか知っているかい?」N君:「いやー、今まであんまり気にしたことがありませんでした。」Vさん:「そうかい。コンピューターはどうやって文字列を扱っているかコンピューターが扱えるのは基本的には数値のみです。従って、コンピューターの内部では文字列を数値の並び(バイト列)として表現しています。バイト列を文字列として解釈するためには、どの値がどの文字に対応するのかをあらかじめ決めておく必要があり、その対応関係のことを一般に「文字コード」と呼んでいます。アルファベットや数値、記号など(いわゆる半角文字)は文字数が少ないため、1バイト(正確には7ビット)の範囲内で全てを表現することができます。この文字コード体系としてはASCIIコードが標準的に使われており、例えば「A」には65という値が割り当てられています。日本語の漢字などは1バイトで全ての文字を表現できないため、1つの文字に対して複数バイトの文字コードを割り当てる必要があります。どのようなバイト列をどの文字に割り当てるかについてはいくつかの方式あり、文字コード体系としてはShift_JISやEUC、Unicodeなどがよく使われていますが、他にメインフレームでよく使われているEBCDICなど多数のコード体系があります。エンコーディングと文字セット(コードページ)俗に「文字コード」と呼ばれる概念の中には、「エンコーディング」と「文字セット(コードページ)」の二つの定義が混在しています。文字化け問題の原因を正しく理解するためには、エンコーディングと文字セット(コードページ)の違いについて認識しておく必要があります。「文字符号化スキーム」と呼ばれ、どの文字をどのバイト列に対応付けるかの規則を定義したものです。具体的にはShift_JISやEUC、Unicode(UTF-8/UTF-16)など様々な方式があり、エンコーディングが異なると同じ文字であってもバイト列としての表現は異なります。「日本語」という文字列をバイト列として表現した場合は以下のようになります。8A9E従って、EUCで表現されたバイト列をShift_JISとして解釈した場合、元の「日本語」という文字列に戻すことができません。このように、バイト列を解釈する際に誤ったエンコーディングを指定することは文字化けの原因となります。日本語が全て「????」のようになってしまうなど全面的に文字化けが発生した場合、エンコーディング指定の誤りが原因である場合が多いです。エンコーディングが正しく指定されていても文字化けが発生する可能性があります。異なるOS間でテキストデータをやりとりする場合などによく発生する問題が、文字セットの差異による文字化け(文字欠け)です。呼ばれ、取り扱う対象となる文字集合を定義して命名と番号付けを行ったものです。4.0」などの名称で定義されています。ソフトウェアベンダーが発行するドキュメントでは「コードページ」と表記されていることが多いです。エンコーディングが同じでも文字セットが異なると扱える文字種が異なるため、ある処理系では表示できた文字が他の処理系では表示できないなどの問題が発生します。いわゆる機種依存文字の問題は、この文字セットの違いに起因します。特定の文字のみが文字化けする場合などは、処理系の間での文字セットの互換性について確認してみてください。これまでのまとめと用語解説エンコーディングや文字セットに関連して、本記事中では以下の用語を利用します。文字コードエンコーディングと文字セットを包含する一般用語として利用。Authority)で定義された、特定のエンコーディングと文字セットを指定する文字列。IANAはIPアドレス、ドメイン名、ポート番号などの標準化および管理を行っており、インターネット上で利用することのできるキャラクタセットの登録と公開もIANAが主管している。キャラクタセット(Java)java.nio.charset.Charset。特定のエンコーディングと文字セット指定を表すクラスだが、IANAには含まれないキャラクタセットや、IANAで定義されていない別名も含んでいる。コンバーター(Java)特定のキャラクタセットでエンコーディングされたバイト列を、Javaで扱うUnicode文字列に変換するための変換表。Unicode文字列をバイト列に変換する際にも利用される。以上の用語を利用してまとめると、「文字化けは数値の並び(Javaの用語ではバイトストリーム)を文字として解釈するときに正しいキャラクタセットを指定しなかった場合、およびある文字コードの文字列を別の文字コードに変換するときに、適切なコンバーターを使用しなかったときに発生する」と言えます。EEアプリケーションサーバーの間では、二回文字コードの変換が行われます。クライアントから受信した文字データをJavaプログラムで扱うためにUnicodeに変換し、Javaプログラムで扱われているUnicodeの文字データをクライアントに送信するために変換します。このそれぞれで、使用するコンバーターを決定するためにキャラクタセットを指定する必要があります。(図2-1)クリックして大きなイメージを見るまず受信側についてですが、ブラウザからのGETリクエストのクエリ文字列として送信された文字列、POSTリクエストのエンティティー・ボディーとしてapplication/x-www-form-urlencoded形式で送信された文字列には、仕様上の制限から文字コードの指定をつけることができません。そのため、かならず受信側で適切な文字コードを選択・指定して読み取る必要があります。EE標準の方法として提供されているのがjavax.servlet.ServletRequestインターフェースのsetCharacterEncodingメソッドです。このメソッドの引数には、文字列を読み取る際の文字コードをJavaのキャラクタセットで指定します。文字列をUnicodeに変換するには、指定されたコンバーターが直接使用されます。古いプログラムなどを使用していて、アプリケーションでのキャラクタセットの指定ができていない場合のために、WASではキャラクタセットを指定する別の方法が提供されています。この機能を使用するためには、Webアプリケーションで「自動要求エンコーディング」を有効にしておく必要があります。Developerなどの開発ツールでは、Webデプロイメント記述子で「自動要求エンコードが使用可能」にチェックを入れます。これにより、IBM拡張デプロイメント記述子(ibm-web-ext.xmi)の<webappext:WebAppExtension>要素に「autoRequestEncoding="true"」属性が追加されます。(図2-2)クリックして大きなイメージを見る自動要求エンコーディング機能は、クライアントが送信してきたAccept-Languageヘッダを元に送信文字列のキャラクタセットを決定します。Root>/properties/encoding.propertiesに指定されています。ja」のヘッダが送信されてきた場合には、「Shift_JIS」のキャラクタセットが使用されます。(図2-3)クリックして大きなイメージを見るsetCharacterEncodingによる指定も自動要求エンコーディングもおこなわれなかった場合、Javaシステムプロパティの「default.client.encoding」で指定されたキャラクタセットが使用されます。APIの仕様に従い、「ISO-8859-1」のキャラクタセットであるとして処理されます。「ISO-8859-1」はLatin-1とも呼ばれ、ラテンアルファベットのキャラクタセットであり、日本語は含まれません。一つ、ブラウザからの文字列を正しく受け取るために必要なこととして、文字参照に対する対応があります。ブラウザから送信される文字列の中に、使用しているキャラクタセットで表現できない文字が含まれていた場合、多くのブラウザはその文字を文字参照に変換して送信します。ブラウザからShift_JISで送信している文字列の中に、「®」というShift_JISには含まれない文字があると、その文字は「&#174;」という数値文字参照に変換して送信されます。これらの文字参照のデコード機能は標準では提供されていないため、アプリケーションのロジックの一部として実行する必要があります。アプリケーションサーバーつぎに送信側です。javax.servlet.ServletResponseインターフェースのgetWriterメソッドで取得されたPrintWriterを使用して応答をおこなう場合、またJSPにより応答をおこなう場合には、キャラクタセットの指定が必要となります。EE標準では、この指定のための複数の方法が定義されています。優先して使用されるのはjavax.servlet.ServletResponseインターフェースのsetCharacterEncodingメソッドおよびsetContentTypeメソッドです。setCharacterEncodingでは、「UTF-8」や「Shift_JIS」などのキャラクタセットを直接指定します。setContentTypeでは、MIMEタイプの指定につづけて「charset=UTF-8」のように指定します。以下の二つの指定方法は、全く同じ結果となります。charset=UTF-8");JSPでは、pageディレクティブのcontentType属性でMIMEタイプと同時にcharsetで指定します。%>setContentTypeやsetCharacterEncodingを複数回実行した場合には、あとから実行したものが優先されます。ServletからRequestDispatcherでJSPに処理をforwardするときに、ServletとJSPの両方でキャラクタセットを指定した場合、JSPの指定が使用されます。これらのキャラクタセットの指定はgetWriterメソッドでPrintWriterを取得する前におこなう必要があります。明示的なキャラクタセットの指定をしなかった場合には、javax.servlet.ServletResponseインターフェースのsetLocaleメソッドで指定されたロケールに対応したキャラクタセットが使用されます。ロケールとキャラクタセットの対応は、Webアプリケーションのデプロイメント記述子の<locale-encoding-mapping-list>要素で列挙しておく必要があります。送信についても、指定をおこなっていない古いプログラムに対応するための機能がWASにはあります。アプリケーションで「自動応答エンコーディング」を有効にしておくと、クライアントのブラウザが送信したAccept-Languageヘッダを元に、encoding.propertiesで指定されたキャラクタセットが使用されます。これらの全ての指定がおこなわれなかった場合のデフォルトとして、Javaシステムプロパティの「default.client.encoding」で指定されたキャラクタセットが使用されることも同様です。応答についても、これらの指定が全ておこなわれていない場合には、仕様にしたがって「ISO-8859-1」のキャラクタセットが使用されます。応答を送信する際に使用されるキャラクタセットは、Content-Typeヘッダでブラウザに直接送信されるのでIANAで定義されたキャラクタセット名を使用する必要があります。そのため、Shift_JIS系のキャラクタセットを使用する場合に不都合が発生することがあります。Java実行環境が持つ主なShift_JIS系のキャラクタセットとしては、SJIS、MS932、Cp943Cの三種類があります。IANAに登録されいる「Shift_JIS」というキャラクタセットは、JavaではSJIS(一部のバージョンのJRE/JDKではMS932)の別名になっています。SJISのコンバーターには、いわゆるメーカー外字(NEC特殊文字やIBM拡張文字など)を扱えないという問題があります。IANAに登録されいる「Windows-31J」というキャラクタセットは、JavaではMS932の別名となっていますが、一部のブラウザで正しく解釈されないという問題があり、また一部の文字についてJIS標準と異なるUnicodeマッピングがおこなわれている問題(いわゆる「波ダッシュ問題」)もあります。Cp943Cはメーカー外字も扱うことができ、Unicodeとのマッピングも他のJIS系の文字コードと一致しているのですが、IANAに登録された別名を持っていないため、Servletで応答キャラクタセットの指定に使用できないという問題があります。これらの問題に対処するために、WASではServlet/JSPの中で指定されたキャラクタセットと、使用されるJavaのコンバーターの対応を自由に設定できる機能があります。Root>/properties/converter.propertiesでおこないます。デフォルトではこのファイルに「Shift_JIS=Cp943C」と記述されてますので、Servlet/JSP中で「Shift_JIS」というキャラクタセットが指定された場合、実際の変換に用いるJavaのコンバーターとしてはCp943Cのものが使用されるようになっています。ブラウザやWebクライアント多くのブラウザでは、受け取ったHTML/テキストファイルの文字コードを判別するために以下の情報が使用されています(これ以外にコンテンツの中身を見て判断しているブラウザも存在します)。HTML文章中の<meta>タグでhttp-equivに指定されたContent-Typeのcharset属性ブラウザで正しい文字コードでコンテンツを表示させるためには、特に(1)のContent-Typeヘッダのcharset指定が重要です。多くの仕様がContent-Typeヘッダでの指定を強く推奨しています。javax.servlet.ServletResponseインターフェースのgetWriterメソッドで取得したPrintWriterを使用して応答をおこなう場合には、前節の方法で指定したcharsetが自動的に付加されます。getOutputStreamメソッドで取得したSerlvetOutputStreamを使用して応答をおこなう場合には、setContentTypeメソッドによる明示的な指定が必要です。「text/xml」のMIMEタイプでXMLの出力をおこなう場合には、Content-Typeヘッダのcharset属性が必須となります。2376で明言されています。XMLのデフォルトであるUTF-8以外の文字コードでXMLを送信する場合には、かならずcharset指定をおこないます。4627では規定されています。現行のブラウザの中には、この形式で応答を返した場合に日本語が正しく受け取れない実装が多くあります。そのため、「text/javascript」のMIMEタイプにcharsetで文字コードを指定した応答を返すことが多くなっています。アプリケーションサーバーJSPは、実行環境でJavaのソースファイルに変換され、Classファイルにコンパイルして実行されます。JSPをISO-8859-1以外で記述している場合、実行環境に対して文字コードの指定が必要となります。指定はpageディレクティブのpageEncoding属性で指定します。指定は、Javaのキャラクタセットを直接指定します。1.2以前は、文字コードの指定はコンパイル単位で指定することになっていました。そのため、たとえばA.jspがB.jspをincludeディレクティブで読み込んでいる場合、文字コードの指定はA.jspのみでおこなっていました。2.0以降ではファイル単位で指定するように仕様が変更されました。B.jspにもpageEncoding属性による文字コードの指定が必要になります。2.0以降では、Webアプリケーションのデプロイメント記述子の<page-encoding>要素で一括して指定することもできるようになっています。アプリケーションサーバーアプリケーションサーバーが動作しているJVMには、ローカルのファイルシステムから多くのファイルが読み込まれます。Javaのプログラムが格納されているClassファイルの中の文字リテラルはUTF-8に準じた文字コードで格納されています(null文字の表現など、若干異なる点はあります)。JavaソースコードからClassファイルを作成する際には、コンパイラにJavaソースファイルの文字コードを指定します。コンパイラとしてjavacを使用している場合には、-encodingオプションで指定します。SomeSourceFile.javaJavaの構成ファイルの標準形式はpropertiesファイルで、java.util.Propertiesクラスやjava.util.ResourceBundleなどで扱うことができます。propertiesファイル中には、US-ASCII以外の文字を書くことができません。これ以外の文字を記述する場合には、「\u」ではじまるUnicodeエスケープされた文字に変換する必要があります。この変換は、JDKに付属しているnative2asciiコマンドでおこなうことができます。ローカルのテキストファイルをJavaプログラムから直接読む方法としては、java.io.FileReaderを用いる方法とjava.io.FileInputStreamの上に構築したjava.io.InputStreamReaderを用いる方法があります。EEアプリケーションでは使用するべきではありません。FileReaderはJVMのデフォルトの文字コードが使用されます。EEは、JVMの実行されているプラットフォームに依存する書き方をするべきではないとされています。プログラム作成者が明示的に文字コードを指定することができるInputStreamReaderを使用するべきです。最近ではDOM/SAXやJAX-Bなどを用いてXMLファイルを読み書きすることも増えています。このようなAPIでは、通常はXMLファイルの読み込み時に文字コードを指定する必要がありません。XMLファイルは、その仕様で、冒頭のXML宣言で文字コードを明示的に指定することが定められているからです(XML宣言がない場合にはUTF-8ないしUTF-16でなければいけません)。JavaのXML関連のAPIは、このXML宣言にしたがって適切に文字コードを判別します。DBサーバー/メッセージングシステムJavaからDBサーバーへアクセスするための仕様JDBCやメッセージングシステムにアクセスするための仕様JMSには、文字コードに関する既定がありません。そのため、JavaのUnicodeの文字列をDBで扱う文字コードに変換するための指定は、JDBCドライバなどの設定に一任されています。DBの文字コードが非Unicode系の場合、他の部分と同様に正しい文字コードの指定をおこなっていないと、文字化けが発生する可能性があります。以上のような、文字コードの変換が行われるところ、すなわち文字化けの原因となる箇所をまとめると以下のようになります。(図2-6)クリックして大きなイメージを見る文字化けが発生した際には、化けた文字によって調べる場所を特定します。JSP内部に記述した文字にのみ文字化けが発生している場合にはJSPのソースファイルの文字コードの指定を、ブラウザから送信された文字列にのみ文字化けが発生している場合にはブラウザから受信する際の文字コード指定を調べます。ページ全体が文字化けしている場合には、ブラウザへ送信する際の文字コード指定が正しく設定されているかを調べます。Webサービスでは、要求、応答はXMLをベースとしたSOAPメッセージとして送受信されます。EEにおけるWebサービスでは、JAX-WSやJAX-RPCのAPIにより、開発者はXML、SOAP、HTTPなどの低レベルのプロトコルを意識する必要はあまりありません。ここでは、文字コードの観点から見てみます。XMLのエンコーディングXML文書のエンコーディングは、図3-1の1行目の記述のようにXML宣言のencodingの値で指定します。XML宣言は必須ではありません。Webサービスのように、HTTPなどの下位のプロトコルでXML(SOAP)メッセージをラップし、下位プロトコルでエンコーディングを指定する場合にはXML宣言は省略可能です。下位プロトコルでラップする場合には、下位のトランスポート・プロトコルでのエンコーディングの指定を適切におこなうことが重要です。Webサービスのトランスポート・プロトコルは必ずしもHTTPのみに制限されるわけではありませんが、一般に広く使用されているSOAP/HTTPの場合について説明します。SOAP/HTTPの場合、HTTPのエンティティー・ボディーのデータとしてSOAP(XML)メッセージが送信されます。Webサービス通信のHTTPデータをキャプチャーした例を示します。SOAPメッセージのエンコーディングは、XML宣言ではなく、ブラウザとの通信におけるエンコーディングの指定と同様に、HTTPヘッダのContent-Typeのcharset属性で指定することが重要です。Profile)Webサービスのメリットは、異なるプログラム言語や、異なる製品ベンダー間での相互接続が可能なことです。WSDLやSOAPの仕様に準拠しても、必ずしも相互に通信が行えない場合がありました。Profileと呼ばれる推奨事項を規定しています。Profileの管理はOASISに移管されています。Profileに準拠することが重要です。Profileに準拠することにより、多くの相互接続の問題を避けることが可能です。1.2を対象としています。Encodings」のセクションにSOAPメッセージの文字エンコーディングに関連する下記の推奨事項があります。SOAPメッセージのエンコーディングとしては、UTF-8またはUTF-16を使用する。(MUST)SOAP/HTTPの場合、XML宣言のエンコーディング指定ではなく、HTTPヘッダのContent-Typeのcharsetの値でSOAPメッセージのエンコーディングを指定/判定する。(MUST)SOAP受信エンジンは、XML宣言のエンコーディング指定は無視する。EEのアプリケーションにおいてWebサービスを実装する場合には、JAX-WSまたはJAX-RPCを使用して、Webサービス・リクエスターやWebサービス・プロバイダーを開発することで、リモート呼び出しを意識することなく容易に開発をおこなうことができます。JAX-WSは、JAX-RPCの後継の仕様になりますので、新規にWebサービスを開発する場合には、JAX-WSを使用するのが一般的です。WASがWebサービスのリクエスターとなる場合には、JAX-RPC、JAX-WSのAPIによらず、デフォルトで、UTF-8のエンコーディングでSOAPメッセージを送信します。JAX-WSを使用する場合には、下記の図3-3のコード例に示すように、JAX-WSのプロキシ・インスタンスが持つRequestContextに、"CHARACTER_SET_ENCODING"のプロパティ・キーと、"UTF-16"や"Shift_JIS"のようなキャラクタセットのプロパティ値の組を設定することで、SOAPメッセージのエンコーディングを指定します。この場合には、HTTPヘッダのContent-Typeヘッダのcharset属性の値にも、指定したキャラクタセットの値が設定されます。helloserviceproxy.sayHello(person);JAX-WSを使用し、Webサービス・プロバイダーを開発した場合、WASのWebサービス・エンジンが受信したSOAPメッセージのHTTPヘッダのContent-Typeのcharset属性の値により、SOAPメッセージのデシリアライズ(XMLからJavaオブジェクトへの変換)をおこないます。したがって、受信するメッセージのエンコーディングがUTF-8かUTF-16かShift_JISかなどをユーザー・コードで意識する必要はありません。応答のSOAPメッセージの文字コードは受信した文字コードでシリアライズ(JavaオブジェクトからXMLへの変換)されます。WASのJAX-RPCエンジンは、UTF-8とUTF-16の文字コードでの変換のみサポートしています。UTF-16の文字コードで、SOAPメッセージを送信する場合、スタブ・インスタンスに、下記の図3-4に示すようにプロパティを設定します。Unicode(UTF-8、UTF-16)以外でのSOAPメッセージの送受信についてWASでJAX-WSを使用した場合には、Shift_JISなどUnicode以外のエンコーディングでSOAPメッセージの送受信をおこなうことができます。WASのWebサービスの送信(リクエスト、レスポンスとも)は、ブラウザへのHTTPレスポンスの応答とは異なり、Content-Typeのcharsetの指定とコンバーターを別々に指定することができません。したがって、JAX-WSのRequestContextの"CHARACTER_SET_ENCODING"のプロパティ値として、"Shift_JIS"を指定した場合、コンバーターとしても、IANAのShift_JIS(SJISと同じメーカー外字を含まないエンコーディング)が使用されます。「1」のようなメーカー外字文字が含まれるデータをWASから送信する場合、Shift_JISの文字エンコーディングには変換できませんので、XMLの数値文字参照を使用したSOAPメッセージが送信されます。下記の例では、データとして、「start~1iI(株)end」を送信しています。Shift_JIS(SJIS)に変換できない文字を送信した例<Name>start&#65374;&#9312;&#8560;&#8544;&#12849;end</Name>JAX-WSのRequestContextの"CHARACTER_SET_ENCODING"のプロパティ値として、"Cp943C"や"MS932"を指定した場合は、Cp943CやMS932のコンバーターを使用してメーカー外字も変換されますが、HTTPヘッダのContent-TypeにIANAで規定されていないcharset=Cp943Cやcharset=MS932の値が指定されます。サービス・リクエスターとサービス・プロバイダーの双方がWASの場合には、"Shift_JIS"、"Cp943C"などいずれのコンバーターを使用した場合も、相互通信をおこなうことができます。異なるベンダーの製品/Webサービス・エンジンと通信をおこなう場合には、注意が必要です。エピローグN君:「なるほど、よく理解できました。ありがとうございます。」Vさん:「N君、君のコードは何が問題なのか分かるかい?」N君:「FileReaderを使っていたのがまずかったんですね。InputStreamReaderでキャラクタセットを指定しなきゃいけなかったんだ。」Vさん:「その通りだよ。これで文字化けには悩まされなくて済むね。Serverの評価版・開発者版ダウンロードコメントコメントを登録するにはサインインあるいは登録してください。