欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入解析Java的Servlet过滤器的原理及其应用

程序员文章站 2024-03-08 09:01:40
1.servlet过滤器 1.1 什么是过滤器 过滤器是一个程序,它先于与之相关的servlet或jsp页面运行在服务器上。过滤器可附加到一个或多个servlet或js...

1.servlet过滤器
1.1 什么是过滤器
过滤器是一个程序,它先于与之相关的servlet或jsp页面运行在服务器上。过滤器可附加到一个或多个servlet或jsp页面上,并且可以检查进入这些资源的请求信息。在这之后,过滤器可以作如下的选择:
①以常规的方式调用资源(即,调用servlet或jsp页面)。
②利用修改过的请求信息调用资源。
③调用资源,但在发送响应到客户机前对其进行修改。
④阻止该资源调用,代之以转到其他的资源,返回一个特定的状态代码或生成替换输出。
 
1.2 servlet过滤器的基本原理
在servlet作为过滤器使用时,它可以对客户的请求进行处理。处理完成后,它会交给下一个过滤器处理,这样,客户的请求在过滤链里逐个处理,直到请求发送到目标为止。例如,某网站里有提交“修改的注册信息”的网页,当用户填写完修改信息并提交后,服务器在进行处理时需要做两项工作:判断客户端的会话是否有效;对提交的数据进行统一编码。这两项工作可以在由两个过滤器组成的过滤链里进行处理。当过滤器处理成功后,把提交的数据发送到最终目标;如果过滤器处理不成功,将把视图派发到指定的错误页面。


2.servlet过滤器开发步骤
开发servlet过滤器的步骤如下:
①编写实现filter接口的servlet类。
②在web.xml中配置filter。
开发一个过滤器需要实现filter接口,filter接口定义了以下方法:
①destory()由web容器调用,初始化此filter。
②init(filterconfig filterconfig)由web容器调用,初始化此filter。
③dofilter(servletrequest request,servletresponse response,filterchain chain)具体过滤处理代码。


3.一个过滤器框架实例
simplefilter1.java

package com.zj.sample;
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;
 
public class simplefilter1 implements filter {
  @suppresswarnings("unused")
  private filterconfig filterconfig;
 
  public void init(filterconfig config) throws servletexception {
    this.filterconfig = config;
  }
 
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) {
    try {
      system.out.println("within simplefilter1:filtering the request...");
      chain.dofilter(request, response);// 把处理发送到下一个过滤器
      system.out .println("within simplefilter1:filtering the response...");
    } catch (ioexception ioe) {
      ioe.printstacktrace();
    } catch (servletexception se) {
      se.printstacktrace();
    }
  }
 
  public void destroy() {
    this.filterconfig = null;
  }
}

 
simplefilter2.java

package com.zj.sample;
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;
 
public class simplefilter2 implements filter {
  @suppresswarnings("unused")
  private filterconfig filterconfig;
 
  public void init(filterconfig config) throws servletexception {
    this.filterconfig = config;
  }
 
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) {
    try {
      system.out.println("within simplefilter2:filtering the request...");
      chain.dofilter(request, response); // 把处理发送到下一个过滤器
      system.out.println("within simplefilter2:filtering the response...");
    } catch (ioexception ioe) {
      ioe.printstacktrace();
    } catch (servletexception se) {
      se.printstacktrace();
    }
  }
 
  public void destroy() {
    this.filterconfig = null;
  }
}

 
web.xml

<filter>
  <filter-name>filter1</filter-name>
  <filter-class>com.zj.sample.simplefilter1</filter-class>
