使用 GZIP 压缩算法对网页内容进行压缩,然后传给浏览器。浏览器接收到GZIP 压缩数据后会自动解压并正确显示。
压缩Filter 中需要先判断客户浏览器是否支持GZip 自动解压,如果支持,则进行GZIP压缩,否则不压缩。判断的依据是浏览器提供的Header 信息。
GZipFilter.java代码如下:
package cn.joker.filter;
import java.io.IOException;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.joker.util.GZipResponseWrapper;
/**
* Servlet Filter implementation class GZipFilter
*/
@WebFilter(urlPatterns="/*") // 注解配置的Filter ,执行顺序是与 Filter 的类名有关
public class GZipFilter implements Filter {
/**
* Default constructor.
*/
public GZipFilter() {
// TODO Auto-generated constructor stub
}
/**
* @see Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
// place your code here
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
String acceptEncoding = req.getHeader("Accept-Encoding"); // 支持的编码方式
// 验证
System.out.println(req.getRequestURI());
if(acceptEncoding != null && acceptEncoding.toLowerCase().indexOf("gzip") != -1) {
// 如果客户浏览器支持GZIP格式,则使用GZIP 压缩数据
GZipResponseWrapper gzipRes = new GZipResponseWrapper(res);
chain.doFilter(req, gzipRes);
gzipRes.finishResponse();
}else
// pass the request along the filter chain
chain.doFilter(req, res);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
// TODO Auto-generated method stub
}
}
GZipResponseWrapper 为自定义的 response类,内部将对输出的内容进行GZIP压缩。
package cn.joker.util;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class GZipResponseWrapper extends HttpServletResponseWrapper {
private HttpServletResponse response; // 默认的response
private GZipOutputStream gzipOutputStream; //自定义的outputstream ,执行close() 的时候对数据压缩,并输出
private PrintWriter writer; //自定义printWriter , 将内容输出到 GZipOutputStream 中
public GZipResponseWrapper(HttpServletResponse response) {
super(response);
// TODO Auto-generated constructor stub
this.response = response;
}
// 覆盖getOutputStream 方法,处理二进制内容
public ServletOutputStream getOutputStream() throws IOException{
if(gzipOutputStream == null)
gzipOutputStream = new GZipOutputStream(response);
return gzipOutputStream;
}
//覆盖 getWriter 方法,处理字符内容
public PrintWriter getWriter() throws IOException{
if(writer == null)
writer = new PrintWriter(new OutputStreamWriter(new GZipOutputStream(response),"UTF-8"));
return writer;
}
// 压缩后数据长度会发生变化,因此将该方法置位空
public void setContentLength(int contentLength) {
}
public void flushBuffer() throws IOException{
gzipOutputStream.flush();
}
// 执行该方法将对数据进行GZIP压缩,并输出到浏览器
public void finishResponse() throws IOException{
if(gzipOutputStream != null)
gzipOutputStream.close();
if(writer != null)
writer.close();
}
}
GZipOutputStream 类 是自定义的一个ServletOutputStream 类,它先将数据缓存起来,然后使用JDK 自带的GZIP 压缩类将数据压缩,并输出到客户端浏览器。
GZipOutputStream.java
package cn.joker.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
public class GZipOutputStream extends ServletOutputStream {
//原 response
private HttpServletResponse response;
// JDK 自带的GZIP 压缩数据的类
private GZIPOutputStream gzipOutputStream;
// 将压缩后的数据存放到 ByteArrayOutputStream 对象中
private ByteArrayOutputStream byteArrayOutputStream;
public GZipOutputStream (HttpServletResponse response) throws IOException{
this.response = response;
byteArrayOutputStream = new ByteArrayOutputStream();
gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
// TODO Auto-generated method stub
}
@Override
public void write(int b) throws IOException {
// TODO Auto-generated method stub
//输出到JDK 的GZIP 输出类中
gzipOutputStream.write(b);
}
// 执行压缩,并将数据输出到浏览器中
public void close() throws IOException{
// 执行压缩,一定要调用的方法
gzipOutputStream.finish();
// 将压缩后的数据输出到客户端
byte[] content = byteArrayOutputStream.toByteArray();
// 设定压缩方式为GZIP,客户端浏览器会自动将数据解压
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Content-Length", Integer.toString(content.length));
System.out.println("执行压缩");
// 输出到浏览器
ServletOutputStream out = response.getOutputStream();
out.write(content);
out.close();
}
public void flush() throws IOException{
gzipOutputStream.flush();
}
public void write(byte[] b,int off,int len) throws IOException{
gzipOutputStream.write(b, off, len);
}
public void write(byte[] b) throws IOException{
gzipOutputStream.write(b);
}
}
对于压缩Filter自己很容易形成的想法是在chain.doFilter() 后处理response里的数据。这样做就必须让这个Filter在链中最后执行,然后才能对所有的数据都压缩。
像代码中所示的那样,在将数据放入ServletOutputStream中时,就将其进行GZIP 压缩,然后再放入 ServletOutputStream 中;同样的对于 Response 需要返回的是 自定义的 ServletOutputStrem 类,所以也得自定义一个HttpServletResponse类,还有个优点就是 该 Response 能够在 FilterChain 中传播,但同样的需要把它置于最前面,然后才能对所有输出流中的数据压缩。