Filter 全站GZIP压缩过滤的原理及其实现

来源:互联网 发布:怎么制作淘宝客小程序 编辑:程序博客网 时间:2024/06/04 19:02

网上找的两篇不错的response过滤压缩过程与原理

 在客户端访问数据时候,为了尽可能高效率的传输,在传输的JSP网页的时候,可以采用GZIP压缩的方式,使得网页经过压缩后再去传输。在此,使用过滤器,对发送到的客户端的显示,都先进行一次压缩。然后再显示,具体流程可以参考下图:

也就是说,当每获得一次请求是的时候,通过对getOutputStream的重写,不让其输出到客户端,而是 将其写入到内存字节数组中去。   然后,当需要输出的时候, 也就是过滤器的第二次执行从chain.doFilter(request,response)开始

再次充内存中取出缓存的数据,进行压缩,并用response进行输出。

?
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
package cn.Filter;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
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;
import javax.servlet.http.HttpServletResponseWrapper;
 
public class FilterGZIP implements Filter {
 
    public void destroy() {
        // TODO Auto-generated method stub
    }
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
     HttpServletRequest request=(HttpServletRequest) req;
     HttpServletResponse response=(HttpServletResponse) res;
      
     //重新封装response ,来达到改写其已知方法的过程
     MyResponse mresponse=new MyResponse(response);
      
    //以上为过滤器的请求时候的过滤过程;
    //当请求的时候,传入的response 的getOutPutStream方法已经被修改过了。
    //因此,此时如果页面上调用getOutPutStream 方法的时候,其实调用的是自己写的。
     chain.doFilter(request, mresponse);
      
     //请求接受, 开始响应,二次输出的时候,就是开始要将内存中的字节数组,经过压缩传输到客户端的过程了。
     ByteArrayOutputStream  baos =new ByteArrayOutputStream();
     GZIPOutputStream gos=new GZIPOutputStream(baos);
     //从内存中获取到 原始为压缩的数据。
     byte b[]= mresponse.getByte();
     System.out.println("压缩前共有"+b.length+"字节");
     gos.write(b);
     gos.flush();
     gos.close();
     b=baos.toByteArray();
     System.out.println("压缩后共有"+b.length+"字节");
     //设置客户端识别打开的方式。
     response.setHeader("Content-Encoding""GZIP");
     response.setContentLength(b.length);
     response.getOutputStream().write(b);
    }
 
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
 
    }
 
}

以上就是Filter中的全部代码,然后 就是怎样重新封装response的问题了。J2EE中提供了,响应的包装类ServletResponseWrapper    只要继承这个类,重写相应的getOutPutStreaM方法,则目标即刻达成!