</filter>
<filter-mapping>
  <filter-name>filter1</filter-name>
  <url-pattern>/*</url-pattern>//为所有的访问做过滤
</filter-mapping>
 
<filter>
  <filter-name>filter2</filter-name>
  <filter-class>com.zj.sample.simplefilter2</filter-class>
</filter>
<filter-mapping>
  <filter-name>filter2</filter-name>
  <url-pattern>/*</url-pattern>//为所有的访问做过滤
</filter-mapping>

 
打开web容器中任意页面输出结果:(注意过滤器执行的请求/响应顺序)

within simplefilter1:filtering the request...
within simplefilter2:filtering the request...
within simplefilter2:filtering the response...
within simplefilter1:filtering the response...

4.报告过滤器
我们来试验一个简单的过滤器,只要调用相关的servlet或jsp页面,它就打印一条消息到标准输出。为实现此功能,在dofilter方法中执行过滤行为。每当调用与这个过滤器相关的servlet或jsp页面时,dofilter方法就生成一个打印输出,此输出列出请求主机和调用的url。因为getrequesturl方法位于httpservletrequest而不是servletrequest中,所以把servletrequest对象构造为httpservletrequest类型。我们改动一下章节3的simplefilter1.java。
simplefilter1.java

package com.zj.sample;
import java.io.ioexception;
import java.util.date;
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;
 
public class simplefilter1 implements filter {
  @suppresswarnings("unused")
  private filterconfig filterconfig;
 
  public void init(filterconfig config) throws servletexception {
    this.filterconfig = config;
  }
 
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) {
    try {
      system.out.println("within simplefilter1:filtering the request...");
      httpservletrequest req = (httpservletrequest) request;
      system.out.println(req.getremotehost() + " tried to access "
         + req.getrequesturl() + " on " + new date() + ".");
      chain.dofilter(request, response);
      system.out.println("within simplefilter1:filtering the response...");
    } catch (ioexception ioe) {
      ioe.printstacktrace();
    } catch (servletexception se) {
      se.printstacktrace();
    }
  }
 
  public void destroy() {
    this.filterconfig = null;
  }
}

 
web.xml设置不变,同章节3。
 
测试:
输入[url]http://localhost:8080/test4jsp/login.jsp[/url]
 
结果:

within simplefilter1:filtering the request...
0:0:0:0:0:0:0:1 tried to access [url]http://localhost:8080/test4jsp/login.jsp[/url] on sun mar 04 17:01:37 cst 2007.
within simplefilter2:filtering the request...
within simplefilter2:filtering the response...
within simplefilter1:filtering the response...

5.访问时的过滤器(在过滤器中使用servlet初始化参数)
下面利用init设定一个正常访问时间范围,对那些不在此时间段的访问作出记录。我们改动一下章节3的simplefilter2.java。
simplefilter2.java。

package com.zj.sample;
import java.io.ioexception;
import java.text.dateformat;
import java.util.calendar;
import java.util.gregoriancalendar;
import javax.servlet.filter;
import javax.servlet.filterchain;
import javax.servlet.filterconfig;
import javax.servlet.servletcontext;
import javax.servlet.servletexception;
import javax.servlet.servletrequest;
import javax.servlet.servletresponse;
import javax.servlet.http.httpservletrequest;
 
public class simplefilter2 implements filter {
  @suppresswarnings("unused")
  private filterconfig config;
  private servletcontext context;
  private int starttime, endtime;
  private dateformat formatter;
 
  public void init(filterconfig config) throws servletexception {
    this.config = config;
    context = config.getservletcontext();
    formatter = dateformat.getdatetimeinstance(dateformat.medium,
       dateformat.medium);
    try {
      starttime = integer.parseint(config.getinitparameter("starttime"));// web.xml
      endtime = integer.parseint(config.getinitparameter("endtime"));// web.xml
    } catch (numberformatexception nfe) { // malformed or null
      // default: access at or after 10 p.m. but before 6 a.m. is
      // considered unusual.
      starttime = 22; // 10:00 p.m.
      endtime = 6; // 6:00 a.m.
    }
  }
 
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) {
    try {
      system.out.println("within simplefilter2:filtering the request...");
      httpservletrequest req = (httpservletrequest) request;
      gregoriancalendar calendar = new gregoriancalendar();
      int currenttime = calendar.get(calendar.hour_of_day);
      if (isunusualtime(currenttime, starttime, endtime)) {
       context.log("warning: " + req.getremotehost() + " accessed "
           + req.getrequesturl() + " on "
           + formatter.format(calendar.gettime()));
       // the log file is under <catalina_home>/logs.one log per day.
      }
      chain.dofilter(request, response);
      system.out
         .println("within simplefilter2:filtering the response...");
    } catch (ioexception ioe) {
      ioe.printstacktrace();
    } catch (servletexception se) {
      se.printstacktrace();
    }
  }
 
  public void destroy() {}
 
  // is the current time between the start and end
  // times that are marked as abnormal access times?
  private boolean isunusualtime(int currenttime, int starttime, int endtime) {
    // if the start time is less than the end time (i.e.,
    // they are two times on the same day), then the
    // current time is considered unusual if it is
    // between the start and end times.
    if (starttime < endtime) {
      return ((currenttime >= starttime) && (currenttime < endtime));
    }
    // if the start time is greater than or equal to the
    // end time (i.e., the start time is on one day and
    // the end time is on the next day), then the current
    // time is considered unusual if it is not between
    // the end and start times.
    else {
      return (!isunusualtime(currenttime, endtime, starttime));
    }
  }
}

 
web.xml设置不变。
关于tomcat日志处理,这里补充介绍一下。config.getservletcontext().log("log message")会将日志信息写入<catalina_home>/logs文件夹下,文件名应该为localhost_log.2007-03-04.txt这样的形式(按日期每天产生一个,第二天可以看见)。要得到这样一个日志文件,应该在server.xml中有:
<logger classname="org.apache.catalina.logger.filelogger" prefix="catalina_log." suffix=".txt" timestamp="true"/>

6.禁止站点过滤器
如果你希望在你的过滤器检测到不正常的异常而中途中断后面的过滤过程时,可这样做:

public void dofilter(servletrequest request, servletresponse response,
    filterchain chain) throws servletexception, ioexception {
  httpservletrequest req = (httpservletrequest) request;
  httpservletresponse res = (httpservletresponse) response;
  if (isunusualcondition(req)) {
    res.sendredirect("http://www.somesite.com");
  } else {
    chain.dofilter(req, res);
  }
}

下例是一个禁止站点过滤器,如果不希望某些站点访问你的网站,你可以在web.xml的param-value中列出它的站点,然后应用上面的原理跳出常规过滤,给出禁止访问的页面。
bannedaccessfilter.java

package com.zj.sample;
import java.io.ioexception;
import java.io.printwriter;
import java.net.malformedurlexception;
import java.net.url;
import java.util.hashset;
import java.util.stringtokenizer;
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;
 
public class bannedaccessfilter implements filter {
  private hashset<string> bannedsitetable;
 
/**
* deny access if the request comes from a banned site or is referred here
* by a banned site.
 */
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) throws servletexception, ioexception {
    system.out.println("within bannedaccessfilter:filtering the request...");
    httpservletrequest req = (httpservletrequest) request;
    string requestinghost = req.getremotehost();
    string referringhost = getreferringhost(req.getheader("referer"));
    string bannedsite = null;
    boolean isbanned = false;
    if (bannedsitetable.contains(requestinghost)) {
      bannedsite = requestinghost;
      isbanned = true;
    } else if (bannedsitetable.contains(referringhost)) {
      bannedsite = referringhost;
      isbanned = true;
    }
    if (isbanned) {
      showwarning(response, bannedsite);
    } else {
      chain.dofilter(request, response);
    }
    system.out.println("within bannedaccessfilter:filtering the response...");
  }
 
