通过Filter实现Http body压缩
我们将给出一个压缩的例子,解压,加密,解密都可以使用类似的方式。我们先看看运行的效果,通过curl命令发起http请求:
curl -si -H "Accept-Encoding: gzip" http://191.8.1.103:8080/chapter09/servlet
通过Wrapper进行压缩
对于Filter chain,可能会认为在chain.doFilter()后面进行处理,如压缩,但是虽然处理顺序是这样,但是响应数据可不一定等这个顺序结束后才发送,例如通过writer.flush(),可以强制马上发送。因此,我们采用通过HttpServletResponseWrapper对输出的response进行重新封装,将原始输出转为gzip的压缩输出。
public class CompressionFilter implements Filter {
@Override
public void destroy() { }
@Override
public void init(FilterConfig fConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException, ServletException{
if(StringUtils.contains(((HttpServletRequest)request).getHeader("Accept-Encoding"),"gzip")){
//如果支持gzip压缩方式,进行gzip压缩
System.out.println("Encoding requested.");
//(1)设置Header,表明响应进行了gzip压缩
((HttpServletResponse)response).setHeader("Content-Encoding", "gzip");
//(2)通过GzipResponseWrapper(是HttpServletResponseWrapper的继承)对响应response进行重封装,重点是output的重构
GzipResponseWrapper wrapper = new GzipResponseWrapper((HttpServletResponse)response);
try{
chain.doFilter(request, wrapper);
}finally{
try{
//(3)Wrapper是对output的处理,需要确保安全的close。这里通过finish来实现。对于输入/输出,原则是谁创建谁关闭,ServletReponse的output(如response.getWriter())是我们代码获取,但不是我们代码创建,其关闭归创建的ServletResponse来处理,我们不需要。但是新的Wrapper中我们对输出进行了重构,需要由我们来确保关闭。
wrapper.finish();
}catch(Exception e){
e.printStackTrace();
}
}
}else{
// 不支持gzip的,不进行压缩
System.out.println("Encoding not requested.");
chain.doFilter(request, response);
}
}
}
压缩Wrapper的实现
public class GzipResponseWrapper extends HttpServletResponseWrapper{
//(1)进行输出的格式转换。定义内部静态类,通过将ServletOutputStream的输出放入java.util.zip.GZIPOutputStream后输出。
// Java的各类outputStream类似于将内容从一个模子导入另一个模子,每个模子转换格式,而且内容倒出去后,原模子不再有内容。
// 这里我们将ServletOutputStream倒入到GZIPServletOutputStream,成为了压缩的输出。
private static class GZIPServletOutputStream extends ServletOutputStream{
private final ServletOutputStream servletOutputStream;
private final GZIPOutputStream gzipStream;
private GZIPServletOutputStream(ServletOutputStream servletOutputStream) throws IOException{
this.servletOutputStream = servletOutputStream;
// 1.1)输出从ServletOutputStream模子倒入java.util.zip.GZIPOutputStream模子
this.gzipStream = new GZIPOutputStream(servletOutputStream);
}
// 1.2)模子变了,相应的状态检测,写,关闭等,应该转到新的模子中
@Override
public boolean isReady() {
return this.servletOutputStream.isReady();
}
@Override
public void setWriteListener(WriteListener writeListener) {
this.servletOutputStream.setWriteListener(writeListener);
}
@Override
public void write(int b) throws IOException {
this.gzipStream.write(b);
}
@Override
public void close() throws IOException {
this.gzipStream.close();
}
@Override
public void flush() throws IOException {
this.gzipStream.flush();
}
// 1.3)根据GZIPOutputStream的方法,增加finish(),确保关闭该output
public void finish() throws IOException{
this.gzipStream.finish();
}
}
// (2)Wrapper的实现 ---------------------
// 对于HttpServletResponse,可以通过ServletOutputStream写binary,以及通过PrintWriter来写txt,需要对此两者进行封装。
private GZIPServletOutputStream outputStream;
private PrintWriter writer;
public GzipResponseWrapper(HttpServletResponse response) {
super(response);
}
// 2.1)涉及到getOutputStream(),getWriter(),以及将内容输出给client的flushBuffer(),同时增加finish()用于保证关闭。
@Override
public ServletOutputStream getOutputStream() throws IOException {
// Writer是OutputStream的下一个模子,因此有Writer,就没有outputStream,内容已经倒到Writer中了
if(this.writer != null)
throw new IllegalStateException("getWriter() already called.");
if(this.outputStream == null)
this.outputStream = new GZIPServletOutputStream(super.getOutputStream());
return this.outputStream;
}
@Override
public PrintWriter getWriter() throws IOException {
if(this.writer == null && this.outputStream != null)
throw new IllegalStateException("getOutputStream() already called.");
if(this.writer == null){
this.outputStream = new GZIPServletOutputStream(super.getOutputStream());
this.writer = new PrintWriter(new OutputStreamWriter(this.outputStream, this.getCharacterEncoding()));
}
return this.writer;
}
@Override
public void flushBuffer() throws IOException {
if(this.writer != null)
this.writer.flush();
else if(this.outputStream != null)
this.outputStream.flush();
super.flushBuffer();
}
// 2.2)内容压缩,改变body内容,就会改变body的长度,需要对涉及ContentLength进行重构。我们将取消相关的设定,由web contain在发送HTTP响应包时根据实际情况自动给出
@Override
public void setContentLength(int len) { //不进行设置,由web container根据实际情况给出设定
}
@Override
public void setContentLengthLong(long len) {
}
@Override
public void setHeader(String name, String value) {
if(!name.equalsIgnoreCase("Content-Length"))
super.setHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
if(!name.equalsIgnoreCase("Content-Length"))
super.addHeader(name, value);
}
@Override
public void setIntHeader(String name, int value) {
if(!name.equalsIgnoreCase("Content-Length"))
super.setIntHeader(name, value);
}
@Override
public void addIntHeader(String name, int value) {
if(!name.equalsIgnoreCase("Content-Length"))
super.addIntHeader(name, value);
}
// 2.3)提供finish()确保关闭
public void finish() throws IOException{
if(this.writer != null)
this.writer.close();
else if(this.outputStream != null)
this.outputStream.close();
}
}
相关链接: 我的Professional Java for Web Applications相关文章