過濾器通過HttpServletResponseWrapper包裝HttpServletResponse實現獲取response中的返回數據,以及對數據進行gzip壓縮


 

非原創文章:原文鏈接:http://blog.csdn.net/qq_33206732/article/details/78623042

前幾天我們項目總監給了我一個任務,就是將請求的接口數據進行壓縮,以達到節省流量的目的。

對於實現該功能,有以下思路:

1.獲取到response中的值, 
2.對數據進行gzip壓縮(因為要求前端不變,所以只能選在這個瀏覽器都支持的壓縮方式) 
3.將數據寫入到response中, 
4.將response返貨前端

但是,當我執行第一步的時候,就遇到了很蛋疼的事情,response中的返回數據拿不到,這里就很無語了,又不允許在每個接口方法都加上處理方法,剛開始想的是在攔截器中的afterCompletion()方法里進行數據處理的,但是response里沒有提供可以獲取body值的方法,只能自己想辦法了。

通過網上查找,有一種方式可以獲取到response中的數據,就是使用HttpServletResponseWrapper包裝HttpServletResponse來實現。

通過網上找通過HttpServletResponseWrapper實現獲取response中的數據,大概有兩個版本,有一個版本的數量很多,但是根本沒用啊,就是下面的代碼:

public class ResponseWrapper extends HttpServletResponseWrapper { private PrintWriter cachedWriter; private CharArrayWriter bufferedWriter; public ResponseWrapper(HttpServletResponse response) throws IOException { super(response); bufferedWriter = new CharArrayWriter(); cachedWriter = new PrintWriter(bufferedWriter); } public PrintWriter getWriter() throws IOException { return cachedWriter; } public String getResult() { byte[] bytes = bufferedWriter.toString().getBytes(); try { return new String(bytes, "UTF-8"); } catch (Exception e) { LoggerUtil.logError(this.getClass().getName(), "getResult", e); return ""; } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

經過測試getResult()根本就獲取不到值,具體的大家可以研究下上面的代碼,就知道為啥了,完全是一個坑啊,這里就不多說了。

還有另一個版本,也就是我現在用的(這里先謝謝這位哥們了,具體的原路徑一會貼在下面),下面是我的代碼 
原來的代碼在我這里有一個問題,不知道是都有這個問題,還是就我這有問題,下面會說什么問題以及怎么解決的

import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.*; public class ResponseWrapper extends HttpServletResponseWrapper { private ByteArrayOutputStream bytes = new ByteArrayOutputStream(); private HttpServletResponse response; private PrintWriter pwrite; public ResponseWrapper(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 中 } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

因為HttpServletResponse的包裝類只能在過濾器中使用,所以只能在過濾器中實現了,下面是我的過濾器的doFilter()方法的代碼:

 @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String headEncoding = ((HttpServletRequest)servletRequest).getHeader("accept-encoding"); if (headEncoding == null || (headEncoding.indexOf("gzip") == -1)) { // 客戶端 不支持 gzip filterChain.doFilter(servletRequest, servletResponse); System.out.println("----------------該瀏覽器不支持gzip格式編碼-----------------"); } else { // 支持 gzip 壓縮,對數據進行gzip壓縮 HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; ResponseWrapper mResp = new ResponseWrapper(resp); // 包裝響應對象 resp 並緩存響應數據 filterChain.doFilter(req, mResp); byte[] bytes = mResp.getBytes(); // 獲取緩存的響應數據 System.out.println("壓縮前大小:" + bytes.length); System.out.println("壓縮前數據:" + new String(bytes,"utf-8")); ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gzipOut = new GZIPOutputStream(bout); // 創建 GZIPOutputStream 對象 gzipOut.write(bytes); // 將響應的數據寫到 Gzip 壓縮流中 gzipOut.flush(); gzipOut.close(); // 將數據刷新到 bout 字節流數組 byte[] bts = bout.toByteArray(); System.out.println("壓縮后大小:" + bts.length); resp.setHeader("Content-Encoding", "gzip"); // 設置響應頭信息 resp.getOutputStream().write(bts); // 將壓縮數據響應給客戶端 } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

這里我解釋下上面的代碼,首先判斷一下request請求接不接受gzip壓縮,這個是根據request的請求頭的accept-encoding這個屬性來判斷,因為現在的各大瀏覽器都是支持gzip的,所以如果你想做gzip壓縮,前端只需要加上這個請求頭,如果后端返回的數據是gzip壓縮過的數據,瀏覽器就會自動解壓的。

上面的代碼 
如果不支持gzip壓縮,不處理,正常流程往下走。 
如果支持gzip壓縮,就需要數據處理

大家可以看下這個代碼

filterChain.doFilter(req, mResp);
  • 1

這個方法很重要,這個方法前面部分都是請求接口之前的部分,如果你有一些想要在調用接口前統一處理的東西都可以在前面處理,當然你也可以在攔截器的preHandle()方法中處理。對應的這個方法之后的部分就是請求接口有返回值之后的部分了。也就是這次我們需要進行對數據壓縮的部分。

當然需要注意的是doFilter的第二個參數,原本是ServletResponse對象的,但是現在因為要處理數據,我們使用ResponseWrapper類包裝了ServletResponse,所以第二個參數傳的就是ResponseWrapper對象了,當然對應的如果你包裝了servletRequest,那么第一個參數就要傳你包裝servletRequest類的對象了。

接下來就是先用包裝類對象獲取返回的數據,然后使用GZIPOutputStream對數據進行壓縮,然后在使用resp.getOutputStream().write(bts); 將壓縮后的數據寫入到response中,當然,我們不能忘了需要在返回的請求頭加上Content-Encoding(返回內容編碼)為gzip格式。

這樣我們就可以將response中的數據拿出來進行壓縮后返回到前端,當然你不一定要壓縮,你也可以加密等等處理。

在上面的流程中,我遇到了一個問題,需要注意一下,不知道你們有沒有遇到, 
就是上面的流程進行的都很正常,數據也獲取到了,壓縮也壓縮了,執行時間也打印出來了,但是前端一直在響應中,也就是說我們響應的太慢了,我看了下,平均在30秒左右,這就沒有辦法接受了。

剛開始我以為是前端對gzip數據解壓的速度太慢,但是我屏蔽掉gzip相關代碼,返顯數據返回的還是一樣的慢,所以gzip壓縮解壓排除。

然后只能是一個地方有問題了,那就是我們的包裝類ResponseWrapper有問題了,通過debug,我發現我們封裝的類中的各個方法執行的順序,

首先在我們new 一個對象的時候調用了它的構造方法ResponseWrapper(HttpServletResponse response)方法,然后在執行過濾器的doFilter方法的時候,會調用包裝類的getOutputStream()方法將數據寫入到我們定義的ByteArrayOutputStream中 也就是bytes 中,然后我們調用getBytes()方法將bytes轉換成byte數組返回,這里面就是我們的返回數據。

我們從上面的流程中可以看到,理論上沒有問題,實際上我們也獲取到了我們想要的數據,這些方法執行速度也很快,沒有在哪部分卡頓住。那問題出現在哪呢,我從網上搜了半天,這方面的資料很少,最后在一個博客中,寫了這一句代碼就是在寫數據之前我們需要使用Response對象充值contentLength。也就是下面這一句代碼

response.setContentLength(-1);
  • 1

這里我剛開始沒有想到在哪加這一段代碼,本來想的是在過濾器中,但是想了想,加入的時機都不對,后來看看包裝類,發現了寫這個代碼的哥們定義了一個HttpServletResponse對象,並且在構造方法中也初始化了。但是全文沒有用到這個response對象。我就想是不是在我們執行方法是調用getOutputStream()將數據寫入到bytes前加上這一句代碼。試了一下,還真可以。至此問題解決。

這一次的需求,在怎么解決相應緩慢的問題花費了我一天的時間,但是也收獲很很多東西。所以在這里謝謝上面代碼的哥們,還有寫那個雖然很短,但解決了我最終問題的博客的哥們了。

下面是兩篇博客的地址: 
http://blog.csdn.net/yy417168602/article/details/53534776 
http://blog.csdn.net/qbian/article/details/53909778


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM