1. 程式人生 > >java web實現Gzip壓縮傳輸(轉)

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檔案進行合併壓縮還是很有必要的。