過濾器實現檔案的gzip壓縮

aitangyong發表於2014-03-12

gzip和介紹和回想方式,可以參考我的這篇部落格 gzip的介紹以及web伺服器對檔案壓縮的支援。本文介紹如何藉助j2ee的過濾器Filter自己實現gzip對網頁檔案的壓縮。

將該過濾器配置在web.xml中的第一個,實現對*.js檔案攔截

package net.aty;

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 destroy()
    {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException
    {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String ac = httpRequest.getHeader("accept-encoding");
		
		// 檢查瀏覽器是否支援gzip格式
        if (ac != null && ac.contains("gzip"))
        {
            httpResponse.setHeader("Content-Encoding", "gzip");
			
			//對原始的response進行封裝
            GZIPResponseWrapper wrappedResponse = new GZIPResponseWrapper(httpResponse);

            chain.doFilter(httpRequest, wrappedResponse);

            // 完成gzip壓縮,將壓縮後的資料寫入web伺服器建立的原始輸出流中
            // 一定要手動關閉wrappedResponse,否則httpResponse中資料是空,伺服器不能正確的將資料傳給瀏覽器
            wrappedResponse.finishCompress();
        }
        else
        {
            chain.doFilter(request, httpResponse);
        }
    }

    @Override
    public void init(FilterConfig config) throws ServletException
    {

    }

}

package net.aty;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class GZIPResponseWrapper extends HttpServletResponseWrapper
{
    private HttpServletResponse origResponse = null;

    private GZIPResponseStream stream = null;

    private PrintWriter writer = null;

    public GZIPResponseWrapper(HttpServletResponse response) throws IOException
    {
        super(response);
        this.origResponse = response;
    }

    public ServletOutputStream getOutputStream() throws IOException
    {
        if (this.writer != null)
        {
            throw new IllegalStateException("getWriter() has already been called!");
        }

        if (this.stream == null)
        {
            this.stream = this.createOutputStream();
        }
        return this.stream;
    }
	
	public PrintWriter getWriter() throws IOException
    {
        if (this.writer != null)
        {
            return this.writer;
        }

        if (this.stream != null)
        {
            throw new IllegalStateException("getOutputStream() has already been called!");
        }

        this.stream = this.createOutputStream();
        this.writer = new PrintWriter(new OutputStreamWriter(this.stream, "UTF-8"));
        return this.writer;
    }

    public void finishCompress() throws IOException
    {
        if (this.writer != null)
        {
            this.writer.close();
        }
        else if (this.stream != null)
        {
            this.stream.close();
        }
    }

    public long getContentBytes()
    {
        return stream.getWriteBytes();
    }

    private GZIPResponseStream createOutputStream() throws IOException
    {
        return new GZIPResponseStream(origResponse.getOutputStream());
    }

}

GZIPResponseStream 實現對GZIP的封裝

package net.aty;

import java.io.IOException;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletOutputStream;

// 封裝web伺服器原始的httpResponse.getOutputStream().伺服器會將原本應該寫入httpResponse.getOutputStream()
// 中資料,寫入到GZIPOutputStream實現資料壓縮
public class GZIPResponseStream extends ServletOutputStream
{
    private long writeBytes = 0;

    private GZIPOutputStream gzipStream = null;

    // outputStream是輸出目的地。如果在構建gzipStream之前,已經有資料寫入outputStream,那麼這些資料不會壓縮
    public GZIPResponseStream(ServletOutputStream outputStream) throws IOException
    {
        this.gzipStream = new GZIPOutputStream(outputStream);
    }

    @Override
    public void write(int asciiValue) throws IOException
    {
        this.gzipStream.write((byte) asciiValue);
        writeBytes++;
    }

    @Override
    public void write(byte bytesArray[]) throws IOException
    {
        this.write(bytesArray, 0, bytesArray.length);
    }

    @Override
    public void write(byte bytesArray[], int offset, int length) throws IOException
    {
        this.gzipStream.write(bytesArray, offset, length);

        writeBytes += length;
    }

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

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

    public long getWriteBytes()
    {
        return writeBytes;
    }

}

自己通過除錯,得出以下幾個結論

1、response物件由web伺服器建立,當第一個filter拿到response的時候,內容是空的,這個時候還沒有誰向response響應流中寫入資料。
當走完最後個filter或servlet的時候,伺服器會將靜態的html或者動態的jsp寫入的reponse的響應流中。

2、GZIPFilter應該放在web.xml中作為第一個過濾器,這樣才可以保證所有寫入到response中的資料都經過gzip壓縮。


相關文章