/**
* create a table of banned sites based on initialization parameters.
* remember that version 2.3 of the servlet api mandates the use of the
* java 2 platform. thus, it is safe to use hashset (which determines
* whether a given key exists) rather than the clumsier hashtable
* (which has a value for each key).
*/
 
  public void init(filterconfig config) throws servletexception {
    bannedsitetable = new hashset<string>();
    string bannedsites = config.getinitparameter("bannedsites");
    // default token set: white space.
    stringtokenizer tok = new stringtokenizer(bannedsites);
    while (tok.hasmoretokens()) {
      string bannedsite = tok.nexttoken();
      bannedsitetable.add(bannedsite);
      system.out.println("banned " + bannedsite);
    }
  }
 
  public void destroy() {}
 
  private string getreferringhost(string refererringurlstring) {
    try {
      url referringurl = new url(refererringurlstring);
      return (referringurl.gethost());
    } catch (malformedurlexception mue) { // malformed or null
      return (null);
    }
  }
 
  // replacement response that is returned to users
  // who are from or referred here by a banned site.
  private void showwarning(servletresponse response, string bannedsite)
      throws servletexception, ioexception {
    response.setcontenttype("text/html");
    printwriter out = response.getwriter();
    string doctype = "<!doctype html public \"-//w3c//dtd html 4.0 "
       + "transitional//en\">\n";
    out.println(doctype + "<html>\n"
       + "<head><title>access prohibited</title></head>\n"
       + "<body bgcolor=\"white\">\n" + "<h1>access prohibited</h1>\n"
       + "sorry, access from or via " + bannedsite + "\n"
       + "is not allowed.\n" + "</body></html>");
  }
}

 
web.xml

