为什么要进行全站压缩?
HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。
那么如何让web资源经压缩后呈现给用户呢?要知道jsp页面实际上都会被”翻译”成Servlet,Servlet是利用输出流动态生成HTML页面,包括每一个HTML标签和每个在HTML页面中出现的内容。通过分析被翻译后的servlet会发现所有的页面内容都是通过out隐含变量(JspWriter对象)进行输出的,而JspWriter最终所指的输出流, 也就是真正发送字节给用户的输出流就是response.getwriter()
通过过滤器拦截指定的请求,将原始的reponse替换为经过装饰后的response装饰类(该类继承一个默认的response装饰类),通过重写这个类的getWriter()和getOutputStream()方法,不让其输出到浏览器,而是 将其写入到内存字节数组中去,当需要输出的时候,也就是过滤器的第二次执行从chain.doFilter(request,response)开始,再次从内存中取出缓冲区中的数据,进行压缩,并用response进行输出。
首先重写getOutputStream()方法,通过对getOutputStream的重写,不让其输出到客户端,而是 将其写入到内存字节数组中去
@Override
public ServletOutputStream getOutputStream() throws IOException {
//不能够返回ServletOutputStream 对象,这样就输出给浏览器了
return super.getOutputStream();
}
ServletOutputStream装饰类:
这里可能会有一个疑问:只重写了writer(int arg0)方法,并没有重写write(byte[] b)和write(byte[] b, int offset, int end)方法,那么为什么会把数据写入到字节数组缓冲区呢?这是因为后面两种writer()方法最终都要调用writer(int arg0)方法写入到输出流
package pers.msidolphin.newcase.wrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
public class ServletOutputStreamWrapper extends ServletOutputStream{
//字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
private ByteArrayOutputStream out = null;
public ServletOutputStreamWrapper(ByteArrayOutputStream out) {
this.out = out;
}
@Override
public void write(int arg0) throws IOException {
//写入输出缓存,而不是直接输出到浏览器
this.out.write(arg0);
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
// TODO Auto-generated method stub
return;
}
}
重写后的getOutputStream()方法
package pers.msidolphin.newcase.wrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class BufferResponseWrapper extends HttpServletResponseWrapper{
private ByteArrayOutputStream out = new ByteArrayOutputStream();
private PrintWriter printer = null;
private HttpServletResponse response= null;
public BufferResponseWrapper(HttpServletResponse response) {
super(response);
// TODO Auto-generated constructor stub
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
/*返回的是经过“装饰”的ServletOutputStream,当Servlt中的response调用getOutputStream方法时,得到的其实是ServletOutputStream包装类,进而调用的writer()方法也是经过重写之后的writer()方法*/
return new ServletOutputStreamWrapper(out);
}
//定义一个getBuffer()方法,获取被写入到缓存中的数据,因为这些数据还要经过压缩处理
public byte[] getBuffer() {
if(out != null) {
try {
this.out.flush();
return this.out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
}
接着就可以写一个过滤器,拦截指定的请求,将response对象替换为经过”装饰”后的response对象,这样当Servlet调用getOutputStream()方法时,实际调用的是你重写后的方法了
package pers.msidoplphin.newcase.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 pers.msidolphin.newcase.wrapper.BufferResponseWrapper;
/**
* Servlet Filter implementation class GZipFilter
*/
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 res, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) res;
HttpServletResponse response = (HttpServletResponse) resp;
//重新封装response ,代替掉原始的response
BufferResponseWrapper responseWrapper = new BufferResponseWrapper(response);
// pass the request along the filter chain
chain.doFilter(request, responseWrapper);
//请求接受, 开始响应,二次输出的时候,就是开始要将内存中的字节数组,经过压缩传输到浏览器的过程了。
byte[] buffer = responseWrapper.getBuffer();
System.out.println("压缩前大小:" + buffer.length);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout);
gout.write(buffer);
//切记一定要关闭输出流,不然数据可能还会驻留在内存
gout.close();
bout.close();
byte[] zip = bout.toByteArray();
System.out.println("压缩后大小:" + zip.length);
//告诉浏览器,当前数据是被压缩过的
response.setHeader("Content-encoding", "gzip");
response.setContentLength(zip.length);
//调用真正的response,将数据写给浏览器
response.getOutputStream().write(zip);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
// TODO Auto-generated method stub
}
}
上面仅仅只能够满足当Servlet调用getOutputStream()方法向浏览器输出内容的情况,更多情况则是调用getWriter()方法
重写HttpServletResponseWrapper中的getWriter()方法,同样让所有发送到输出流的数据保存在字节数组缓冲区中
public PrintWriter getWriter() throws IOException {
//OutputStreamWriter类 将字符流转字节流,是字符流和字节流之间的桥梁
//但是要注意转换时的编码,如果没有指定编码,则会使用默认的编码,这个编码应当为网页使用的编码,所以 response.getCharacterEncoding() 的作用是让编码得到统一,防止乱码
this.printer = new PrintWriter(new OutputStreamWriter(out, response.getCharacterEncoding()));
return this.printer;
}
之前的getBuffer()方法也要进行改造,PrintWriter缓冲区也要进行刷新,让数据真正进入到字节数组流的缓冲区
public byte[] getBuffer() {
if(this.printer != null) {
this.printer.flush();
this.printer.close();
}
if(out != null) {
try {
this.out.flush();
return this.out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
最后配置web.xml文件即可
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>NewCase</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<filter>
<display-name>GZipFilter</display-name>
<filter-name>GZipFilter</filter-name>
<filter-class>pers.msidoplphin.newcase.filter.GZipFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GZipFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>GZipFilter</filter-name>
<url-pattern>*.js</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>GZipFilter</filter-name>
<url-pattern>*.css</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>GZipFilter</filter-name>
<url-pattern>*.html</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>GZipFilter</filter-name>
<url-pattern>*.xhtml</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
</web-app>
完整的response装饰类代码:
package pers.msidolphin.newcase.wrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
public class BufferResponseWrapper extends HttpServletResponseWrapper{
private ByteArrayOutputStream out = new ByteArrayOutputStream();
private PrintWriter printer = null;
//接收被增强对象
private HttpServletResponse response= null;
public BufferResponseWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
/*返回的是经过“装饰”的ServletOutputStream,当Servlt中的response调用getOutputStream方法时,得到的其实是ServletOutputStream包装类,进而调用的writer()方法也是经过重写之后的writer()方法*/
return new ServletOutputStreamWrapper(out);
}
@Override
public PrintWriter getWriter() throws IOException {
//OutputStreamWriter类 将字符流转字节流,是字符流和字节流之间的桥梁
//但是要注意转换时的编码,如果没有指定编码,则会使用默认的编码,这个编码应当为网页使用的编码,所以 response.getCharacterEncoding() 的作用是让编码得到统一,防止乱码
this.printer = new PrintWriter(new OutputStreamWriter(out, response.getCharacterEncoding()));
return this.printer;
}
//定义一个getBuffer()方法,获取被写入到缓存中的数据,因为这些数据还要经过压缩处理
public byte[] getBuffer() {
if(this.printer != null) {
this.printer.flush();
this.printer.close();
}
if(out != null) {
try {
this.out.flush();
return this.out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
}