備忘録です。SpringBootなどのFWに頼る場合、2025年現在でこういうコードを書くことは殆どない気がしますが念の為。
素のJava ServletにてWeb開発を行っていて、RequestのURIなどを加工する必要があるときなどの話です。
HttpServletRequest/ResponseのWrapper
Java Servletでの開発を行う際、リクエスト、およびレスポンスの実態は HttpServletRequest / HttpServletResponse のクラスがそれぞれ担います。
このクラスを拡張するための仕組みとして、HttpServletRequestWrapper や HttpServletResponseWrapper というクラスが用意されており、これらを継承して独自クラスを作ることが可能です。
例えば、HttpServletResponseWrapper を継承することで、レスポンス本文やヘッダを横取り・加工することができます。
同様に HttpServletRequestWrapper を継承することで、リクエストのパラメータやパス情報などを差し替えることも可能です。
Filter でのラップ構成
Java Servletでは、HttpServletRequestWrapper を使うと
HttpServletRequest のメソッドをオーバーライドして振る舞いを上書きできます。
これを活用し、Filterの先頭で HttpServletRequestWrapper を差し込み、
アプリ全体に「/project を取り除いた値」を見せるようにしました。
public class LogicalContextPathRequestWrapper extends HttpServletRequestWrapper { private static final String PHYSICAL_CONTEXT = "/project"; public LogicalContextPathRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getContextPath() { String ctx = super.getContextPath(); // "/project" if (ctx.startsWith(PHYSICAL_CONTEXT)) { return ctx.substring(PHYSICAL_CONTEXT.length()); // → "" } return ctx; } @Override public String getRequestURI() { String uri = super.getRequestURI(); if (uri.startsWith(PHYSICAL_CONTEXT)) { return uri.substring(PHYSICAL_CONTEXT.length()); } return uri; } }
public class CommonWrappingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) request; HttpServletResponse httpRes = (HttpServletResponse) response; // 必要に応じて Response も wrap できる chain.doFilter(new LogicalContextPathRequestWrapper(httpReq), httpRes); } }
<!-- web.xml --> <filter> <filter-name>commonWrappingFilter</filter-name> <filter-class>com.example.CommonWrappingFilter</filter-class> </filter> <filter-mapping> <filter-name>commonWrappingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
これにより、既存コードは**getContextPath() を今まで通り "" として扱える**ようになり、
フロント()から見えるURL構造と齟齬が出なくなりました。
補足:設計上の注意点
Wrapperを入れると、実際のリクエスト実装クラス(例:RequestFacade)ではなくなるため、 既存コードで具体クラスにキャストしている箇所があるとClassCastExceptionになります。 → 正しくはHttpServletRequestインタフェースで扱うべきです。- 他のFilterでも別のWrapperを重ねることがあるため、このFilterをチェーンの先頭に置くようにします。 (多重ラップ時は一番外側のオーバーライドだけが有効になるため)
- 多重ラップ環境では、
instanceofとgetRequest()を使って内側のWrapperを探索するのが一般的です。
多重ラップされた中から特定のWrapperを探す
Wrapperを継承したクラスに、独自のメソッドをもたせた場合、そのメソッドを呼び出すときに直接キャストをすると、ClassCastExceptionが起きる場合があります。
これは、AWrapperとBWrapperでwrapされたとき、最後にwrapしたクラスがどちらかによって、インスタンスの実体が異なるからです。
そのため、下記のようなコードで、実際に使用したいクラスをWrapperを探索する必要があります。
@SuppressWarnings("unchecked") public static <T> T findWrappedRequest(HttpServletRequest request, Class<T> targetType) { HttpServletRequest current = request; while (current instanceof HttpServletRequestWrapper) { if (targetType.isInstance(current)) { return (T) current; } current = (HttpServletRequest) ((HttpServletRequestWrapper) current).getRequest(); } return null; }
使い方:
LogicalContextPathRequestWrapper wrapper =
findWrappedRequest(request, LogicalContextPathRequestWrapper.class);
if (wrapper != null) {
String logical = wrapper.getContextPath();
}
- これにより、入れ子の中から目的の型だけを安全に取り出すことができます
- 同様のロジックで
HttpServletResponseWrapper版を作ることも可能です