関連リンク集:/「Javaの哲学」の恥かき/「Javaによるテキスト処理」の恥かき/「モア・サーブレット」の恥かき/恥かきのホームページ/JavaBeginner/javacのエラーメッセージ/UIDefaultsのkeyとデフォルト値/comp.lang.java.guiのFAQ/


★2006年より、Javaプログラミング関連記事の新規掲載場所を[Javaの手帖]に統一しました。



[    ][HOME][NEXT](シングルトンと同期化)

FORMデータとエンコーディング



「コア・サーブレット」(原書)は昨年(2003)8月に第二版が出まして、大幅に改定&増補されました。私(岩谷)自身の経験も含めて、ここに、有意義なメモを作っていこうと思います。

(1)FORMデータ, GET/POST, multipartの問題

第一版16章には、FORMデータをGETでなくPOSTで送るためにはENCTYPE="multipart/form-data"を指定すればよい、と書かれていますが(訳書p371)、これは間違いです。HTML4を正しく実装したブラウザなら、METHOD="POST"を指定しなければmultipart/form-dataでもデータはGETで送られます。ただし、METHOD="POST"を指定しただけでENCTYPE="multipart/form-data"を指定しなければ、送られるデータの形式はapplication/x-www-form-urlencodedとなり、すべてのFORM入力データがURLエンコードされたワンデータとしてリクエストボディになります(URLにくっつかない)。

ですから、POSTでかつ、複数のFORM入力データをマルチパート(複数データ)として送るためには、METHOD="POST"とENCTYPE="multipart/form-data"の両方を指定しなければなりません。そしてこのときにかぎり、UnicodeでBasic Latin(ISO-8859-1)の文字(UTF-8で1バイトになる文字)にかぎりURLデコードが行われますが(というか、もともとURLエンコードされないデータが送られる)、UTF-8の16ビット文字(24ビットで表現される文字)は力のようなエンコード化文字列(HTML規格のいわゆる文字実体, character entity)で表されます。そしてしかしこれらを、ブラウザは正しい文字で表示します。

しかし一方、Javaサーブレットの規格は、POSTでもデータ形式がx-www-form-urlencodedでなければrequest.getParameter()しない/できない、と(なぜか)言い張っていますから、サーブレットで使うためのFORMではMETHOD="POST"とENCTYPE="multipart/form-data"の両方を指定するとパラメータがnullになってしまいます。ResinもTomcatもこの規格には忠実です。

まとめとして繰り返しますと、ENCTYPE="multipart/form-data"だけならデータはGETで送られます。データ形式は当然application/x-www-form-urlencodedです。また、METHOD="POST"だけを指定すると、データ形式はワンデータのapplication/x-www-form-urlencodedとなります。いずれもURLデコードされていない、ナマのリクエストデータです。URLデコードされた(ないし元々URLエンコードされていない)マルチパートデータ(ただしUTF-8,16bit文字は力のようなエンコード化文字列)が欲しければmultipart/form-dataとPOSTの両方を指定しますが、これはサーブレット用には使えません。サーブレットはあくまでも、元の文字コードや文字実体そのものではなく、URLエンコードされたデータ(すなわち7bit ASCII文字列)をFORMデータ等として要請するのです。ここが、どうやら、カンジンな点のようです。

そこで、いずれにしてもサーブレット側では、力権...のようなエンコード化文字列,“文字実体”ならばそれを、JavaのStringすなわち, 21147 27177 ...という16ビット値の列に変換しなければ、内部的なテキスト処理等はできません。しかしFORM HTML側でcharsetを明示的/現物的(=本文文字として)に指定し、サーブレット側でもリクエストのエンコードをやはり明示的に指定すれば、HTML4の規格のおかげで、文字実体ではなくUTF-8のコード値そのものが…もちろんURLencodeされて…送られますし、個々のパラメータへの切り分けと8ビット部分のURLデコードはgetParameter()側でやってくれますから、たいへん気が楽です(なんといってもJavaのStringがUnicodeだからですね!)。

