使用gzip優化web應用(filter實現)

相關知識:javascript

  gzip是http協議中使用的一種加密算法,客戶端向web服務器端發出了請求後,一般狀況下服務器端會將頁面文件和其餘資源,返回到客戶端,客戶端加載後渲染呈現,這種狀況文件通常都比較大,若是開啓Gzip ,那麼服務器端響應後,會將頁面,JS,CSS等文本文件或者其餘文件經過高壓縮算法將其壓縮,而後傳輸到客戶端,由客戶端的瀏覽器負責解壓縮與呈現。一般能節省40%以上的流量(通常都有60%左右),一些PHP,JSP文件也可以進行壓縮。css

 

1.實現:  html

Tomcat 開啓Gzip :java

1.找到Tomcat 目錄下的conf下的server.xml,並找到以下信息web

Connector port ="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true"

將它改爲以下的形式(其實在上面代碼的下面已經有了,將他們打開而已。):算法

<!-- Define a non-SSL HTTP/1.1 Connector on port 8080 --> <Connector port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml" >

這樣,就可以對html和xml進行壓縮了,若是要壓縮css 和 js,那麼須要將瀏覽器

compressableMimeType=」text/html,text/xml」加入css和js:服務器

<Connector port="8080" ......... compressableMimeType="text/html,text/xml,text/css,text/javascript" >

你甚至能夠壓縮圖片:網絡

compressableMimeType=」text/html,text/xml」加入css和js:app

<Connector port="8080" ......... compressableMimeType="text/html,text/xml,text/css,text/javascript,image/gif,image/jpg" >

開啓後重啓Tomcat ,經過瀏覽器查看headers信息就能看到是否開啓(firebug中有),若是開啓了,那麼transfer-encoding就會是Gzip,不然就是chunked。

 

 

2.在代碼級別完成web應用的gzip壓縮的開啓:

 

 

1.Wrapper  用來包裝HttpServletResponse 對象

 

package com.shop.gzip;

import java.io.ByteArrayOutputStream;
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 Wrapper extends HttpServletResponseWrapper {
    public static final int OT_NONE = 0, OT_WRITER = 1, OT_STREAM = 2;
    private int outputType = OT_NONE;
    private ServletOutputStream output = null;
    private PrintWriter writer = null;
    private ByteArrayOutputStream buffer = null;

    public Wrapper(HttpServletResponse resp) throws IOException {
        super(resp);
        buffer = new ByteArrayOutputStream();
    }

    public PrintWriter getWriter() throws IOException {
        if (outputType == OT_STREAM)
            throw new IllegalStateException();
        else if (outputType == OT_WRITER)
            return writer;
        else {
            outputType = OT_WRITER;
            writer = new PrintWriter(new OutputStreamWriter(buffer,
                    getCharacterEncoding()));
            return writer;
        }
    }

    public ServletOutputStream getOutputStream() throws IOException {
        if (outputType == OT_WRITER)
            throw new IllegalStateException();
        else if (outputType == OT_STREAM)
            return output;
        else {
            outputType = OT_STREAM;
            output = new WrappedOutputStream(buffer);
            return output;
        }
    }

    public void flushBuffer() throws IOException {
        if (outputType == OT_WRITER)
            writer.flush();
        if (outputType == OT_STREAM)
            output.flush();
    }

    public void reset() {
        outputType = OT_NONE;
        buffer.reset();
    }

    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();

    }

    class WrappedOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream buffer;

        public WrappedOutputStream(ByteArrayOutputStream buffer) {
            this.buffer = buffer;
        }

        public void write(int b) throws IOException {
            buffer.write(b);
        }

        public byte[] toByteArray() {
            return buffer.toByteArray();
        }
    }
} 

2.過濾器

package com.shop.gzip;

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

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class GZipFilter implements Filter {

    public void destroy() {
    }
      /**
       * 判斷瀏覽器是否支持GZIP
       * @param request
       * @return
       */
      private static boolean isGZipEncoding(HttpServletRequest request){
        boolean flag=false;
        String encoding=request.getHeader("Accept-Encoding");
        if(encoding.indexOf("gzip")!=-1){
          flag=true;
        }
        return flag;
      }
      
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpServletRequest req=(HttpServletRequest)request;
        if(isGZipEncoding(req)){
            Wrapper wrapper = new Wrapper(resp);
            chain.doFilter(request, wrapper);
            byte[] gzipData = gzip(wrapper.getResponseData());
            resp.addHeader("Content-Encoding", "gzip");
            resp.setContentLength(gzipData.length);
            ServletOutputStream output = response.getOutputStream();
            output.write(gzipData);
            output.flush();
        } else {
            chain.doFilter(request, response);
        }        

    }

    public void init(FilterConfig arg0) throws ServletException {

    }

    private byte[] gzip(byte[] data) {
        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(10240);
        GZIPOutputStream output = null;
        try {
            output = new GZIPOutputStream(byteOutput);
            output.write(data);
        } catch (IOException e) {
        } finally {
            try {
                output.close();
            } catch (IOException e) {
            }
        }
        return byteOutput.toByteArray();
    }

}

 

