2010年5月5日水曜日

Servletでのパラメータ解析(3)

HttpプロトコルのGETリクエスト時に、パラメータの変数値などに日本語が含まれた場合のパラメータ解析方法について「Servletでのパラメータ解析について(2)」のようにするれば、各ブラウザでの日本語文字コード変換に合わせてパラメータ値を取得が可能であるとお話しました。
しかし、この方法で上手く行かない場合があることが判明しました。
ブラウザからURLを指定時に、URLにパラメータを指定してGETリクエストを発行する以外の方法として、以下のようなHTMLによりGETリクエストを発行することが可能です。

<html>
<body>
<form name="f1" action="http://localhost:8080/TestServlet/Test" method="get">
<input type="text" name="p1">
<input type="submit" nput>
</form>
</body>
</html>

このようなHTMLでは、テキスト入力された日本語文字列をどのような文字コード(ShiftJIS,EUC,UTF8など)で出力するか指定することができます。

例えば、
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
と言うタグがHTML上に存在していれば、入力フィールドで指定された日本語文字列は、ShiftJISに変換され、URL上のパラメータとして指定されます。
Servletでのパラメータ解析について(2)」では、各ブラウザに合わせて固定の文字コードに変換するようになっていますが、同じブラウザであっても、HTMLからGETリクエストを行った場合には、HTMLの書き方によって、文字コードがShiftJISになったり、UTF-8に成ったりします。

ブラウザからのURL指定によるGETリクエストと、HTMLからのGETリクエストでの違いがあれば、何らかの対応も可能でしたが、Servletで渡されるHttpServletRequestパラメータからは、全く違いがないために処理を変えることが不可能であることが判明しました。Webアプリケーションでは、各社ブラウザでの動作の違いによって実装が面倒ですが、なんでこんな仕様になってしまったのでしょうか?標準化して欲しいものです。

対応方法としては、ブラウザからの直接GETリクエスト指定時と、HTMLからGETリクエスト時に渡すパラメータで、文字コードを指定するなどの対応が必要かと思います。

2010年5月3日月曜日

Servletでのパラメータ解析(2)

「Servletでのパラメータ解析について(1)」では、ブラウザから「http://localhost:8080/TestSevlet/Test?p1=あいう」などのように直接URL指定を行った場合に、後続のパラメータの処理がブラウザによって異なることをお話しました。
各ブラウザの動作を、プロトコルヘッダのUser-Agentの特定のパラメータから引き出せないか、Webなどで確認したところ、このUser-Agentは、各メーカーが勝手に定義しており定義などないようです。ただし、各ブラウザメーカーともに、他社のブラウザと同等の機能を提供したいので、あまり、突飛なUser-Agentは存在していないようです。
したがって、User-Agentからブラウザの種別を判別して、String命令で各ブラウザにあわせて変換する文字コードを個別に実装するしかなさそうです。

なお、本日、Operaブラウザでの動作を確認しました。すると「http://localhost:8080/TestSevlet/Test?p1=あいう」などと指定するとChromeや他社ブラウザとも異なり、JISコードで展開しているようです。
例えば、上の例をOperaブラウザから入力すると
「http://localhost:8080/TestSevlet/Test?p1=%1B$B$%22$$$&%1B(B」と展開れます。文字列の最初にある「%1B$B」は、これから2バイトの漢字を開始するための指示、最後の「%1B(B」は、2バイト漢字が終了したことを示しています。途中も文字列は、JIS漢字コードとなります。
したがって、OperaブラウザからサーブレットにGetメソッドが送信された場合には、

    String p = req.getParameter("p1");
    v = new String(p.getBytes("iso-8859-1"),"iso-2022-jp");
    
と処理する必要があります。

※ これは、Operaのバグと思われますが、上記の仕様だと、上のp1パラメータとして「あいう」などと入力した例を見てください。展開されたURLは次のようになっています。
   「http://localhost:8080/TestSevlet/Test?p1=%1B$B$%22$$$&%1B(B
このURLでは、パラメータの途中に「&」が現れています。このため、Tomcatでは、「&」は、パラメータ指定の終了を示すので「あいう」のパラメータ値をサーブレットに渡すことは不可能となってしまいます。

なお、いままでの話を整理すると、

① User_Agentから、呼び出しブラウザがChromeだったら、
    String p = req.getParameter("p1");
    v = new String(p.getBytes("iso-8859-1"),"UTF-8");
としてパラメータ値を取り出す。

② User_Agentから、呼び出しブラウザがOperaだったら、
    String p = req.getParameter("p1");
    v = new String(p.getBytes("iso-8859-1"),"iso-2022-jp");
としてパラメータ値を取り出す。

③ それ以外だったら、
    String p = req.getParameter("p1");
    v = new String(p.getBytes("iso-8859-1"),"Windows-31J");
としてパラメータ値を取り出す。

となります。ただし、Operaのようにパラメータの日本語処理で問題が発生することも想定されますので、パラメータは極力英語とすべきでしょう。

Servletでのパラメータ解析(1)

Servletに、パラメータを指定する方法として、FORMタグ無いの変数を設定しておきHTMLでSubmitでPOSTメソッドやGETメソッドで指定する方法が一般的です。
このほかに直接、Webブラウザで
http://localhost:8080/TestSevlet/Test?P1=aaaa
等と指定する方法があります。この指定では、P1と言うパラメータに「aaaa」を指定することになります。
ところが、Chromeブラウザのみ変数の値を「日本語文字」あるいは「漢字」にする(例えば:http://localhost:8080/TestSevlet/Test?p1=あいう)と正しいパラメータが取得できないことが判明しました。文字化けが発生してしまいます。

サーブレット側のコーディングは、通常のサーブレットプログラミング同様に以下のようなコーディングとなっているだけです。
  String p = req.getParameter("p1");
  v = new String(p.getBytes("iso-8859-1"),"Windows-31J");

そこで、Chromeとその他のブラウザgetParameterで得られた変数pを使ってp.getBytesによって取り出した結果を調べました。その結果、Chrome以外のブラウザでは、取得したバイト列がシフトJISである一方、Chromeでは、UTF-8であることが分かりました。

実際、
  String p = req.getParameter("p1");
  v = new String(p.getBytChes("iso-8859-1"),"UTF-8");
とするとChromeでは正常にパラメータが取得できました。

コーディング上で見ると、RequestパラメータからgetHeaderメソッドを使って”User-Agent"のブラウザ情報を取得して、この情報がChromeブラウザであることを示していたら、変換を"UTF-8"にするのが簡単でしょう。

なお、私が調べてブラウザでのUser-Agentは次のようになっていました。

Chrome 4.1.249
Agent=Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1064 Safari/532.5

FireFox 3.5.2
Agent=Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 GTB7.0 ( .NET CLR 3.5.30729)

IE8
Agent=Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB6.3; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)

Safari 3.0.3
Agent=Mozilla/5.0 (Windows; U; Windows NT 5.1; ja) AppleWebKit/522.15.5 (KHTML, like Gecko) Version/3.0.3 Safari/522.15.5

今回の現象面からみると、Chromeかどうか判断できれば、ChromeのみUTF-8にするだけでよいが、その他、UTF-8が標準のブラウザの場合に同等の処理が必要となります。上に掲載したUser-Agent文字列などから、言語体系が分かるのかもしれません。(User-Agent文字内には、en-US,ja等と言った言語圏を示す文字列が存在しています。
明日は、ゴールデンウィークなのでUser-Agent文字列について調べようと思います。

続きは、Servetでのパラメータ解析について(2)をご覧ください。