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

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]

结果:

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>

测试结果:
过滤前

过滤后

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>
(0)

相关推荐

  • JSP使用Servlet过滤器进行身份验证的方法

    本文实例讲述了JSP使用Servlet过滤器进行身份验证的方法.分享给大家供大家参考,具体如下: 1.Servlet过滤器的作用描述 (1)在HttpServletRequest到达Servlet 之前,拦截客户的HttpServletRequest. 根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据. (2)在HttpServletResponse 到达客户端之前,拦截HttpServletResponse. 根据需要检查HttpServ

  • java servlet过滤器使用示例

    Servlet过滤器简介      Servlet过滤器实际上就是一个标准的java类,这个类通过实现Filter接口获得过滤器的功能.它在jsp容器启动的时候通过web.xml配置文件被系统加载.Servlet过滤器在接收到用户请求的时候被调用,当服务器接收到用户的请求的时候,依次调用配置好的过滤器,完成后将执行请求所要求的servlet,而servlet执行后的响应,则先通过配置好的过滤器后再发送给用户. 过滤器的用途:1.用户认证和授权管理.2.统计web应用的访问量和访问命中率,生成访问

  • 详解Servlet之过滤器(Filter)

    过滤器 1.为什么使用它? 有很多全站性的东西需要处理,例如乱码问题,通过过滤器统一进行过滤更简单 2.有什么用? 实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截.简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理. 3.怎么用? 步骤1:先写一个类实现javax.servlet.Filter接口 步骤2:重写抽象方法 步骤3:在doFilter()方法中写处理代码,最后用FilterChain调用FilterCha

  • servlet过滤器(Filter)详解(九)

    本文实例为大家分享了servlet过滤器的具体代码,供大家参考,具体内容如下 1.servlet过滤器产生背景 项目中我们会遇到这样一类的需求,对访问服务器的用户ip实施过滤,只有在允许名单中的ip才能访问服务,为了实现需求,每当有客户端请求时,我们都会写校验ip的代码,客户端能够访问到的servlet我们都需要这样做,很明显这样做有一个缺点,就是代码冗余,维护不方便,如果验证规则改变,修改起来也特别麻烦. 为了解决以上问题,Filter 技术应运而生. 2.servlet过滤器是什么? ser

  • Servlet 过滤器详细介绍

    1. 建立一个实现Filter接口的类所有过滤器都必须实现javax.servlet.Filter.这个接口包含三个方法,分别为doFilter.init和destroy. 复制代码 代码如下: public void doFilter(ServletRequset request,                ServletResponse response,               FilterChain chain)    thows ServletException, IOExce

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

    1.Servlet过滤器 1.1 什么是过滤器 过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上.过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息.在这之后,过滤器可以作如下的选择: ①以常规的方式调用资源(即,调用servlet或JSP页面). ②利用修改过的请求信息调用资源. ③调用资源,但在发送响应到客户机前对其进行修改. ④阻止该资源调用,代之以转到其他的资源,返回一个特定的状态代码或生成替换输出.   1.2 Servl

  • 深度源码解析Java 线程池的实现原理

    java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网络等等.所以,如何高效的使用这些资源就是程序员在平时写代码时候的一个努力的方向.本文要说的线程池就是一种对 CPU 利用的优化手段. 线程池,百度百科是这么解释的: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的

  • Java RPC框架过滤器机制原理解析

    过滤器 字面义上理解的过滤器类似下图,从一堆物品中筛选出符合条件的留下,不符合的丢弃. GOF 职责链 GOF中有一种设计模式叫职责链,或者叫责任链,常规的UML图如下: 正统的职责链是将一个请求发给第一个接收者,接收者判断是否属于自己能处理的,如果能处理则执行操作并中止请求下发,流程到此为止.如果不能处理则将请求下发给下一个接收者一直到最后一个接收者. 变体职责链 上面提到正统的职责链有一个特点:当找到符合条件的执行者后流程中止并不会将请求继续下发给后续的执行者.这类场景比较适合比如审核操作,

  • 基于java servlet过滤器和监听器(详解)

    1 过滤器 1.过滤器是什么? servlet规范当中定义的一种特殊的组件,用于拦截容器的调用. 注:容器收到请求之后,如果有过滤器,会先调用过滤器,然后在调用servlet. 2.如何写一个过滤器? 1.写一个java类,实现Filter接口; 2.在接口方法中实现拦截方法; 3.配置过滤器(web.xml); 3.配置初始化参数 1.配置初始化参数.(init-param) 2.通过filterconfig提供的getinitparamenter方法读取初始化的值. 4.优先级: 当有多个过

  • 基于servlet的执行原理与生命周期(全面解析)

    一.先从servlet容器说起:大家最为熟悉的servlet容器就是Tomcat ,Servlet 容器是如何管理 Servlet? 先看一下tomcat的容器模型: 从上图可以看出 Tomcat 的容器分为四个等级,真正管理Servlet 的容器是Context 容器,一个 Context 对应一个 Web 工程 Tomcat 的容器等级中,Context 容器是直接管理 Servlet 在容器中的包装类Wrapper(StandardWrapper)的容器,所以 Context 容器如何运行

  • Java流程控制顺序结构原理解析

    这篇文章主要介绍了Java流程控制顺序结构原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 流程控制的概念 在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的.也就是说,程序的流程对运行结果有直接的影响.所以,我们必须清楚每条语句的执行流程.而且,很多时候我们要通过控制语句的执行顺序来实现我们要完成的功能. 流程控制之顺序结构 根据代码的编写顺序,从上往下,依次执行. 顺序结构之流程图 ​ 需求 举例说明顺序结构的执行

  • python实现布隆过滤器及原理解析

    在学习redis过程中提到一个缓存击穿的问题, 书中参考的解决方案之一是使用布隆过滤器, 那么就有必要来了解一下什么是布隆过滤器.在参考了许多博客之后, 写个总结记录一下. 一.布隆过滤器简介 什么是布隆过滤器? 本质上布隆过滤器( BloomFilter )是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 "某样东西一定不存在或者可能存在". 相比于传统的 Set.Map 等数据结构,它更高效

  • Java继承方法重写实现原理及解析

    这篇文章主要介绍了Java继承方法重写实现原理及解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在Java继承中,子类可以获得父类所有的结构,也可以增加与父类不同的属性和方法.但是,有一种情况,一个相同的方法,子类的行为并不像父类那样,这时,就需要重写父类的方法,下面是重写的代码实现: 代码体现 package com.my.pac12; /** * @author Summerday * @date 2019/12/11 21:26 */

  • Java方法覆盖重写实现原理解析

    这篇文章主要介绍了Java方法覆盖重写实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 方法覆盖重写注意事项: 1.必须保证方法名相同,返回值也相同 @Override:写在方法前面,用来检测方法的覆盖重写是否有效,这个注解不是必要的,就算不写,方法覆盖重写符合要求也是正确的 2.子类方法的返回值必须[小于等于]父类方法的返回值 3.子类方法的修饰符必须[大于等于]父类方法的修饰符 继承关系中,父子类构造方法的访问特点: 1.子类构造

随机推荐