通常服务端的网页格式数据是通过gzip压缩后再发送给客户端的,gzip压缩算法对文本的压缩率非常高,尤其对html,js,css格式的数据,原因是这些文本中相同的文本块通常非常多,而相同几份文本块数据通过gzip压缩后几乎只有一份文本块数据的大小,所以一般对纯文本内容gzip压缩算法可压缩到原大小的40%。
JavaWeb开发模式所有资源都是通过Servlet发送给客户端的(那些静态的直接调用的文件实际也是用过一个默认的Servlet发送的),所以要实现数据压缩后发生非常简单,基本方法是:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//普通servlet要实现压缩后再输出的步骤:
//1.获得要输出数据(byte[])
byte[] toBeWriting = "abcefg".getBytes();
//2.将数据压缩,得到压缩后的数据
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
GZIPOutputStream out = new GZIPOutputStream(byteOut,true);
out.write(toBeWriting);
out.close(); //记得从如果没有自动刷新的话则要把流关闭底层的ByteArrayOutputStream才有数据,读数据前要关流!!!
byte[] beWriting = byteOut.toByteArray();
//3.加上响应头将数据输出
response.setHeader("content-encoding", "gzip");
response.setHeader("content-length", beWriting.length + "");
response.getOutputStream().write(beWriting);
}
即在文本发送之前,用GZIPOutputStream将数据压缩,后用输出流将数据发出,
但是很明显,这样每个Servlet都要进行压缩,实际开发是不可能使用该方法的,还好我们有管理请求信息与响应信息的统一入口与出口类——Filter,我们可以使用它将Servlet中要发的数据写到一个自己管理的输出流中,最后将数据统一压缩并发送:
package com.hao.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.util.zip.GZIPOutputStream;
/**
* 发送前将request封装,增强response的功能,将输出流数据拦截记录下来,方法返回后获取该流,将其压缩后发送
*/
public class GzipFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
/**将自定义链后面可以使用的HttpServletResponse,使后面使用该HttpServletResponse的getOutputStream(),getWrite()方法写数据时写到toBeWriting中*/
MyHttpServletResponse myResponse = new MyHttpServletResponse(response);
chain.doFilter(request, myResponse);
byte[] toBeWriting = myResponse.getBuffer();
byte[] beWriting = gzip(toBeWriting);
response.setHeader("content-encoding", "gzip");
response.setHeader("content-length", beWriting.length + "");
response.getOutputStream().write(beWriting);
}
public byte[] gzip(byte[] beZipping) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(beZipping);
gout.close();
return bout.toByteArray();
}
private class MyHttpServletResponse extends HttpServletResponseWrapper {
private ByteArrayOutputStream toBeWriting;
private PrintWriter pw;
/**
* 由于HttpServletResponseWrapper是默认的HttpServletResponse代理,所以要接收真正的response来处理调用的方法,为什么要传response参数详细可以参考java的包装设计模式(代理设计模式)
*/
public MyHttpServletResponse(HttpServletResponse response) throws UnsupportedEncodingException {
super(response);
/**要实现Filter拦截后面response的输出,我们必须将后面的数据写到一个byte[]中,由于我们不知道后面的数据量,可以改用使用ByteArrayOutputStream封装数据*/
this.toBeWriting = new ByteArrayOutputStream();
this.pw = new PrintWriter(new OutputStreamWriter(toBeWriting, getResponse().getCharacterEncoding()));
}
/**
* 由于其他调用response写数据是先获取一个ServletOutputStream,后将数据用write方法写到其中(response.getOutputStream().write();),
* 我们要使其写到toBeWriting中,所以还要把返回的ServletOutputStream包装一下,改写其write方法
*/
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new MyServletOutputStream(super.getOutputStream(), toBeWriting);
}
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
/**由于字节流ByteArrayOutputStream转字符流PrintWriter必须要查码表,如果不指定码表会查ISO-8859-1码表,从而会出现中文乱码
* 同样PrintWriter是带缓存的流,数据在缓存中,要写到底层的ByteArrayOutputStream中必须刷新或关闭*/
return pw;
}
public byte[] getBuffer() throws IOException {
if (pw != null) {
pw.close();
}
toBeWriting.close();
return toBeWriting.toByteArray();
}
}
private class MyServletOutputStream extends ServletOutputStream {
private ServletOutputStream outputStream;
private ByteArrayOutputStream toBeWriting;
/**
* 由于ServletOutputStream是OutputStream的子类,是一个底层流,与服务器无关,所以我们可以自己创建实例
*/
public MyServletOutputStream(ServletOutputStream outputStream, ByteArrayOutputStream toBeWriting) {
super();
this.outputStream = outputStream;
this.toBeWriting = toBeWriting;
}
/**
* 该方法覆盖的是OutputStream的抽象方法,由于底层所有重载的write方法都是调用该方法,所以其实其他方法的write不用覆盖了
*/
@Override
public void write(int b) {
toBeWriting.write(b);
}
@Override
public boolean isReady() {
return outputStream.isReady();
}
@Override
public void setWriteListener(WriteListener writeListener) {
outputStream.setWriteListener(writeListener);
}
}
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void destroy() {
}
}
最后把该Filter配置到web.xml文件中,使其拦截.jsp .html .js .css文件即可
<filter>
<filter-name>GzipFilter</filter-name>
<filter-class>com.hao.web.filter.GzipFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GzipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.html</url-pattern>
<url-pattern>*.js</url-pattern>
<url-pattern>*.css</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>