2007 年 4 月 8 日 23 時 58 分

文字化けが発生する原因を探る


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


一番大きな問題点は、日本語が通らないことだ。
これには、HTTP, HTML, Servlet, Struts の 4 つの規格が、
それぞれ何かしらの処理で関わっているため、
その原因を正しく理解するのは少し難しい。

そのため、フォームの送信を順番に追いかけ、
どのような流れで日本語が処理されているか考えてみよう。

まず、データを送信するフォームがどうなっているか、
http://localhost:8080/struts-test/filter/edit-info.do
のソースをブラウザで開いて確認する。

以下に、重要な部分だけを引用する。

    <form id="filterInfoForm" method="post"
            action="/struts-test/filter/update-info.do">

        <input type="radio" value="allow"
                name="info.defaultAction" />許可
        <input type="radio" value="deny" checked="checked"
                name="info.defaultAction"  />拒否

        <input type="text" name="info.note"
                size="60" value="" />

        <input type="submit" value="更新" />

    </form>

符号化方式の指定がないので、フォームのデータは、
「application/x-www-form-urlencoded」
コンテントタイプを使って送信される。

この符号化では、最初に入力項目の名前と値を、
URI のパーセントエンコーディングを使って、
URL で予約されていない文字だけの表現に変形した後、
= 文字 と & 文字を使って、名前=値&名前=値&名前=値
という形に連結し、最終的に ASCII 文字列へと変換する。

なぜ符号化に URL が出てくるのかといえば、
もしこれを GET メソッドで送信する場合、
単に送信先 URL の後ろに ? をつけ、
上記の ASCII 文字列を連結するだけで済むからである。
application/x-www-form-urlencoded は GET 向けなのだ。

ただし、上記フォームでは method 属性が post であるため、
このフォームのデータを送信する際には、
HTTP の POST メソッドが使用される。

POST メソッドの場合は、データを URL に含めず、
別途本体(エンティティボディ)として送信する。
そこでは任意の 8 ビットのバイナリデータが扱えるため、
特に URL で使える表現にこだわる必要はないのだが、
application/x-www-form-urlencoded を使う場合は、
その流儀に従って、ASCII 文字列へと変換する必要がある。

さて、application/x-www-form-urlencoded には、
エンコーディングやコードの問題は生じない。
そもそも使う文字が ASCII の範囲だけに限られるため、
1 文字 = 1 バイトが成立しているからである。

そのため、ASCII の範囲に含まれない文字を、
application/x-www-form-urlencoded で符号化する挙動は、
「未定義となっており、ブラウザに依存している」

この事実は、意外に知られていない。

規格上は、application/x-www-form-urlencoded では、
ASCII 以外のデータの送信を想定しておらず、
そういったデータを扱うためには、multipart/form-data
という異なる符号化を使う必要があるのだ。

とはいうものの、一般的な Web ブラウザでは、
application/x-www-form-urlencoded において、
ASCII 以外の符号化を扱うような拡張がなされており、
その方法については統一されているため、
今回はその拡張に基づいて考えることにする。

となると、フォームの「更新」ボタンが押され、
サーバにデータが送信される前の符号化処理において、
何かしらのトラブルが起きている可能性はないだろうか。

調べてみよう。

上記のフォームにおいて、
既定の処理:「拒否」、備考「内向きフィルタ」として
フォームを送信する場合、どのようなデータになるか考える。

Internet Explorer や Firefox の場合、
以下のような ASCII 文字列に変換されるはずである。

info.defaultAction=deny&info.note=%E5%86%85%E5%90%91%E3%81%8D%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF

info.defaultAction, deny, info.note は、
元々 ASCII で表現できる範囲の文字だし、
URL で予約されている文字でもないため、
そのままの形で表現されている。

「内向きフィルタ」は、
%E5%86%85%E5%90%91%E3%81%8D%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF
という長い文字列になっている。

これをよく見てみると、UTF-8 のようだ。
「内」という漢字を UTF-8 で表現すると、
「E5」「86」「85」の 3 バイトの並びで表現されるのだが、
これをパーセントエンコーディングすると、
「%E5%86%85」となるからである。

では、何故 UTF-8 なのだろうか。

HTML では、FORM タグの accept-charset 属性を指定すると、
送信時の文字エンコーディングを強制することができる。
もし、accept-charset 属性が指定されない場合は、
文書自身の文字エンコーディングが代わりとして使われる。

この属性は、本来は multipart/form-data 用なのだが、
ブラウザによってはこの属性の解釈を、
application/x-www-form-urlencoded にも適用する。

今回、JSP で UTF-8 として出力しているので、
生成された HTML も UTF-8 となっている。

そのため、パーセントエンコーディングの際に、
UTF-8 が使われてエスケープされたということだ。

まずは、サーバにデータを送信するまでの間に、
日本語文字列が破損したわけではなさそうである。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.