<filter>
  <filter-name>bannedaccessfilter</filter-name>
  <filter-class>com.zj.sample.bannedaccessfilter</filter-class>
  <init-param>
    <param-name>bannedsites</param-name>
    <param-value>
      [url]www.competingsite.com[/url] [url]www.bettersite.com[/url]
      [url]www.moreservlets.com[/url] 127.0.0.1//我们测试这个
    </param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>bannedaccessfilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

 
测试:

[url]http://localhost:8080/test4jsp/[/url]

 
结果:

深入解析Java的Servlet过滤器的原理及其应用

7.替换过滤器
7.1修改响应
过滤器能够阻止对资源的访问或者阻止激活它们。但如果过滤器想更改资源所生成的响应。怎么办呢?似乎没有办法能够对一个资源所生成的响应进行访问。dofilter的第二个参数(servletresponse)给过滤器提供了一种发送新输出到客户机的办法,但没有给过滤器提供对servlet或jsp页面输出进行访问的办法。为什么会这样呢?因为在第一次调用dofilter方法时,servlet或jsp页面甚至还没有执行。一旦调用了filterchain对象中的dofilter方法,要修改响应似乎就太迟了,这是数据已经发送到客户机。
不过,办法是有的,那就是修改传递给filterchain对象的dofilter方法的响应对象。一般,建立缓存servlet或jsp页面生成的所有输出的版本。servlet api 2.3版为此提供了一种有用的资源,即,httpservletresponsewrapper类。这个类的使用包括以下五个步骤:
1)建立一个响应包装器。扩展javax.servlet.http.httpservletresponsewrapper。
2)提供一个缓存输出的printwriter。重载getwriter方法,返回一个保存发送给它的所有东西的printwriter,并把结果存进一个可以稍后访问的字段中。
3)传递该包装器给dofilter。此调用是合法的,因为httpservletresponsewrapper实现httpservletresponse。
4)提取和修改输出。在调用filterchain的dofilter方法后,原资源的输出只要利用步骤2中提供的机制就可以得到。只要对你的应用适合,就可以修改或替换它。
5)发送修改过的输出到客户机。因为原资源不再发送输出到客户机(这些输出已经存放到你的响应包装器中了),所以必须发送这些输出。这样,你的过滤器需要从原响应对象中获得printwriter或outputstream,并传递修改过的输出到该流中。
 
7.2一个可重用的响应包装器
下例程序给出了一个包装器,它可用于希望过滤器修改资源的输出的大多数应用中。chararraywrapper类重载getwriter方法以返回一个printwriter,它累积一个大字符数组中的所有东西。开发人员可利用tochararray(原始char[])或tostring(从char[]得出的一个string)方法得到这个结果。
chararraywrapper.java