3.在web.xml中配置 GZipFilter,當咱們訪問應用中以.do結尾的資源的使用,服務器端就開啓http gzip壓縮,將壓縮後的信息經過http 協議傳遞給瀏覽器.

<filter>  

  <filter-name>ecsideExport</filter-name>  

  <filter-class>com.web.servlet.GZipFilter</filter-class>  

 </filter>  

<filter-mapping>  

             <filter-name>ecsideExport</filter-name>  

            <url-pattern>*.do</url-pattern>  

</filter-mapping>  

 

 



番外:使用Jayson Falkner的過濾器

 

Jayson Falkner 在他的Two Servlet Filters Every Web Application Should Have(http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html?page=1 ) 裏面介紹了2個提升Java Web Application 效能的Servlet。這裏記一下其中的GZIPFilter。
GZIPFilter能夠用gzip的方式壓縮HTTP Response的內容,從而在Server端加快了響應回覆的速度(gzip壓縮過程需耗用CPU資源,卻大幅減輕了網卡的處理負擔),在Client端縮短了頁面刷新時間,在網絡內減小了數據流量。另外一方面,由於採用的Filter的方式,無需改動應用現有的代碼就可引入該功能。

 


以上Filter的source code在這裏(http://www.onjava.com/onjava/2003/11/19/examples/jspbook.zip )下載。固然也能夠在這裏(http://www.onjava.com/onjava/2003/11/19/examples/jspbook.jar )下載現成的JAR。

做者定義了3個類來實現這個壓縮功能:GZIPFilter, GZIPResponseWrapper, GZIPResponseStream。類圖大體以下:

 


這3個類要作的,就是在Server將響應內容裝入HTTP Response以前,先對內容進行GZIP壓縮。其中GZIPFilter實現了javax.servlet.Filter接口,在Java Web Container處理HTTP Response的過程當中即可以掛載使用。另外2個類(GZIPResponseWrapper,GZIPResponseStream)基本上是輔助類,真正的壓縮動做是在GZIPResponseStream中由java.util.zip.GZIPOutputStream來完成的,除此還有其它的一些方法,感受彷佛並無所有用到。這部分還須要另外再研究。

 

要佈署這個Filter也很簡單。只要將JAR放至應用的library目錄,而且在應用的佈署配置文件web.xml中加入

GZIPFilter

com.jspbook.GZIPFilter

GZIPFilter

/*.jsp

而後啓動Server就能夠了。

GZIPFilter壓縮HTTP Response內容的做用很是明顯。做者在source code的包裏面另外放了TestGZIP.jsp,這是用來顯示GZIPFilter的壓縮效果的JSP。你能夠找一支產生大量內容的目標JSP來測驗一下,就像下面這支show_response_compressed.jsp:

<%@ page import="java.io.*" %>

<%@ page import="java.util.zip.*" %>

 


<%

String title = "Show Compressed Response";

int size = 100000;

 


out.println("<HTML>/n" +

"<HEAD><TITLE>" + title + "</TITLE></HEAD>/n" +

"<BODY BGCOLOR=/"#FDF5E6/">/n" +

"<H1 ALIGN=/"CENTER/">" + title + " SIZE="+size+"</H1>/n");

 


String line = "Blah, blah, blah, blah, blah. " +

"Yadda, yadda, yadda, yadda.";

for(int i=0; i<size; i++) {

out.println(line);

}

out.println("</BODY></HTML>");

 


%>

運行show_response_compressed.jsp能夠產生相似以下截圖中的頁面。

 

 

 

運行TestGZIP.jsp,在URL中填入show_response_compressed.jsp 所在的地址,提交後便可獲得以下結果。

 


 

能夠看到,未經壓縮的HTTP Response數據量達到了2,950,086 bytes (2.9MB),而壓縮後的數據量僅有8,687 bytes(8 KB),壓縮比高達99.7%!

 


由於是在一臺機器上做的驗證,因此在響應時間的改善方面感受不是很明顯。一樣的,若是是在Intranet環境內,則這種效果也不會很明顯。若是是在Internet上測試,改善的效果應該會比較明顯。

 

 

 

 

 

附上源碼

/*
 * Copyright 2003 Jayson Falkner (jayson@jspinsider.com)
 * This code is from "Servlets and JavaServer pages; the J2EE Web Tier",
 * http://www.jspbook.com. You may freely use the code both commercially
 * and non-commercially. If you like the code, please pick up a copy of
 * the book and help support the authors, development of more free code,
 * and the JSP/Servlet/J2EE community.
 */
package cn.javass.common.web.filter;

/*使用gzip優化web應用(filter實現)*/
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class GZIPFilter implements Filter {

    private static Logger log = LoggerFactory.getLogger(GZIPFilter.class);

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        if (req instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            String ae = request.getHeader("accept-encoding");
            if (ae != null && ae.indexOf("gzip") != -1) {
                log.debug("GZIP supported, compressing.");
                GZIPResponseWrapper wrappedResponse = new GZIPResponseWrapper(response);
                chain.doFilter(req, wrappedResponse);
                wrappedResponse.finishResponse();
                return;
            }
            chain.doFilter(req, res);
        }
    }

    public void init(FilterConfig filterConfig) {
        // noop
    }

    public void destroy() {
        // noop
    }
}
/*
 * Copyright 2003 Jayson Falkner (jayson@jspinsider.com)
 * This code is from "Servlets and JavaServer pages; the J2EE Web Tier",
 * http://www.jspbook.com. You may freely use the code both commercially
 * and non-commercially. If you like the code, please pick up a copy of
 * the book and help support the authors, development of more free code,
 * and the JSP/Servlet/J2EE community.
 */
