GZip Servlet过滤器

时间:2020-01-09 10:36:21  来源:igfitidea点击:

GZip Servlet过滤器可用于GZip压缩从Java Web应用程序发送到浏览器的内容。本文将解释其工作原理,并包含一个GZip Servlet过滤器,我们可以在自己的Java Web应用程序中使用它。如果我们不知道什么是Servlet过滤器,请阅读我在Servlet过滤器上的文字。

本教程中显示的GZIP Servlet过滤器使用Java GZIPOutputStream,这在我的Java ZIP教程中已介绍。

为什么GZip压缩内容?

GZip压缩HTML,JavaScript,CSS等使发送到浏览器的数据更小。这样可以加快下载速度。这对于互联网带宽可能受到限制的移动电话尤其有利。 GZip压缩内容增加了服务器和浏览器的CPU开销,但是与未压缩GZip相比,它仍在加快页面总加载速度。

GZip HTTP标头

浏览器在发送到HTTP服务器(例如Java Web服务器)的请求中包含" Accept-Encoding" HTTP标头。 " Accept-Encoding"标头的内容告诉浏览器可以接受哪些内容编码。如果该标头中包含值" gzip",则浏览器可以接受GZip压缩内容。然后,服务器可以GZip压缩发送回浏览器的内容。

如果从服务器发回的内容经过GZip压缩,则服务器会在HTTP响应中包含" Content-Encoding" HTTP标头,其值为" gzip"。这样,浏览器就知道内容是GZip压缩的。

为什么要使用GZip Servlet过滤器?

如果需要,可以在应用程序中的每个Servlet或者JSP中实现GZip压缩。但这变得笨拙。

GZip Servlet过滤器的聪明之处在于,它可以在任何Servlet,JSP甚至静态文件之前和之后执行。这样,我们可以创建单个servlet过滤器,从而为需要它的所有内容启用GZip压缩。 Servlet,JSP等甚至都不知道内容正在压缩,因为它发生在Servlet过滤器中。 GZip Servlet过滤器启用GZip压缩,设置正确的HTTP标头,并确保压缩Servlet,JSP等编写的内容。

GZip Servlet过滤器设计

GZip Servlet过滤器的设计如下所示:

<img src =“ http://theitroad.local/images/java-servlets/gzip-servlet-filter-1.png” width =“ 600” alt =“ GZip Servlet过滤器设计。” />
GZip Servlet过滤器设计。</ b>

首先,我们需要一个Servlet过滤器类。该类被映射到web.xml文件中的一组URL。

当HTTP请求到达映射到过滤器的Servlet容器时,过滤器会在请求针对的Servlet,JSP等处理该请求之前对其进行拦截。 GZip Servlet过滤器检查客户端(浏览器)是否可以接受GZip压缩内容。如果是,则启用响应压缩。

通过将HttpServletResponse对象包装在GZipServletResponseWrapper中来启用响应的GZip压缩。该包装器传递给处理请求的Servlet,JSP等。当Servlet,JSP等将输出发送到浏览器时,它会将其发送到响应包装器对象。 Servlet,JSP等无法看到实际的HttpServletResponse和包装对象之间的区别。然后,响应包装对象压缩写入的内容,并将压缩后的内容写入HttpServletResponse。非常简单。

GZip Servlet过滤器代码

这是GZip Servlet过滤器代码。我们编写的方式实际上并不多。这很简单。

该代码包含3个类。 GZipServletFilter,GZipServletResponseWrapper和GZipServletOutputStream。

GZipServletOutputStream是压缩写入其中的内容的方法。它是通过在内部使用" GZIPOutputStream"来实现的,这是一个标准的Java类。

当" GZipServletResponseWrapper"将" OutputStream"或者" PrintWriter"返回给Servlet或者JSP时,它是" GZipServletOutputStream"或者" PrintWriter"写入返回的" GZipServletOutputStream"。

GZipServletFilter是拦截请求,检查客户端是否接受压缩并启用压缩的函数。它是通过将HttpServletResponse封装在GZipServletResponseWrapper中,然后再将其向下传递到过滤器链来实现的。

这是所有三个类:

public class GZipServletFilter implements Filter {

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

  @Override
  public void destroy() {
  }