package com.zj.sample;
import java.io.chararraywriter;
import java.io.printwriter;
import javax.servlet.http.httpservletresponse;
import javax.servlet.http.httpservletresponsewrapper;
 
/**
 * a response wrapper that takes everything the client would normally
 * output and saves it in one big character array.
 */
public class chararraywrapper extends httpservletresponsewrapper {
  private chararraywriter charwriter;
 
  /**
   * initializes wrapper.
   * <p>
   * first, this constructor calls the parent constructor. that call
   *is crucial so that the response is stored and thus setheader, *setstatus, addcookie, and so forth work normally.
   * <p>
   * second, this constructor creates a chararraywriter that will
* be used to accumulate the response.
   */
  public chararraywrapper(httpservletresponse response) {
    super(response);
    charwriter = new chararraywriter();
  }
 
  /**
   * when servlets or jsp pages ask for the writer, don't give them
* the real one. instead, give them a version that writes into
* the character array.
   * the filter needs to send the contents of the array to the
* client (perhaps after modifying it).
   */
  public printwriter getwriter() {
    return (new printwriter(charwriter));
  }
 
  /**
   * get a string representation of the entire buffer.
   * <p>
   * be sure <b>not</b> to call this method multiple times on the same
   * wrapper. the api for chararraywriter does not guarantee that it
   * "remembers" the previous value, so the call is likely to make
* a new string every time.
   */
  public string tostring() {
    return (charwriter.tostring());
  }
 
  /** get the underlying character array. */
  public char[] tochararray() {
    return (charwriter.tochararray());
  }
}

 
7.3 替换过滤器
这里展示前一节中给出的chararraywrapper的一个常见的应用:更改一个多次出现的目标串为某个替代串的过滤器。
 
7.3.1通用替换过滤器
replacefilter.java给出一个过滤器,它在chararrarywrapper中包装响应,传递该包装器到filterchain对象的dofilter方法中,提取一个给出所有资源的输出的string型值,用一个替代串替换某个目标串的所有出现,并发送此修改过的结果到客户机。
关于这个过滤器,有两件事情需要注意。首先,它是一个抽象类。要使用它,必须建立一个提供gettargetstring和getreplacementstring方法的实现的子类。下一小节中给出了这种处理的一个例子。其次,它利用一个较小的实用类(见filterutils.java)来进行实际的串替换。你可使用新的常规表达式包而不是使用string和stringtokenizer中低级的和繁琐的方法。
replacefilter.java

package com.zj.sample;
import java.io.ioexception;
import java.io.printwriter;
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.httpservletresponse;
 
/**
 * filter that replaces all occurrences of a given string with a
* replacement.
 * this is an abstract class: you <i>must</i> override the gettargetstring
* and getreplacementstring methods in a subclass.
* the first of these methods specifies the string in the response
* that should be replaced. the second of these specifies the string
* that should replace each occurrence of the target string.
 */
public abstract class replacefilter implements filter {
  private filterconfig config;
 
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) throws servletexception, ioexception {
    chararraywrapper responsewrapper = new chararraywrapper(
       (httpservletresponse) response);
    // invoke resource, accumulating output in the wrapper.
    chain.dofilter(request, responsewrapper);
    // turn entire output into one big string.
    string responsestring = responsewrapper.tostring();
    // in output, replace all occurrences of target string with replacement
    // string.
    responsestring = filterutils.replace(responsestring, gettargetstring(),
       getreplacementstring());
    // update the content-length header.
    updateheaders(response, responsestring);
    printwriter out = response.getwriter();
    out.write(responsestring);
  }
 
  /**
   * store the filterconfig object in case subclasses want it.
   */
  public void init(filterconfig config) throws servletexception {
    this.config = config;
  }
 
  protected filterconfig getfilterconfig() {
    return (config);
  }
 
  public void destroy() {
  }
 
  /**
   * the string that needs replacement.
*override this method in your subclass.
   */
  public abstract string gettargetstring();
 
  /**
   * the string that replaces the target. override this method in
   * your subclass.
   */
  public abstract string getreplacementstring();
 
  /**
   * updates the response headers. this simple version just sets
*the content-length header, assuming that we are using a
*character set that uses 1 byte per character.
* for other character sets, override this method to use
* different logic or to give up on persistent http connections.
* in this latter case, have this method set the connection header
* to "close".
   */
  public void updateheaders(servletresponse response, string responsestring) {
    response.setcontentlength(responsestring.length());
  }
}

 
filterutils.java