?
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
class MyResponse extends HttpServletResponseWrapper {
    //这个是用来存放页面要输出信息的字节数组。
    private ByteArrayOutputStream baos=new ByteArrayOutputStream();
     //这个封装的字符流输出对象
    private PrintWriter pw;
    public MyResponse(HttpServletResponse response) {
        super(response);
    }
       //这个方法,很重要,就是用来获取页面信息存放在内存中的字节数组。 也是压缩的目的
    public byte[] getByte() {
        try {
            if(pw!=null){
                pw.flush();
                pw.close();
            }
            baos.flush();
        catch (IOException e) {
            e.printStackTrace();
        }
        return baos.toByteArray();
 
    }
    //这个方法,就是我们要重写的方法,但是这个方法,有个返回值ServletOutputStream,而这个类又是个抽象类,所以必须还要把这个类给实现了。可以参考下一个代码块 
    @Override
    public ServletOutputStream getOutputStream() throws IOException {   
        return new MyServletOutputStream(baos);
    }
 
    @Override
    public PrintWriter getWriter() throws IOException {
        pw= new PrintWriter(new OutputStreamWriter(baos,super.getCharacterEncoding()));
        return pw;
    }
     
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//这个是应上面getOutPutStream 的邀请的返回值。 也是要 servlet中实际调用的方法。    不会输出到页面,只会将servlet中要写的内容放入到内存中去。
class  MyServletOutputStream extends ServletOutputStream{
     //缓冲页面数据存放区域
    private ByteArrayOutputStream baos;
    public  MyServletOutputStream(ByteArrayOutputStream baos){
        this.baos=baos;
    }
    @Override
    public void write(int b) throws IOException {
        //将输出写入输出缓存
        baos.write(b);
    }
     
}


我自己的理解图形







*****************************************************************************************************************************



    GZIP压缩:将压缩后的文本文件,发送给浏览器,减少流量。

 

一、进行gzip压缩条件:

  1、请求头:Accept-Encoding : gzip  告诉服务器,该浏览器支持gzip压缩。

  2、响应头:Content-Encoding : gzip.  告诉浏览器,输出信息用gzip进行压缩了。

  3、两个主要类:

    ByteArrayOutputStream : 内存输出流,还有缓存。

    GZIPOutputStream 包装流;

 

二、gzip 压缩步骤:

    1、获取字符的字节数组 byte[] buf = str.getBytes() ;

    2、通过GZIPOutputStream 包装流进行输入:

       创建 GZIPOutputStream 输出流时,需要传一个带有缓冲区的输出流,所以我们ByteArrayOutputStream 输出流。而且,ByteArrayOutputStream还可以获取byte[];

    3、将ByteArrayOutputStream 流中的缓存数据,转换成字节数组。

    4、将 压缩后的字节数组通过response 进行输出。不过输出之前要设置Content-Encoding 响应头,value为gzip。告诉浏览器数据进行了gzip压缩,要使用gzip解压

复制代码
 1             String str = "我是个测试"; 2             //1\获取字节数组 3             byte[] bytes = str.getBytes() ; 4              5             System.out.println("压缩前的长度:" + bytes.length);  6             //2\ 7             ByteArrayOutputStream baos = new ByteArrayOutputStream() ;  8             GZIPOutputStream  gzip = new GZIPOutputStream(baos) ; 9             10             gzip.write(bytes) ; 11             gzip.close() ;12             //3\13             bytes = baos.toByteArray() ;14             System.out.println("压缩后的长度:" + bytes.length);
复制代码

    数据较小是,压缩的效果不是很明显,不过数据越大,压缩效果越明显。所以,GZIP压缩一般只处理文本内容,对图片、已经压缩过的文件则不进行压缩。这时就要在配置文件时,配置要过滤的资源

  

 

 

三、GZIPFilter

复制代码
 1 import itheima.decorator.MyHttpServletResponse; 2  3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.util.zip.GZIPOutputStream; 6  7 import javax.servlet.Filter; 8 import javax.servlet.FilterChain; 9 import javax.servlet.FilterConfig;10 import javax.servlet.ServletException;11 import javax.servlet.ServletRequest;12 import javax.servlet.ServletResponse;13 import javax.servlet.http.HttpServletRequest;14 import javax.servlet.http.HttpServletResponse;15 /**16  * Gzip压缩过滤器17  * @author 贺佐安18  *19  */20 public class GZIPFilter implements Filter{21 22     public void init(FilterConfig filterConfig) throws ServletException {23     }24 25     public void doFilter(ServletRequest req , ServletResponse resp ,26             FilterChain chain) throws IOException, ServletException {27         HttpServletResponse response = (HttpServletResponse) resp ; 28         HttpServletRequest request = (HttpServletRequest) req ;29         //创建HttpServletResponse 包装类的实例30         MyHttpServletResponse myResponse = new MyHttpServletResponse(response) ;31         32         chain.doFilter(request, myResponse) ;33         34         //GZIP压缩:35         byte[] buff = myResponse.getBufferedBytes() ;  36         //创建缓存容器:37         ByteArrayOutputStream baos = new ByteArrayOutputStream() ;38         39         GZIPOutputStream gzip = new GZIPOutputStream(baos) ;40         41         gzip.write(buff) ;42         43         gzip.close() ;44         45         buff = baos.toByteArray() ;46          47         //设置响应头;48         response.setHeader("Content-Encoding", "gzip");49         response.setContentLength(buff.length) ;  50         response.getOutputStream().write( buff) ;51     }52 53     public void destroy() {54     }55 56 }
复制代码

 

  步骤:

    1、对HttpServletResponse 进行包装 :改写getOutputStream()、getWriter() 方法,并且设置一个临时容器,存储Serlvet处理后要输出的数据。 这里是重点。

复制代码
 1 import java.io.ByteArrayOutputStream; 2 import java.io.IOException; 3 import java.io.OutputStreamWriter; 4 import java.io.PrintWriter; 5  6 import javax.servlet.ServletOutputStream; 7 import javax.servlet.http.HttpServletResponse; 8 import javax.servlet.http.HttpServletResponseWrapper; 9 /**10  * 对HttpServletResponse 进行包装11  * @author 贺佐安12  *13  */14 public class MyHttpServletResponse extends HttpServletResponseWrapper {15     //定义一个容器,用来存储Serlvet 处理完后response 写出的数据16     private ByteArrayOutputStream bos = new ByteArrayOutputStream()  ;17     private PrintWriter printWriter = null;18     public MyHttpServletResponse(HttpServletResponse  response) {19         super(response) ;20     }21     //处理字节流输出的情况22     public ServletOutputStream getOutputStream() throws IOException {23         return new MyServletOutputStream(bos); 24     } 25 26     //处理字符流输出的情况:用字符流时要注意乱码:字节转字符要查码表,字符转字节也要查码表27     public PrintWriter getWriter() throws IOException {28         printWriter  = new PrintWriter(new OutputStreamWriter(bos, super.getCharacterEncoding())) ;29         return printWriter;30     }31     //获取response 写出的数据32     public byte[] getBufferedBytes(){33         try {34             if (printWriter != null) 35                 printWriter.close() ;36             bos.flush() ;37         } catch (IOException e) {38             e.printStackTrace(); 39         }40         byte[] byteArray = bos.toByteArray() ;41         return  byteArray;42     }43 }
复制代码

 

    2、改写getOutputStream 方法时,要返回一个SerlvetOutputStream 类实例,因为SerlvetOutputStream是抽象类,不能创建实例,所以要重写SerlvetOutputStream 类:

复制代码
 1 import java.io.ByteArrayOutputStream; 2 import java.io.IOException; 3  4 import javax.servlet.ServletOutputStream; 5 /** 6  * 包装ServletOutputStream ,改写write 方法。 7  * @author 贺佐安 8  * 9  */10 public class MyServletOutputStream extends ServletOutputStream {11     private ByteArrayOutputStream bos = null ; 12     public MyServletOutputStream (ByteArrayOutputStream bos) {13         this.bos = bos ;14     }15     public void write(int b) throws IOException {16         bos.write(b) ;17     }18 }
复制代码

 

    3、将包装过的HttpServletResponse 类的实例放行。

    4、然后获取Servlet 处理过后的数据,然后进行Gzip压缩。

    5、调用ServletResponse 的实例,将压缩后的数据写出去。

复制代码
 1     //GZIP压缩: 2         byte[] buff = myResponse.getBufferedBytes() ;   3         //创建缓存容器: 4         ByteArrayOutputStream baos = new ByteArrayOutputStream() ; 5          6         GZIPOutputStream gzip = new GZIPOutputStream(baos) ; 7          8         gzip.write(buff) ; 9         10         gzip.close() ;11         12         buff = baos.toByteArray() ;13          14         //设置响应头;15         response.setHeader("Content-Encoding", "gzip");16         response.setContentLength(buff.length) ;  17         response.getOutputStream().write( buff) ;
复制代码

  

  以上便是用Filter 对一些文本资源进行GIZP压缩的步骤。重点就是第二步,如何获取Servlet 返回的数据。更细一点的流程如下图:

    

    



0 0
原创粉丝点击