使用Filter实现网站文本数据压缩后再输出

        通常服务端的网页格式数据是通过gzip压缩后再发送给客户端的,gzip压缩算法对文本的压缩率非常高,尤其对html,js,css格式的数据,原因是这些文本中相同的文本块通常非常多,而相同几份文本块数据通过gzip压缩后几乎只有一份文本块数据的大小,所以一般对纯文本内容gzip压缩算法可压缩到原大小的40%。

        JavaWeb开发模式所有资源都是通过Servlet发送给客户端的(那些静态的直接调用的文件实际也是用过一个默认的Servlet发送的),所以要实现数据压缩后发生非常简单,基本方法是:

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //普通servlet要实现压缩后再输出的步骤:
        //1.获得要输出数据(byte[])
        byte[] toBeWriting = "abcefg".getBytes();

        //2.将数据压缩,得到压缩后的数据
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        GZIPOutputStream out = new GZIPOutputStream(byteOut,true);
        out.write(toBeWriting);
        out.close(); //记得从如果没有自动刷新的话则要把流关闭底层的ByteArrayOutputStream才有数据,读数据前要关流!!!
        byte[] beWriting = byteOut.toByteArray();

        //3.加上响应头将数据输出
        response.setHeader("content-encoding", "gzip");
        response.setHeader("content-length", beWriting.length + "");
        response.getOutputStream().write(beWriting);
    }

        即在文本发送之前,用GZIPOutputStream将数据压缩,后用输出流将数据发出,

        但是很明显,这样每个Servlet都要进行压缩,实际开发是不可能使用该方法的,还好我们有管理请求信息与响应信息的统一入口与出口类——Filter,我们可以使用它将Servlet中要发的数据写到一个自己管理的输出流中,最后将数据统一压缩并发送:

package com.hao.web.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.util.zip.GZIPOutputStream;

/**
 * 发送前将request封装,增强response的功能,将输出流数据拦截记录下来,方法返回后获取该流,将其压缩后发送
 */
public class GzipFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        /**将自定义链后面可以使用的HttpServletResponse,使后面使用该HttpServletResponse的getOutputStream(),getWrite()方法写数据时写到toBeWriting中*/
        MyHttpServletResponse myResponse = new MyHttpServletResponse(response);
        chain.doFilter(request, myResponse);

        byte[] toBeWriting = myResponse.getBuffer();
        byte[] beWriting = gzip(toBeWriting);
        response.setHeader("content-encoding", "gzip");
        response.setHeader("content-length", beWriting.length + "");
        response.getOutputStream().write(beWriting);
    }

    public byte[] gzip(byte[] beZipping) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        GZIPOutputStream gout = new GZIPOutputStream(bout);
        gout.write(beZipping);
        gout.close();
        return bout.toByteArray();
    }

    private class MyHttpServletResponse extends HttpServletResponseWrapper {
        private ByteArrayOutputStream toBeWriting;
        private PrintWriter pw;

        /**
         * 由于HttpServletResponseWrapper是默认的HttpServletResponse代理,所以要接收真正的response来处理调用的方法,为什么要传response参数详细可以参考java的包装设计模式(代理设计模式)
         */
        public MyHttpServletResponse(HttpServletResponse response) throws UnsupportedEncodingException {
            super(response);
            /**要实现Filter拦截后面response的输出,我们必须将后面的数据写到一个byte[]中,由于我们不知道后面的数据量,可以改用使用ByteArrayOutputStream封装数据*/
            this.toBeWriting = new ByteArrayOutputStream();
            this.pw = new PrintWriter(new OutputStreamWriter(toBeWriting, getResponse().getCharacterEncoding()));
        }

        /**
         * 由于其他调用response写数据是先获取一个ServletOutputStream,后将数据用write方法写到其中(response.getOutputStream().write();),
         * 我们要使其写到toBeWriting中,所以还要把返回的ServletOutputStream包装一下,改写其write方法
         */
        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return new MyServletOutputStream(super.getOutputStream(), toBeWriting);
        }

        @Override
        public PrintWriter getWriter() throws UnsupportedEncodingException {
            /**由于字节流ByteArrayOutputStream转字符流PrintWriter必须要查码表,如果不指定码表会查ISO-8859-1码表,从而会出现中文乱码
             * 同样PrintWriter是带缓存的流,数据在缓存中,要写到底层的ByteArrayOutputStream中必须刷新或关闭*/
            return pw;
        }

        public byte[] getBuffer() throws IOException {
            if (pw != null) {
                pw.close();
            }
            toBeWriting.close();
            return toBeWriting.toByteArray();
        }
    }

    private class MyServletOutputStream extends ServletOutputStream {
        private ServletOutputStream outputStream;
        private ByteArrayOutputStream toBeWriting;

        /**
         * 由于ServletOutputStream是OutputStream的子类,是一个底层流,与服务器无关,所以我们可以自己创建实例
         */
        public MyServletOutputStream(ServletOutputStream outputStream, ByteArrayOutputStream toBeWriting) {
            super();
            this.outputStream = outputStream;
            this.toBeWriting = toBeWriting;
        }

        /**
         * 该方法覆盖的是OutputStream的抽象方法,由于底层所有重载的write方法都是调用该方法,所以其实其他方法的write不用覆盖了
         */
        @Override
        public void write(int b) {
            toBeWriting.write(b);
        }

        @Override
        public boolean isReady() {
            return outputStream.isReady();
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
            outputStream.setWriteListener(writeListener);
        }
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

        最后把该Filter配置到web.xml文件中,使其拦截.jsp .html .js .css文件即可

    <filter>
        <filter-name>GzipFilter</filter-name>
        <filter-class>com.hao.web.filter.GzipFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>GzipFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.html</url-pattern>
        <url-pattern>*.js</url-pattern>
        <url-pattern>*.css</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值