package com.zj.sample;
 
/**
 * small utility to assist with response wrappers that return strings.
 */
public class filterutils {
  /**
   * change all occurrences of orig in mainstring to replacement.
   */
  public static string replace(string mainstring, string orig,
      string replacement) {
    string result = "";
    int oldindex = 0;
    int index = 0;
    int origlength = orig.length();
    while ((index = mainstring.indexof(orig, oldindex)) != -1) {
      result = result + mainstring.substring(oldindex, index)
         + replacement;
      oldindex = index + origlength;
    }
    result = result + mainstring.substring(oldindex);
    return (result);
  }
}
 
7.3.2实现一个字符替换过滤器
假设百度收购了google(只是假设),现在所有的页面上凡是出现google字样的文字都必须替换为百度!replacesitenamefilter.java继承上文replacefilter.java来实现这一功能。
replacesitenamefilter.java
package com.zj.sample;
 
public class replacesitenamefilter extends replacefilter {
  public string gettargetstring() {
    return ("google.com.cn");
  }
 
  public string getreplacementstring() {
    return ("baidu.com");
  }
}

 
web.xml

<filter>
  <filter-name>replacesitenamefilter</filter-name>
  <filter-class>com.zj.sample.replacesitenamefilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>replacesitenamefilter</filter-name>
  <url-pattern>/login.jsp</url-pattern>
</filter-mapping>

 
测试结果:
过滤前

深入解析Java的Servlet过滤器的原理及其应用

过滤后

深入解析Java的Servlet过滤器的原理及其应用

8.压缩过滤器
有几个最新的浏览器可处理压缩的内容,自动解开以gzip作为content-encoding响应头值的压缩文件,然后就像对原文档那样处理结果。发送这样的压缩内容可以节省很多时间,因为在服务器上压缩文档,然后在客户机上解开文档所需的时间与下载文件的时间相比是微不足道的。程序longservlet.java给出了一个具有很长的、重复的纯文本输出的servlet,这是一个可供压缩使用的成熟的servlet。如果使用gzip,它可以把输出结果压缩到1/300!
在浏览器支持这个压缩能力时,压缩过滤器可利用章节7介绍的chararraywrapper来压缩内容,完成此任务需要下列内容:
1)实现filter接口的类。这个类名为compressionfilter。init方法存放filterconfig对象在一个字段中,以防子类需要访问servlet环境或过滤器名。destory方法体为空。
2)包装的响应对象。dofilter方法将servletresponse对象包装在一个chararraywrapper中,并传递此包装器到filterchain对象的dofilter方法上。在此调用完成后,所有其他过滤器和最终资源都已执行,且输出结果位于包装器之内。这样,原dofilter提取一个代表所有资源的输出的字符数组。如果客户机指出它支持压缩(即,以gzip作为accept-encoding头的一个值),则过滤器附加一个gzipoutputstream到bytearrayoutputstream上,将字符数组复制到此流中,并设置content-encoding响应头为gzip。如果客户机不支持gzip,则将未修改过的字符数组复制到bytearrayoutputstream。最后,dofilter通过将整个字符数组(可能是压缩过的)写到与original响应相关的outputstream中,发送结果到客户机。
3)对longservlet进行注册。
compressionfilter.java