単純な結論としては、FORMのあるHTMLの上に日本語の本文があるなら、その文字のエンコード(文字コード種)でrequest.setCharacterEncoding()をgetParameter()の前に呼んでおく。現物的に日本語本文がなく、しかしFORMデータがISO-8859-1(ASCII)とはかぎらないときは、request.setCharacterEncoding("UTF-8")を呼んでおく、でよいと思います。そうするとgetParameter()は単なるURLデコードにとどまらず、完全なJavaのString(Unicode/UTF-8列)を作ります。ただしブラウザによっては、このスタンダードから逸れているものの可能性もあります(現実に私は知りませんが)。いずれにしても、私の苦い経験から言えば、FORMはあくまでも(可搬性を尊ぶ)Webアプリ側のリソースですから、アプリを作る側に主体性があります。

(互いに異なるMETA ... charset= ...の指定と現物日本語本文の両方が、FORM HTML上にあるときは、送信でもcharsetが優先され、日本語本文のほうは文字化けします。)


なお(上の説明文は非常にややこしいので)、ご参考までに、日本語本文のない、英語だけのHTML上で、「ローカル権力」という計6文字の日本語をFORM入力したときの、サーブレット側の結果を、下表に示します(Mozilla 1.6/Linux/Resin 2.1.12)。これらから、次のことが自覚できます:

  (1)request.getParameter()にパラメータのエンコード情報(文字コード情報)を教えてやることは必須である。

  (2)しかしFROM HTML側に正しいcharset情報がないと、文字ではなくいわゆるHTMLの文字実体(character entity)が送られてしまうことがある。それらはHTML上では正しい文字で表示されるが、サーブレット内部でテキスト処理等に使うためには、JavaのStringに変換しなければならない。たとえば、こんなコードを使って。

【日本語本文のない(英語のみの)FORM HTMLの場合】

●サーブレット側にrequest.setCharacterEncoding("UTF-8");があると…
FORM HTML上の
METAタグ
ブラウザによる
文字コード認識
サーバに送られた
パラメータ文字列
なしISO-8859-1文字実体(character entity)(別記A)
text/htmlのみISO-8859-1文字実体(character entity)(別記A)
charset
  =ISO-8859-1
ISO-8859-1文字実体(character entity)(別記A)
charset
  =UTF-8
UTF-8UTF-8の24ビット文字コード列(別記B)


●サーブレット側にrequest.setCharacterEncoding()呼び出しがないと…

FORM HTML上の
METAタグ
ブラウザによる
文字コード認識
サーバに送られた
パラメータ文字列
なしISO-8859-1文字実体(character entity)(別記A)
text/htmlのみISO-8859-1文字実体(character entity)(別記A)
charset
  =ISO-8859-1
ISO-8859-1文字実体(character entity)(別記A)
charset
  =UTF-8
UTF-8UTF-8の24ビット文字コード列, しかし
getParameter()は正しいStringを
作れない(別記C)


[別記A--文字実体(character entity)]
現物データ(実際は1行)%26%2312525%3B%26%2312540%3B%26%2312459%3B
%26%2312523%3B%26%2327177%3B%26%2321147%3B
URLデコードすると…ローカル権力
getParameter()の返し値: ローカル権力

[別記B--UTF-8の24ビット文字コード列(getParameter()にエンコード情報を与えた場合)]
現物データ(実際は1行)%E3%83%AD%E3%83%BC%E3%82%AB
%E3%83%AB%E6%A8%A9%E5%8A%9B
UTF-8でデコードすると…ローカル権力
getParameter()の返し値: ローカル権力

[別記C--UTF-8の24ビット文字コード列(getParameter()にエンコード情報を与えなかった場合)]
現物データ(実際は1行)%E3%83%AD%E3%83%BC%E3%82%AB
%E3%83%AB%E6%A8%A9%E5%8A%9B
UTF-8でデコードすると…ローカル権力
getParameter()の返し値: ?????????????¨????

(「¨」のところは、実際は0xa8という1バイト値。これはたまたま、Microsoft Extended-ASCIIでウムラウト文字にあたる。)

URL encodeとUTF-8に関する基本資料
--この項終わり--

●Webアプリケーション開発におけるマルチバイト文字への正しい対応
●CHARSET CONVERSION FROM BROWSER TO DATABASE
●Character Conversions from Browser to Database(上の記事の拡張バージョン)

[    ][HOME][NEXT](シングルトンと同期化)

関連リンク集:/「Javaの哲学」の恥かき/「Javaによるテキスト処理」の恥かき/「モア・サーブレット」の恥かき/恥かきのホームページ/JavaBeginner/javacのエラーメッセージ/UIDefaultsのkeyとデフォルト値/comp.lang.java.guiのFAQ/