ELB を利用して Aipo を稼動させる(ServletFilter編)

ELBを経由して Aipo を稼動させようとすると、いくつかの課題があります。

特に SSL Termination で ELB に SSL の処理を任せた場合、

  • ・ポートやホストに不整合が生じるため base タグなどが壊れてしまう。
  • ・イベントログにロードバランサーのIPアドレスが記録されてしまう。
アプリケーションの改修を行わずに、ServletFilterのみの対応で稼動させる方法を紹介します。
まず、自前の HttpServletRequestWrapper を用意します。
public class ELBHttpServletRequestWrapper extends HttpServletRequestWrapper {

  private final String protocol;

  private final int port;

  private String remoteAddr;

  public ELBHttpServletRequestWrapper(HttpServletRequest request) {
    super(request);

    String hfor = getHeader("X-FORWARDED-FOR");

    if (hfor != null && hfor != "") {
      String[] split = hfor.split(",");
      remoteAddr = split[0];
    } else {
      remoteAddr = null;
    }
    String hport = getHeader("X-FORWARDED-PORT");
    if (hport != null && hport != "") {
      port = Integer.valueOf(hport);
    } else {
      port = -1;
    }
    String hhttps = getHeader("X-FORWARDED-PROTO");
    if (hhttps != null && hhttps != "") {
      protocol = hhttps;
    } else {
      protocol = null;
    }
  }

  @Override
  public int getServerPort() {
    return port != -1 ? port : super.getServerPort();
  }

  /**
   * 
   * @return
   */
  @Override
  public String getScheme() {
    return protocol != null ? protocol : super.getScheme();
  }

  @Override
  public String getRemoteAddr() {
    return isELBRequest() ? remoteAddr : super.getRemoteAddr();
  }

  @Override
  public StringBuffer getRequestURL() {
    int port = getServerPort();
    String protocol = getScheme();
    return isELBRequest() ? new StringBuffer(protocol).append("://").append(
      getServerName()).append(
      (port == 443 || port == 80) ? "" : ":" + String.valueOf(port)).append(
      getRequestURI()) : super.getRequestURL();
  }

  @Override
  public boolean isSecure() {
    return protocol != null ? "https".equals(protocol) : super.isSecure();
  }

  protected boolean isELBRequest() {
    return remoteAddr != null;
  }

}
ELB を経由すると X-FORWARDED-FOR にアクセス元のアドレスが入ってくるので、HttpServletRequest を書き換えています。
X-FORWARDED-PORT, X-FORWARDED-PROTO も確認して同じように書き換えています。
次に、ServletFilterを用意して、今回作成した HttpServletRequestWrapper を間に挟むようにします。
  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain filterChain) throws IOException, ServletException {
    filterChain.doFilter(new ELBHttpServletRequestWrapper(
      (HttpServletRequest) request), response);
  }
この ServletFilter を web.xml で指定すれば完成です。
この方法であれば、Apache や Tomcat といったミドルウェアの設定やアプリケーションの内部実装に依存することなく、ELB 経由で Aipo を稼動させることができるようになります。
もちろん、同じ環境にダイレクトに繋ぐこともできます。