package com.zj.sample;
import java.io.bytearrayoutputstream;
import java.io.ioexception;
import java.io.outputstream;
import java.io.outputstreamwriter;
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;
 
/**
 * filter that compresses output with gzip (assuming that browser supports
 * gzip).
 */
public class compressionfilter implements filter {
 
  private filterconfig config;
 
  /**
   * if browser does not support gzip, invoke resource normally. if browser
   * <i>does</i> support gzip, set the content-encoding response header and
   * invoke resource with a wrapped response that collects all the output.
   * extract the output and write it into a gzipped byte array. finally, write
   * that array to the client's output stream.
   */
  public void dofilter(servletrequest request, servletresponse response,
      filterchain chain) throws servletexception, ioexception {
    httpservletrequest req = (httpservletrequest) request;
    httpservletresponse res = (httpservletresponse) response;
    if (!isgzipsupported(req)) {
      // invoke resource normally.
      chain.dofilter(req, res);
    } else {
      // tell browser we are sending it gzipped data.
      res.setheader("content-encoding", "gzip");
      // invoke resource, accumulating output in the wrapper.
      chararraywrapper responsewrapper = new chararraywrapper(res);
      chain.dofilter(req, responsewrapper);
      // get character array representing output.
      char[] responsechars = responsewrapper.tochararray();
      // make a writer that compresses data and puts it into a byte array.
      bytearrayoutputstream bytestream = new bytearrayoutputstream();
      gzipoutputstream zipout = new gzipoutputstream(bytestream);
      outputstreamwriter tempout = new outputstreamwriter(zipout);
      // compress original output and put it into byte array.
      tempout.write(responsechars);
      // gzip streams must be explicitly closed.
      tempout.close();
      // update the content-length header.
      res.setcontentlength(bytestream.size());
      // send compressed result to client.
      outputstream realout = res.getoutputstream();
      bytestream.writeto(realout);
    }
  }
 
  /**
   * store the filterconfig object in case subclasses want it.
   */
  public void init(filterconfig config) throws servletexception {
    this.config = config;
  }
 
  protected filterconfig getfilterconfig() {
    return (config);
  }
 
  public void destroy() {}
 
  private boolean isgzipsupported(httpservletrequest req) {
    string browserencodings = req.getheader("accept-encoding");
    return ((browserencodings != null) && (browserencodings.indexof("gzip") != -1));
  }
}
 
longservlet.java
package com.zj.sample;
import java.io.ioexception;
import java.io.printwriter;
import javax.servlet.servletexception;
import javax.servlet.http.httpservlet;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
 
/**
 * servlet with <b>long</b> output. used to test the effect of the compression
 * filter of chapter 9.
 */
 
public class longservlet extends httpservlet {
  public void doget(httpservletrequest request, httpservletresponse response)
      throws servletexception, ioexception {
    response.setcontenttype("text/html");
    printwriter out = response.getwriter();
    string doctype = "<!doctype html public \"-//w3c//dtd html 4.0 "
       + "transitional//en\">\n";
    string title = "long page";
    out.println(doctype + "<html>\n" + "<head><title>" + title
       + "</title></head>\n" + "<body bgcolor=\"#fdf5e6\">\n"
       + "<h1 align=\"center\">" + title + "</h1>\n");
    string line = "blah, blah, blah, blah, blah. "
       + "yadda, yadda, yadda, yadda.";
    for (int i = 0; i < 10000; i++) {
      out.println(line);
    }
    out.println("</body></html>");
  }
}

 
web.xml

<filter>
  <filter-name>compressionfilter</filter-name>
  <filter-class>com.zj.sample.compressionfilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>compressionfilter</filter-name>
  <servlet-name>longservlet</servlet-name>
</filter-mapping>
 
<servlet>
  <servlet-name>longservlet</servlet-name>
  <servlet-class>com.zj.sample.longservlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>longservlet</servlet-name>
  <url-pattern>/longservlet</url-pattern>
</servlet-mapping>