  public void doFilter(ServletRequest request, 
                       ServletResponse response,
                       FilterChain chain) 
  throws IOException, ServletException {

    HttpServletRequest  httpRequest  = (HttpServletRequest)  request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    if ( acceptsGZipEncoding(httpRequest) ) {
      httpResponse.addHeader("Content-Encoding", "gzip");
      GZipServletResponseWrapper gzipResponse =
        new GZipServletResponseWrapper(httpResponse);
      chain.doFilter(request, gzipResponse);
      gzipResponse.close();
    } else {
      chain.doFilter(request, response);
    }
  }

  private boolean acceptsGZipEncoding(HttpServletRequest httpRequest) {
      String acceptEncoding = 
        httpRequest.getHeader("Accept-Encoding");

      return acceptEncoding != null && 
             acceptEncoding.indexOf("gzip") != -1;
  }
}
class GZipServletResponseWrapper extends HttpServletResponseWrapper {

  private GZipServletOutputStream gzipOutputStream = null;
  private PrintWriter             printWriter      = null;

  public GZipServletResponseWrapper(HttpServletResponse response)
          throws IOException {
      super(response);
  }

  public void close() throws IOException {

      //PrintWriter.close does not throw exceptions.
      //Hence no try-catch block.
      if (this.printWriter != null) {
          this.printWriter.close();
      }

      if (this.gzipOutputStream != null) {
          this.gzipOutputStream.close();
      }
  }

  /**
   * Flush OutputStream or PrintWriter
   *
   * @throws IOException
   */

  @Override
  public void flushBuffer() throws IOException {

    //PrintWriter.flush() does not throw exception
    if(this.printWriter != null) {
      this.printWriter.flush();
    }

    IOException exception1 = null;
    try{
      if(this.gzipOutputStream != null) {
        this.gzipOutputStream.flush();
      }
    } catch(IOException e) {
        exception1 = e;
    }

    IOException exception2 = null;
    try {
      super.flushBuffer();
    } catch(IOException e){
      exception2 = e;
    }

    if(exception1 != null) throw exception1;
    if(exception2 != null) throw exception2;
  }

  @Override
  public ServletOutputStream getOutputStream() throws IOException {
    if (this.printWriter != null) {
      throw new IllegalStateException(
        "PrintWriter obtained already - cannot get OutputStream");
    }
    if (this.gzipOutputStream == null) {
      this.gzipOutputStream = new GZipServletOutputStream(
        getResponse().getOutputStream());
    }
    return this.gzipOutputStream;
  }

  @Override
  public PrintWriter getWriter() throws IOException {
     if (this.printWriter == null && this.gzipOutputStream != null) {
       throw new IllegalStateException(
         "OutputStream obtained already - cannot get PrintWriter");
     }
     if (this.printWriter == null) {
       this.gzipOutputStream = new GZipServletOutputStream(
         getResponse().getOutputStream());
       this.printWriter      = new PrintWriter(new OutputStreamWriter(
       this.gzipOutputStream, getResponse().getCharacterEncoding()));
     }
     return this.printWriter;
  }

  @Override
  public void setContentLength(int len) {
    //ignore, since content length of zipped content
    //does not match content length of unzipped content.
  }
}
class GZipServletOutputStream extends ServletOutputStream {
  private GZIPOutputStream    gzipOutputStream = null;

  public GZipServletOutputStream(OutputStream output)
        throws IOException {
    super();
    this.gzipOutputStream = new GZIPOutputStream(output);
  }

  @Override
  public void close() throws IOException {
    this.gzipOutputStream.close();
  }

  @Override
  public void flush() throws IOException {
    this.gzipOutputStream.flush();
  }

  @Override
  public void write(byte b[]) throws IOException {
    this.gzipOutputStream.write(b);
  }

  @Override
  public void write(byte b[], int off, int len) throws IOException {
    this.gzipOutputStream.write(b, off, len);
  }

  @Override
  public void write(int b) throws IOException {
     this.gzipOutputStream.write(b);
  }
}

GZip Servlet筛选器web.xml配置

为了在Java Web应用程序中激活GZip Servlet过滤器,我们需要以下配置。请记住,用我们自己的GZip Servlet过滤器类的完全限定名称替换类名称。筛选器映射确定激活筛选器的HTTP请求。

<filter>
  <filter-name>GzipFilter</filter-name>
  <filter-class>com.myapp.GZipServletFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>GzipFilter</filter-name>
  <url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>GzipFilter</filter-name>
  <url-pattern>*.css</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>GzipFilter</filter-name>
  <url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>GzipFilter</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>GzipFilter</filter-name>
  <url-pattern>/</url-pattern>
</filter-mapping>