java web實現Gzip壓縮傳輸(轉)

爲了減少數據在網絡中的傳輸量,從而減少傳輸時長,增加用戶體驗,瀏覽器大都是支持Gzip壓縮技術的。http的請求頭 Accept-Encoding:gzip, deflate 就表示這次請求可以接受Gzip壓縮後的數據,但是這隻表示客戶端接受的數據可以是壓縮數據,服務端具體要怎麼實現壓縮呢?我們就從代碼層面講解一下服務端實現壓縮後的數據傳輸。
第一步、將響應對象HttpServletResponse包裝爲我們自己繼承HttpServletResponseWrapper的MyResponse對象,MyResponse類會重寫父類的getWriter()方法,在getWriter()方法內我們可以將響應數據緩存到PrintWriter中, 然後對外提供一個獲取緩存在PrintWriter中數據的方法getBytes()。
第二部、包裝完HttpServletResponse對象後就需要創建一個過濾器GzipFilter來過濾我們需要壓縮的請求數據了,在執行chain.doFilter()方法前我們需要將HttpServletResponse包裝爲我們自己的MyResponse對象,然後執行doFilter()方法。然後再取得我們第一步緩存的響應數據,並將數據進行GZIPOutputStream壓縮,最後將壓縮後的數據返回給客戶端。
第三部、配置需要過濾的請求類型,即配置過濾路徑。

具體代碼如下:
一、包裝響應對象HttpServletResponse

package com.qbian.gzip;

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

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

public class MyResponse extends HttpServletResponseWrapper{

    private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    private HttpServletResponse response;
    private PrintWriter pwrite;

    public MyResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return new MyServletOutputStream(bytes); // 將數據寫到 byte 中
    }

    /**
     * 重寫父類的 getWriter() 方法,將響應數據緩存在 PrintWriter 中
     */
    @Override
    public PrintWriter getWriter() throws IOException {
        try{
            pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
        } catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return pwrite;
    }

    /**
     * 獲取緩存在 PrintWriter 中的響應數據 
     * @return
     */
    public byte[] getBytes() {
        if(null != pwrite) {
            pwrite.close();
            return bytes.toByteArray();
        } 

        if(null != bytes) {
            try {
                bytes.flush();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }

        return bytes.toByteArray();
    }

    class MyServletOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream ostream ;

        public MyServletOutputStream(ByteArrayOutputStream ostream) {
            this.ostream = ostream;
        }

        @Override
        public void write(int b) throws IOException {
            ostream.write(b); // 將數據寫到 stream 中
        }

    }

}

二、創建過濾器 GzipFilter

package com.qbian.filter;

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.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.qbian.gzip.MyResponse;

public class GzipFilter implements Filter{

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        MyResponse mResp = new MyResponse(resp); // 包裝響應對象 resp 並緩存響應數據

        chain.doFilter(req, mResp);

        byte[] bytes = mResp.getBytes(); // 獲取緩存的響應數據
        System.out.println("壓縮前大小:" + bytes.length);

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        GZIPOutputStream gzipOut = new GZIPOutputStream(bout); // 創建 GZIPOutputStream 對象 

        gzipOut.write(bytes); // 將響應的數據寫到 Gzip 壓縮流中
        gzipOut.close(); // 將數據刷新到  bout 字節流數組

        byte[] bts = bout.toByteArray();
        System.out.println("壓縮後大小:" + bts.length);

        resp.setHeader("Content-Encoding", "gzip"); // 設置響應頭信息
        resp.getOutputStream().write(bts); // 將壓縮數據響應給客戶端

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        System.out.println("+++啓動壓縮。");
    }

}

三、在 web.xml 中配置需要壓縮的請求路徑

<?xml version="1.0" encoding="UTF-8"?>  
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">  
    <display-name>demo</display-name>

    <!-- Gzip 過濾器配置 -->
    <filter>
        <filter-name>gzipF</filter-name>
        <filter-class>com.qbian.filter.GzipFilter</filter-class>
    </filter>
    <!-- Gzip for JavaScript -->
    <filter-mapping>
        <filter-name>gzipF</filter-name>
        <url-pattern>*.js</url-pattern>
    </filter-mapping>
    <!-- Gzip for HTML -->
    <filter-mapping>
        <filter-name>gzipF</filter-name>
        <url-pattern>*.html</url-pattern>
    </filter-mapping>
    <!-- Gzip for CSS -->
    <filter-mapping>
        <filter-name>gzipF</filter-name>
        <url-pattern>*.css</url-pattern>
    </filter-mapping>

</web-app>

最後我們可以對比下看看壓縮的效果,將 web.xml 中的

<filter-mapping>
    <filter-name>gzipF</filter-name>
    <url-pattern>*.js</url-pattern>
</filter-mapping>

註釋掉,然後我們請求服務器一個JavaScript文件,具體信息如下圖所示:

再將上面我們對*.js過濾器配置解開註釋,讓其起到作用。然後再請求剛剛請求的JavaScript文件看一看服務器響應的文件大小是多少,具體信息如下圖所示。

再看看我們後臺打印的壓縮前後的大小對比,如下圖所示。

從以上對比信息中我們可以看到我們寫的Gzip壓縮過濾器起作用了,並且壓縮率很高。
這是服務端的壓縮,前端的JavaScript和CSS在上線時也是需要壓縮的,不過前端構建工具很多,我就不在這裏簡紹了。
總結:現在的開發都是前後端分離,前端框架也有很多,我這次使用的就是angularJs,對於一個單一頁面應用來說,ng需要加載的js文件有很多。ng的默認加載方式是在啓動以後會執行angular.bootstrap()方法並掛載我們創建的相關控制器及其服務,也就是默認的加載方式是同步加載的,這時將JavaScript文件進行合併壓縮還是很有必要的。

原文:https://blog.csdn.net/qbian/article/details/53909778?utm_source=copy

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章