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 中配置需要压缩的请求路径

  
<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>demodisplay-name>

    
    <filter>
        <filter-name>gzipFfilter-name>
        <filter-class>com.qbian.filter.GzipFilterfilter-class>
    filter>
    
    <filter-mapping>
        <filter-name>gzipFfilter-name>
        <url-pattern>*.jsurl-pattern>
    filter-mapping>
    
    <filter-mapping>
        <filter-name>gzipFfilter-name>
        <url-pattern>*.htmlurl-pattern>
    filter-mapping>
    
    <filter-mapping>
        <filter-name>gzipFfilter-name>
        <url-pattern>*.cssurl-pattern>
    filter-mapping>

web-app>

最后我们可以对比下看看压缩的效果,将 web.xml 中的

<filter-mapping>
    <filter-name>gzipFfilter-name>
    <url-pattern>*.jsurl-pattern>
filter-mapping>

注释掉,然后我们请求服务器一个JavaScript文件,具体信息如下图所示:

再将上面我们对*.js过滤器配置解开注释,让其起到作用。然后再请求刚刚请求的JavaScript文件看一看服务器响应的文件大小是多少,具体信息如下图所示。

再看看我们后台打印的压缩前后的大小对比,如下图所示。

从以上对比信息中我们可以看到我们写的Gzip压缩过滤器起作用了,并且压缩率很高。
这是服务端的压缩,前端的JavaScript和CSS在上线时也是需要压缩的,不过前端构建工具很多,我就不在这里简绍了。
总结:现在的开发都是前后端分离,前端框架也有很多,我这次使用的就是angularJs,对于一个单一页面应用来说,ng需要加载的js文件有很多。ng的默认加载方式是在启动以后会执行angular.bootstrap()方法并挂载我们创建的相关控制器及其服务,也就是默认的加载方式是同步加载的,这时将JavaScript文件进行合并压缩还是很有必要的。

原文:https://blog.csdn.net/qbian/article/details/53909778?utm_source=copy

你可能感兴趣的:(Java)