GZip Servlet过滤器
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>