HttpServletRequestWrapper にてはまる

ServletAPI に HttpServletRequestWrapper という、HttpServletRequest インタフェースを実装したラッパークラス(の元になるクラス)がありますよね?
私は、長らくアレはただの便利クラスだと思っていました。
・・・が、どうやらAPIとして用意されていることに意味はあったようです。


サーブレットコンテナに Tomcat を使用している場合、HttpServletRequest をラップして何らかの処理を追加したい場合等は、必ず HttpServletRequestWrapper を継承する必要があるようです・・・ *1 *2


Tomcat では、フォワード時に、HttpServletRequestWrapper の getRequest メソッド、setRequest メソッドを使用して、ラップされている HttpServletRequest の内側にある、Tomcat が生成したインスタンスを直接入れ替えているようです。
そのため、フォワード時に、HttpServletRequestWrapper のインスタンスではない、HttpServletRequest 実装クラスを RequestDispatcher の forward メソッドに渡すと、ClassCastException が発生します。


forward 時のメソッド引数は ServletRequest 型であり、ServletRequestWrapper 型ではありません。にもかかわらず、サーブレットコンテナが Tomcat の場合、引数に渡したオブジェクトが ServletRequestWrapper 型であることを強要します。非常に納得いきません。
何のためのインタフェースなのかと・・・・orz


ServletAPI の Javadoc を見ても、リクエストを拡張する場合はServletRequestWrapper/HttpServletRequestWrapper を使用しなければならないとは記述されていません。なので、この動作は Tomcat 特有のものなのかもしれません。


ちなみに、下記のコードがうまく動作せずに、嵌りました(汗

public class XXXXHttpServletRequest extends HttpServletRequestWrapper {
    private String uri;
    public XXXXHttpServletRequest(HttpServletRequest request, String uri) {
        super(request);
        this.uri = uri;
    }

    public String getRequestURI() {
        return uri;
    }
}

リクエストの URI を偽装するサーブレットリクエストを作成したところ、フォワードされた先でも、URI が偽装された物になってました。
これは、Tomcat では forward が行なわれた時に、ServletRequestWrapper の持つ getRequest メソッドと setRequest メソッドを使用して、Tomcat 自体が生成したリクエストオブジェクトを入れ替えることで、リクエスURI などをフォワード先のものになるようにしているためです。

上記のクラスでは、ラップしたリクエストの getRequestURI メソッドがどんな値を返しても、常にコンストラクタで指定された URI を返すようにしてしまっているため、フォワード先で、リクエスURI を取得した場合に、期待したのと異なる値が取得されてしまいます。


そこで、Tomcatフォワード時に、ServletRequestWrapper の getRequest メソッド、setRequest メソッドを使用しないように、HttpServletRequestWrapper は使用せずに、HttpServletRequest インタフェースのラッパークラスを作成して試してみたところ、上で書いた ClassCastException が発生したということです。


で、結局、泥臭く下記のような記述をしてしまいました・・・微妙。

public class XXXXHttpServletRequest extends HttpServletRequestWrapper {
    private String uri;
    private String originalURI;
    public XXXXHttpServletRequest(HttpServletRequest request, String uri) {
        super(request);
        this.uri = uri;
        this.originalURI = request.getRequestURI();
    }

    public String getRequestURI() {
        if (originalURI.equals(super.getRequestURI()))
            return uri;
        else
            return super.getRequestURI();
    }

*1:他のサーブレットコンテナや、私が試した以外のバージョンの Tomcat ではどうなのかは分かりません。

*2:そもそも、Tomcat にしたって、ドキュメントに 「HttpServletRequestWrapper を必ず使わなければいけない」 といった記述は無いように思います。