getReaderとgetParamater系って一回のリクエストで同時に使えないの?について調べてみた

下記の質問を見つけたので、ちょっと調べてみた。

getReaderとgetParamater系って一回のリクエストで同時に使えないの?
http://bwind.blog19.fc2.com/blog-entry-15.html

TomcatServlet API ServletRequest#getParameter()内には下記の記載がある。

http://mergedoc.sourceforge.jp/tomcat-servletapi-5-ja/javax/servlet/ServletRequest.html
HTTP POST リクエストで送ったなど、パラメータのデータがリクエストのメッセージボディで送られた場合、
getInputStream() や getReader() メソッドを使って直接メッセージボディを読み込む操作は、
このメソッドの実行に影響を与えることがあります。

なぜ1度しか読めないか。
予想としてはHTTP POSTのリクエストで受け取ったメッセージボディを
作成者が作成したクラスに渡すときにサーバで受け取ったメッセージを
そのままストリームとして渡してしまうために、
再度違う方法では取得できないということなのだろう。

ざらっとだけど、
Tomcatのソースを確認すると(org.apache.coyote.tomcat4.CoyoteRequest)
getParameter系、getInputStream系、getReader系でそれぞれ
requestParametersParsed、usingInputStream、usingReaderのフラグを立てていて、
取得時にこれらのフラグを確認して処理を分けている。

厄介なのはgetParameter系の処理の場合、パースするメソッド(parseRequestParameters())
にて、usingInputStream、usingReaderのフラグがたっていると
解析結果を格納するParametersに何も設定せずそのままスルーしてしまうところだろう。
(Exceptionくらい返してもいいと思うのだけど・・・)

とりあえず、Tomcatでは処理をそうしているということは理解した。

getReaderとgetParamater系を両方使えるサーブレットコンテナがないか探してみると
OPEN INTRA MARTというNTTデータ イントラマート社提供のオープンソースに当たった。
http://oss.intra-mart.org/

このAPIの中のim-javaee-assist-impl 0.1.1 API(im-commons)の下記クラス
(org.intra_mart.common.aid.jsdk.javax.servlet.filter.impl.AbstractHttpServletRequestMessageBodyWrapper)
では、

AbstractHttpServletRequestMessageBodyWrapper#getMessageBody()で下記の記載がある。

http://oss.intra-mart.org/projects/im-commons/maveniframe/im-javaee-assist-impl/apidocs/org/intra_mart/common/aid/jsdk/javax/servlet/filter/impl/AbstractHttpServletRequestMessageBodyWrapper.html
このメソッドは、ServletRequest.getInputStream()
または ServletRequest.getReader() を 実行した後でも、
メッセージボディを含む入力ストリームを返します。
また同様に、 ServletRequest.getParameter(String name) を実行した後でも、
メッセージボディを含む入力ストリームを返します。

ソースを確認すると、受け取ったメッセージボディはbyte配列として保持しておき、
getParameter系、getInputStream系、getReader系はそのbyte配列から生成したものを
返してくれる形になっているためにgetMessageBodyとgetParameter系の使用で
rswさんの問題は(代替案が出来たということで)解決かな。

どれでも使用できるようにbyte配列を持っているにもかかわらず
getReaderとgetInputStreamに関しては両方は使えないように実装されているのは
なんか規定された仕様があるようだなぁ・・・
詳細には調べていないけどJSRか何かで規定されてるんだろうな。

うんじゃぁ、TomcatでStreamをとりつつ、ParameterをMapとして取得したい場合どうするか。
getInputStreamで取得したものをbyte列として保持しておいて解析するしかないのだろうなと思う。
javax.servlet.http.HttpUtilsにパースするメソッドがあったけど、
このクラス自体が非推奨になってしまっているなぁ・・・
エンコーディングに気をつけたりする必要があるためなのだろうけど。

うーん・・・
TOMCAT_HOME/server/libにある
catalina.jarの(org.apache.catalina.util.RequestUtil#parseParameters)でいけそうかな?


結論としてはgetReaderとgetParameter系同時に使えない。
TomcatならStreamで取った値をパースしてParameterの代用とすればいいし、
OPEN INTRA MARTならgetMessageBodyをReaderの代用とすればいいということになる。