DeleGateによる日本語文字コード変換

Yutaka Sato
2006年12月30日

DeleGate 9.4.2 では、10年ばかり御無沙汰気味だった日本語文字コード変換機能 について見直しを行い、以下の拡張を行いました。

日本語コード変換関係のドキュメント類も、だいぶ古くなって内容が現状と 不一致だったり、散逸もしてしまった感じなので、この際にちょっとまとめて みることにしました。

関連: DeleGateにおける日本語文字コードおよびMIMEコード変換機能

■ 文字コード変換リバースプロキシとしてのDeleGate

クライアントとサーバの間の文字コードや文字セットの不整合によって、 いわゆる文字化け等の問題が発生します。 そのような問題を、サーバ側で回避するために、DeleGateを 「文字コード変換を行うリバースプロキシ」として使用することができます。

■ リバースプロキシの構成

いわゆる「リバースプロキシ」は、以下のように構成されます。
                               [http://www.com.dom:80]       [http://serv:8880]
    client -------------------> DeleGate -------------------> HTTP-server
この例では、DeleGateはクライアントから見て http://www.com.dom という サーバとして動作していて、 その実際のコンテンツは http://serv:8880 にあり、DeleGateにより ゲートウェイ(リバースプロキシ)されているという形になっています。 これは、DeleGateの「マウント」機能により、以下のようにして実現できます。
[ DeleGateの起動コマンド ]
delegated +=httpd.cnf

[ httpd.cnf ファイルの内容 ]
-P80
SERVER=http
MOUNT="/* http://serv:8880/* vhost=-www.com.dom"
URICONV=where:any
PERMIT="http:serv:*"
RELAY=no

この例では、設定が多少複雑ですので、必要なコマンド引数を "httpd.cnf" というファイルに書いて、"+=" で 参照しています(その一部あるいは全てをコマンド引数として直接書いても 構いません)。 それぞれの引数は、以下のような指定をしています。
  -P80 SERVER=http
このDeleGateは、80番ポートでHTTPサーバとして動作する
  MOUNT="/* http://serv:8880/*"
このDeleGateが受け取る全てのURL を http://serv:8880 に転送する
  vhost=-www.com.dom
このDeleGateが www.com.dom として参照された場合にのみ、 このMOUNTを適用する
(転送先のサーバが唯一である場合には省略可)
  URICONV=where:any
このDeleGateが認識できる全てのテキスト型応答データに、 このMOUNTを適用する
  PERMIT="http:serv:*"
任意のクライアントからのアクセスを許可、ただしアクセス先は http://serv に限定する
  RELAY=no
MOUNTされたサーバへの中継以外は拒否する

■ 文字コード変換プロキシ

DeleGateに文字コード変換を行わせるには、 CHARCODE という オプションで、変換先の文字コードを指定します。例えば、 CHARCODE=EUC-JP と指定すると、 サーバからのレスポンス中の日本語テキストの文字コードは EUC-JP に変換されて クライアントに転送されます。

    client <----- EUC-JP ------- DeleGate <===== Japanese ==== HTTP-server
           ------ EUC-JP ------>          ------ EUC-JP ----->

これが、DeleGateが当初から持っていた機能なのですが、これだけですと、逆に クライアント側からサーバへ、日本語テキストを送信する場合に問題が起こります。 典型的には検索エンジンへの検索語の送信や、掲示版への書き込みなどです。

まず、一般にクライアントは、サーバから返されたレスポンスの文字コードに 合わせてリクエストの文字コードを決定して送信します。 ここで、DeleGateによってレスポンスがサーバの文字コードと異なるコードに 変換され、クライアントがそのコードに合わせてリクエストを送信し、 サーバがその文字コードを認識してくれないと、文字化けが起こります。 また、一度もサーバからの応答を得ていない状態で、クライアントから日本語 を送信する場合には、文字コードはクライアントの一存で決められ、 サーバの期待する文字コードになっていない可能性があります。

このことから、DeleGate は 9.0.3以降、 上の例のように CHARCODE オプションによってサーバからのレスポンスに対して 文字コード変換を行った場合、サーバからのレスポンスの元の文字コードを Cookieに入れてクライアント送り、それを 次のリクエスト時に返送してもらうことで、サーバの文字コードを知り、 サーバ向けの文字コード変換を行うようになっています (ただし、サーバ向けの文字コード変換を明示的にCHARCODEオプションで 指定した場合(†1)には、そちらが優先します)。

ただこの方法には、サーバからの文字コードを一度も見ていない段階(あるいは それを記憶したCookieがクリアされた状態)でクライアントからのリクエストを サーバに中継する場合には、必要な文字コード変換が行えない、 という問題があります。

■ 文字コード変換リバースプロキシの構成

幸い、リバースプロキシの場合にはサーバが特定されていて、そのサーバが受理 できる文字コードが確定していることが普通です。このため、その文字コードに 固定的に変換してやるように、リバースプロキシを設定してやれば、より 確実に必要な文字コード変換を行うことができます。 上の例のように構成されたリバースプロキシ上で、文字 コード変換を行うとしましょう。例えば、 クライアントと http://www.com.dom のサーバとして動作している DeleGate との間では、テキストデータを UTF-8 でコード化して通信し、 DeleGate とhttp://serv:8880 サーバとの間では EUC-JP でコード化するものとします。
                               [http://www.com.dom:80]       [http://serv:8880]
    client <----- UTF-8 ------- DeleGate <=================== HTTP-server
           ===================> DeleGate ----- EUC-JP ------>

クライアント向けのレスポンス中の日本語コードの変換は、 CHARCODE=UTF-8 と指定します。 一方、サーバ向けのリクエスト中の文字コードの変換は CHARCODE=charcode:tosv のように指定します (†1)

[ httpd.cnfの内容 ]
-P80
SERVER=http
MOUNT="/* http://serv:8880/* vhost=-www.com.dom"
URICONV=where:any
PERMIT="http:serv:*"
RELAY=no
CHARCODE=UTF-8
CHARCODE=EUC-JP:tosv

最近のブラウザは、任意の文字コードに対応しているものが多いので、 サーバが受理するコードだけが限定されるという状況ならば、この例の クライアント向けの文字コード変換は不必要であるかも知れません (†2)

ただし、一方を Unicode に、他方を JIS の文字セットに固定することで、 Unicode と JIS の文字セットの変換を行う際に、変換表を限定することで、 変換表に無い文字がコード変換時に自動的に除外されるという効果があり、 これが有効である場合が考えられます (現状のDeleGateでは、この変換の際に JIS X 0213 文字セット中の文字 が自動的に除外されます)。 また、EUC-JPあるいはISO-2022-XX系から Shift_JIS への変換でも同様です (補助漢字が自動的に除外されます)。


(†1) CHARCODE=charcode:tosv という指定で、 サーバ向けのコード変換を指定できるようになったのは、DeleGate/9.4.2 からです。 それ以前の版では、 MOUNT="vURL rURL FTOSV=-cc-EUC-JP" のように、マウントのオプションとして指定する方法があります。
(†2) ちなみに、DeleGate.ORG の サイト内検索エンジン として使用している FreyaSX は、 受理できる文字コードが EUC-JP のみであるため、リバースプロキシとしての DeleGate において、このサーバ向けの文字コード変換(のみ)を行っています。
(†3) Windows VISTA上のクライアントの文字セットがサーバ側で問題を起こす可能性が あるという話を聞きますが、そもそもサーバからクライアント向けの文字コードを Shift_JISにしてやれば、普通のブラウザならばサーバ向けの文字コードも Shift_JISになり、Shift_JISで表現できない文字コードを送っては来ないと 考えられるので、それだけで問題を回避できるとも考えれるのですが、定かでは ありません。なにやら拡張されたシフトJISというのがあって、それをShift_JIS を名乗るサーバに送ってくる可能性も考えられますし、サーバの文字コードを 無視してUTF-8などで送ってくるかも知れません。 あるいは、Accept-Charset あたりでネゴをしてくれて、何も問題を起こさない かも知れません。


■ 文字コード変換プロキシの問題点

DeleGateによる(プロキシによる)文字コード変換は、規約の設計上原理的に、 あるいは DeleGateの実装上の不完全性から、完全ではありません。

□ 日本語テキストの検出・文字コードの判別の問題

 サーバ向けの文字コード変換は、リクエストヘッダのURLの中、 およびPOSTリクエストのボディ(†4)の中に、 %XX%XX あるいは &#nnnnn; (Unicode) のようにエンコードされた日本語テキストに対して適用されます。 ここで、%XX%XX の形式でエンコードされたものについては、 それが日本語テキストであるのか、またどの日本語コードで表現され ているのかは、どこにも指示されていません。このため一つの文字列が、例えば EUC-JP としても Shift_JIS としても解釈可能な場合、 変換結果が期待通りにならない場合があります (†5)

□ 日本語文字コード間の文字セットの不整合の問題

 DeleGate/9.4.2 現在、DeleGate がサポートしている(と思われる)日本語の 文字セットは、JIS X 0201, JIS X 0208, JIS X 0212 です。 文字コードは、ISO-2022-JP, EUC-JP, Shift_JIS および UTF-8 です。 ここで、文字コードの間で、表現できる文字セットは、それぞれに異なります。 例えば「半角カナ」は、ISO-2022-JP には含まれていないので、例えば Shift_JIS から ISO-2022-JP に変換すると、半角から全角に変換されます。 例えば EUC-JP でコード化されている「補助漢字」は、Shift_JIS に変換すると 失われます。 変換先のコードが存在しない場合その文字は(デフォルトでは)ゲタ記号(〓) にしています (†6)

□ Unicodeの問題

 UTF-8 (Unicode文字セット) と JIS文字セットのコードと間の変換では、 Unicode.Org から配布されている変換表を、DeleGate の起動時に (CHARSET オプションが指定されている場合)、 DeleGate.ORG 経由でダウンロードして、用いています。 DeleGate/9.4.2 現在、用いているのは、OBSOLETE と分類されている変換表で、 これは JIS X 0213 を含んでいません。 今後、必要性があると判断された段階で、実装したいと思っています。 (Unicode については、よく言われているように、JISコードとの間の変換が 一意でないという問題もあります)


(†4) POSTリクエストのボディに対する文字コード変換が適用されるのは、 デフォルトでは、そのデータ型(Content-Type)が x-www-form-urlencoded の場合のみです。 これ以外の型にも適用するには、 HTTPCONF=post-ccx-type で指定して下さい。
(†5) サーバからのレスポンスの場合にも同じような問題はありますが、レスポンス の場合、入力の文字コードが Content-Type や META タグで指定されていることが 多く、確実に推定するために十分な文字数がある場合が多いので、入力コードの 判別を誤ることは稀です。
(†6) CHARMAP=jis:0000/2222 などとして、マップ先のない文字を、〓 (JISコードで0x222E) 以外の 文字にマップすることができます。

PageViews: 1,397 hits / 141 nets

以下は、DeleGate の参照マニュアル Manual.htm からの抜粋です。

DeleGate reference manual version 9.9 / CHARCODE

[CTX] [ALL]
CHARCODE parameter* ==  CHARCODE=[inputCode/]outputCode[:[tosv][:connMap]]
        outputCode  ==  charCode
          charCode  ==  iso-2022-jp | euc-jp | shift_jis | utf-8 | us-ascii |
                               JIS | EUC | SJIS | UTF8 | ASCII | guess
           connMap  ==  [ProtoList][:[dstHostList][:[srcHostList]]]
                    --  restriction: applicable to HTTP, FTP, SMTP, POP,
                                     NNTP, Telnet, Tcprelay
                    --  default: none

DeleGate reference manual version 9.9 / CHARMAP
[CTX] [ALL]
CHARMAP parameter*  ==  CHARMAP=mapType:charMap[,charMap]*[:tosv]
           mapType  ==  ascii | ucs | jis | ucsjis | jisucs
           charMap  ==  inCharCode1[-inCharCode2]/outCharCode2[-[outCharCode2]]
          charCode  ==  hexa-decimal code | single ASCII character
                    --  default: none