package cn.javass.common.web.filter;

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

public class GZIPResponseStream extends ServletOutputStream {

    protected ByteArrayOutputStream baos = null;
    protected GZIPOutputStream gzipstream = null;
    protected boolean closed = false;
    protected HttpServletResponse response = null;
    protected ServletOutputStream output = null;

    public GZIPResponseStream(HttpServletResponse response) throws IOException {
        super();
        closed = false;
        this.response = response;
        this.output = response.getOutputStream();
        baos = new ByteArrayOutputStream();
        gzipstream = new GZIPOutputStream(baos);
    }

    @Override
    public void close() throws IOException {
        if (closed) {
            throw new IOException("This output stream has already been closed");
        }
        gzipstream.finish();

        byte[] bytes = baos.toByteArray();

        //   response.addHeader("Content-Length", Integer.toString(bytes.length));
        response.addHeader("Content-Encoding", "gzip");
        output.write(bytes);
        output.flush();
        output.close();
        closed = true;
    }

    @Override
    public void flush() throws IOException {
        if (closed) {
            throw new IOException("Cannot flush a closed output stream");
        }
        gzipstream.flush();
    }

    @Override
    public void write(int b) throws IOException {
        if (closed) {
            throw new IOException("Cannot write to a closed output stream");
        }
        gzipstream.write((byte) b);
    }

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

    @Override
    public void write(byte b[], int off, int len) throws IOException {
        if (closed) {
            throw new IOException("Cannot write to a closed output stream");
        }
        gzipstream.write(b, off, len);
    }

    public boolean closed() {
        return (this.closed);
    }

    public void reset() {
        //noop
    }
}
/*
 * Copyright 2003 Jayson Falkner (jayson@jspinsider.com)
 * This code is from "Servlets and JavaServer pages; the J2EE Web Tier",
 * http://www.jspbook.com. You may freely use the code both commercially
 * and non-commercially. If you like the code, please pick up a copy of
 * the book and help support the authors, development of more free code,
 * and the JSP/Servlet/J2EE community.
 */
package cn.javass.common.web.filter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class GZIPResponseWrapper extends HttpServletResponseWrapper {

    protected HttpServletResponse origResponse = null;
    protected ServletOutputStream stream = null;
    protected PrintWriter writer = null;

    public GZIPResponseWrapper(HttpServletResponse response) {
        super(response);
        origResponse = response;
    }

    public ServletOutputStream createOutputStream() throws IOException {
        return (new GZIPResponseStream(origResponse));
    }

    public void finishResponse() {
        try {
            if (writer != null) {
                writer.close();
            }
            else {
                if (stream != null) {
                    stream.close();
                }
            }
        }
        catch (IOException e) {
        }
    }

    @Override
    public void flushBuffer() throws IOException {
        stream.flush();
    }

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

        if (stream == null) stream = createOutputStream();
        return (stream);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (writer != null) {
            return (writer);
        }

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

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

    @Override
    public void setContentLength(int length) {
    }
}

web.xml

                <!--GZIP文件壓縮的應用  -->
            <filter>
                <filter-name>GZIPFilter</filter-name>
                <filter-class>cn.javass.common.web.filter.GZIPFilter</filter-class>
            </filter>
            <filter-mapping>
                <filter-name>GZIPFilter</filter-name>
                <url-pattern>/*</url-pattern>
            </filter-mapping>
相關文章
相關標籤/搜索