java web实现Gzip压缩传输

来源:互联网 发布:彩票预测算法 编辑:程序博客网 时间:2024/05/01 11:08

为了减少数据在网络中的传输量,从而减少传输时长,增加用户体验,浏览器大都是支持Gzip压缩技术的。http的请求头 Accept-Encoding:gzip, deflate 就表示这次请求可以接受Gzip压缩后的数据,但是这只表示客户端接受的数据可以是压缩数据,服务端具体要怎么实现压缩呢?我们就从代码层面讲解一下服务端实现压缩后的数据传输。
第一步、将响应对象HttpServletResponse包装为我们自己继承HttpServletResponseWrapperMyResponse对象,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文件,具体信息如下图所示:
未压缩前服务器响应的JavaScript文件大小
再将上面我们对*.js过滤器配置解开注释,让其起到作用。然后再请求刚刚请求的JavaScript文件看一看服务器响应的文件大小是多少,具体信息如下图所示。
压缩后的响应文件大小
再看看我们后台打印的压缩前后的大小对比,如下图所示。
后台打印压缩前后对比大小
从以上对比信息中我们可以看到我们写的Gzip压缩过滤器起作用了,并且压缩率很高。
这是服务端的压缩,前端的JavaScriptCSS在上线时也是需要压缩的,不过前端构建工具很多,我就不在这里简绍了。
总结:现在的开发都是前后端分离,前端框架也有很多,我这次使用的就是angularJs,对于一个单一页面应用来说,ng需要加载的js文件有很多。ng的默认加载方式是在启动以后会执行angular.bootstrap()方法并挂载我们创建的相关控制器及其服务,也就是默认的加载方式是同步加载的,这时将JavaScript文件进行合并压缩还是很有必要的。

1 0