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