TomcatのServletRequest#setCharacterEncoding()問題まとめ
1. 問題の概要
Tomcatの4.1.29以降の4.1.*系バージョン、ならびに5.0.16以降の5.0.*系バージョンでは、ServletRequestクラスのsetCharacterEncoding()メソッドの挙動が変更されています。POSTリクエストのbodyで渡されるパラメータのコード変換は(これまで通り)行われますが、GETリクエストのクエリーストリングで渡されるパラメータのコード変換は行われないようになりました。以前はメソッドによらず、パラメータのコード変換はsetCharacterEncoding()メソッドで行うことができたので、Tomcat 4.1.29/5.0.16以前のバージョンに慣れていると、思わぬ落とし穴にはまります。2. 経緯
SunのServlet仕様のAPIドキュメントで、ServletRequest#setCharacterEncoding()メソッドの説明を読むと、Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader().
とあるので(赤字部分に注目)、実はこの変更は仕様変更ではなく、正しい仕様を満たすためのバグ修正なんです。ですので、今後は「setCharacterEncoding()ではクエリーストリングで渡されるパラメータのコード変換は行われない。それがServlet API 2.3の正しい仕様」という認識を持っておく必要があります。別の表現をすると「日本語の文字列をちゃんと処理しようと思ったら、setCharacterEncoding()一発だけではダメだよ」ってことです。
3. 対処方法
サーブレットフィルタを使用している場合は、フィルタ内で吸収してしまうのが一番手軽です。よく使われている(と思われる)、SetCharacterEncodingFilterクラスならば、doFilter()メソッドを以下のように書き換えれば良いでしょう。public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // Conditionally select and set the character encoding to be used if (ignore || (request.getCharacterEncoding() == null)) { String encoding = selectEncoding(request); if (encoding != null) { request.setCharacterEncoding(encoding); // HTTP-GETの場合、ServletRequest#setCharacterEncoding() // ではパラメータのコード変換は行われない。(Tomcat 4.1.29/5.1.16以降) // しかたがないので、自力で変換する。 if (decodeHttpGetParameter) { if ((request instanceof HttpServletRequest) && ("GET".equalsIgnoreCase(((HttpServletRequest)request).getMethod()))) { Map parameterMap = request.getParameterMap(); if (parameterMap != null) { for (Iterator it = parameterMap.keySet().iterator(); it.hasNext(); ) { String name = (String)it.next(); String[] values = request.getParameterValues(name); if (values != null) { for (int i = 0; i < values.length; i++) { values[i] = new String(values[i].getBytes("ISO-8859-1"), encoding); } } } } } } } } // Pass control on to the next filter chain.doFilter(request, response); }
doFilter()メソッド以外は省略しています。decodeHttpGetParameterはクラス変数で、フィルタの初期化パラメータを利用してtrueまたはfalseにセットされるようにしています。web.xmlの設定で、処理を切り替えられるようにするわけです。
フィルタを使用せず、サーブレット内で処理する場合は、
(自分で考えましょう)
4. Tomcat以外のServletコンテナの状況
1. Weblogic
WebLogic Server のI18n(国際化)では、「setCharacterEncoding()でOK」と書かれているように読めます。「setCharacterEncoding()は Servlet 2.3 の仕様に準拠しています」という記述とは矛盾していますね・・・。2. WebSphere
Servlet2.3/JSP1.2/HTTP Session Management(pdf)のp.30「リクエストの文字コード設定」で、「setCharacterEncoding()しましょうね」という記述。やっぱりServlet 2.3仕様を満たしていないことになるのかなあ。
3. Oracle Application Server
(未稿)4. Jetty
(未稿)5. あとなんかあったっけ?
ちゃんと実際に確認したわけではありません。Web上で拾える情報を元に推測で書いています。誰か教えて。