2014年3月31日月曜日

FilterでHTTP通信のGZIP圧縮に対応する

HTTP/1.1では仕様に電文の圧縮が盛り込まれています。
今回はサーブレットフィルタでGZIP圧縮に対応してみたいと思います。

必要なクラス
圧縮されたHTTPリクエストの解凍と、HTTPレスポンスの圧縮を実現するには、次の5つのクラスが必要です。

  1. GZIPFilter - Filterを実装します。HTTPヘッダを確認し、圧縮/解凍をするかどうか判断します。
  2. GZIPRequest - HttpServletRequestWrapperを継承します。GZIPServletInputStreamを返します。
  3. GZIPServletInputStream - HTTPリクエストを解凍するInputStreamです。
  4. GZIPResponse - HttpServletResponseWrapperを継承します。GZIPServletOutputStreamを返します。
  5. GZIPServletOutputStream - HTTPレスポンスを圧縮するOutputStreamです。
ほか、リクエストをそのままレスポンスするEchoServletを準備します。


プログラム
GZIPFilter.java
package gzip;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class GZIPFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
                         FilterChain chain)
        throws IOException, ServletException
    {
        if (!(req instanceof HttpServletRequest)
            || !(resp instanceof HttpServletResponse)) {
            chain.doFilter(req, resp);
            return;
        }

        HttpServletRequest httpReq = (HttpServletRequest) req;
        HttpServletResponse httpResp = (HttpServletResponse) resp;

        if (this.decompressRequired(httpReq)) {
            httpReq = new GZIPRequest(httpReq);
        }

        if (this.compressRequired(httpReq)) {
            httpResp = new GZIPResponse(httpResp);
        }

        chain.doFilter(httpReq, httpResp);
    }

    private boolean decompressRequired(HttpServletRequest request) {
        String contentEncoding = request.getHeader("Content-Encoding");
        return "gzip".equals(contentEncoding);
    }

    private boolean compressRequired(HttpServletRequest request) {
        String acceptEncoding = request.getHeader("Accept-Encoding");
        return "gzip".equals(acceptEncoding);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // nothing to do
    }

    @Override
    public void destroy() {
        // nothing to do
    }
}

GZIPRequest.java
package gzip;

import java.io.IOException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class GZIPRequest extends HttpServletRequestWrapper {

    public GZIPRequest(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new GZIPServletInputStream(super.getInputStream());
    }
}

GZIPServletInputStream.java
package gzip;

import java.io.IOException;
import java.util.zip.GZIPInputStream;
import javax.servlet.ServletInputStream;

public class GZIPServletInputStream extends ServletInputStream {

    private GZIPInputStream gzipStream;

    public GZIPServletInputStream(ServletInputStream stream)
        throws IOException
    {
        this.gzipStream = new GZIPInputStream(stream);
    }

    @Override
    public int read() throws IOException {
        return gzipStream.read();
    }

    @Override
    public void close() throws IOException {
        gzipStream.close();
    }
}

GZIPResponse.java
package gzip;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class GZIPResponse extends HttpServletResponseWrapper {

    public GZIPResponse(HttpServletResponse response) {
        super(response);
        super.setHeader("Content-Encoding", "gzip");
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new GZIPServletOutputStream(super.getOutputStream());
    }
}

GZIPServletOutputStream.java
package gzip;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;

public class GZIPServletOutputStream extends ServletOutputStream {

    private final GZIPOutputStream gzipStream;

    public GZIPServletOutputStream (ServletOutputStream out)
        throws IOException {
        this.gzipStream = new GZIPOutputStream(out);
    }
    
    @Override
    public void write(int b) throws IOException {
        this.gzipStream.write(b);
    }

    @Override
    public void close() throws IOException {
        this.gzipStream.close();
    }
}

EchoServlet.java
package gzip;

import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class EchoServlet extends HttpServlet {

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws IOException, ServletException {
        InputStream in = req.getInputStream();
        OutputStream out = resp.getOutputStream();
        int input = 0;
        while (-1 != (input = in.read())) {
            out.write(input);
        }
        out.flush();
    }
}

通常であれば、HTTPサーバーで対応するのが妥当でしょう。
あえてサーブレットフィルタで実装するメリットがあるとすれば、warファイルにまとめられるので管理が楽になることでしょうか。
クライアントプログラム(C#)は次回の更新で載せる予定です。

関連
GZIP圧縮に対応したHTTPクライアントを作る 

0 件のコメント:

コメントを投稿