SIer だけど技術やりたいブログ

HttpServletRequest を拡張して独自HttpSessionを登録する

Java Servlet

SpringSessionのリファレンスを見てると、「SpringSessionはHttpServletRequestを拡張して実現していますよ」的なことが記載されていた。

Spring Session

Fortunately both HttpSession and HttpServletRequest (the API for obtaining an HttpSession) are both interfaces. This means that we can provide our own implementations for each of these APIs.

この辺のServletの利用方法に詳しくなかったので、HttpServletRequest の拡張方法を実装しながら確認してみた。

HttpServletRequest を拡張する方法

  1. HttpServletRequestWrapperクラスを実装してHttpServletRequestを拡張する。
  2. ServletFilter で、自分が拡張したHttpServletRequestWrapper がHttpServletRequestとして利用されるように登録する。

もう少し実装寄りの説明

HttpSessionクラスを拡張するときを例として、説明する。

HttpServletRequestWrapper はDelegation patternなクラスで、 HttpServletRequestに処理を委譲するだけのクラス。以下みたいなイメージ。

public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest {    
  public HttpServletRequestWrapper(HttpServletRequest request) {
    super(request);
  }

  @Override
  public Cookie[] getCookies() {
    // 処理を委譲する
    return this._getHttpServletRequest().getCookies();
  }

  @Override
  public long getDateHeader(String name) {
    // 処理を委譲する
    return this._getHttpServletRequest().getDateHeader(name);
  }
    ... 
}         

このHttpServletRequestWrapperを継承して、拡張したいメソッドだけオーバライドしたHttpServletRequestを作成する。

//自分用に拡張したHttpServletRequestWrapper クラス
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
  public SessionRepositoryRequestWrapper(HttpServletRequest request) {
    super(request);
  }

  @Override
  public HttpSession getSession() {
    return getSession(true);
  }

  @Override
  public HttpSession getSession(boolean create) {
    //自作のHttpSessionを返すような処理
    return new HttpSessionSpy(super.getSession());
  }
}

独自のHttpSessionクラスを作成する。HttpSessionクラスをコンストラクタの引数にとり、処理は委譲してるだけ。setAttributeとgetAttributeのメソッドが呼ばれたときだけコンソール出力するようにする。

public class HttpSessionSpy implements HttpSession {
  HttpSession session;

  HttpSessionSpy(HttpSession session) {
    this.session = session;
  }

  @Override
  public long getCreationTime() {
    return session.getCreationTime();
  }

  @Override
  public String getId() {
    return session.getId();
  }
  ..
  @Override
  public Object getAttribute(String s) {
    System.out.println("========================");
    System.out.println("getAttribute is called");
    return session.getAttribute(s);
  }

  @Override
  public void setAttribute(String s, Object o) {
    System.out.println("========================");
    System.out.println("setAttribute is called");
    session.setAttribute(s, o);
    System.out.println("key: " + s + " value : " + o);
  }
}

拡張したHttpServletRequestWrapperクラスを作成しただけでは、HttpServletRequestとして利用されないので、ServletFilterでHttpServletRequestとして登録する。

@WebFilter("/*")
public class SessionRepositoryFilter implements Filter {

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }

  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
      SessionRepositoryRequestWrapper customRequest = new SessionRepositoryRequestWrapper(httpServletRequest);
      filterChain.doFilter(customRequest, servletResponse);
  }

  @Override
  public void destroy() {
  }
}

利用側

特に気をつける点はない。普通に使うだけ。

@WebServlet(name = "UserServlet", urlPatterns = {"/user"})
public class UserServlet extends HttpServlet {
    private static final String KEY = "name";

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session = req.getSession();
        System.out.println(session.getAttribute(KEY));
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session = req.getSession();
        session.setAttribute(KEY, "kimura");
    }
}

動作確認

curlでリクエストのイメージを書いたけどcurlでsessionIdを使いまわす方法を知らなかったので、実際はPosterというfirefoxのプラグインで確認しました。

$ curl -X POST localhost:8080/custom/user
$ curl -X GET localhost:8080/custom/user

コンソールの結果

========================
setAttribute is called
key: name value : kimura
========================
getAttribute is called
kimura

サンプルコード

サンプル書いた。
https://github.com/kimullaa/custom-httpsession

その他

すべてのリクエストに共通のheaderを付与する、等も同じようにHttpServletRequestWrapperを拡張する方法で実現できる。
http://sinsengumi.net/blog/2012/04/httpservletrequestwrapper%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%80%81http%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%82%92%E6%94%B9%E5%A4%89%E3%81%99%E3%82%8B/

まとめ

HttpServletRequest に関する処理はHttpServletRequestWrapperで拡張できる。

参考