异步Servlet有时需要一个拦截器,但必须是异步的Filter,否则将会报错:
严重: Servlet.service() for servlet [com.learn.servlet3.async.DemoAsyncLinkServlet] in context with path [/servlet3] threw exceptionjava.lang.IllegalStateException: Not supported.
因此异步的Filter拦截异步Servlet,不要搞错。
我们需要预先定义这么一个异步连接,每秒输出一个数字字符串,从0到99,诸如下面HTML字符串:
<div>2</div>
最后输出Done!
给出两个访问地址,一个用于被拦截(/demoAsyncLink),一个用于单独访问(/demoAsyncLink2),便于对照:
/**
 * 模拟长连接实现,每秒输出一些信息
 * 
 * @author yongboy
 * @date 2011-1-14
 * @version 1.0
 */
@WebServlet(
 urlPatterns = { "/demoAsyncLink", "/demoAsyncLink2" }, 
 asyncSupported = true
)
public class DemoAsyncLinkServlet extends HttpServlet {
 private static final long serialVersionUID = 4617227991063927036L;
 protected void doGet(HttpServletRequest request,
   HttpServletResponse response) throws ServletException, IOException {
  response.setHeader("Cache-Control", "private");
  response.setHeader("Pragma", "no-cache");
  response.setHeader("Connection", "Keep-Alive");
  response.setHeader("Proxy-Connection", "Keep-Alive");
  response.setContentType("text/html;charset=UTF-8");
  PrintWriter out = response.getWriter();
  out.println("<div>Start ...</div>");
  out.flush();
  AsyncContext asyncContext = request.startAsync(request, response);
  new CounterThread(asyncContext).start();
 }
 private static class CounterThread extends Thread {
  private AsyncContext asyncContext;
  public CounterThread(AsyncContext asyncContext) {
   this.asyncContext = asyncContext;
  }
  @Override
  public void run() {
   int num = 0;
   int max = 100;
   int interval = 1000;
   // 必须设置过期时间,否则将会出连接过期,线程无法运行完毕异常
   asyncContext.setTimeout((max + 1) * interval);
   PrintWriter out = null;
   try {
    try {
     out = asyncContext.getResponse().getWriter();
    } catch (IOException e) {
     e.printStackTrace();
    }
    while (true) {
     out.println("<div>" + (num++) + "</div>");
     out.flush();
     if (num >= max) {
      break;
     }
     Thread.sleep(interval);
    }
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   if (out != null) {
    out.println("<div>Done !</div>");
    out.flush();
    out.close();
   }
   asyncContext.complete();
  }
 }
}
若想让HttpServletResponse包装器发挥包装的效果,须调用带有参数的startAsync(request, response)方法开启异步输出,否则MarkWapperedResponse将不起作用。因为,若不传递现有的request,response对象,将会调用原生的request和response对象。
在tomcat7下面,异步连接超时时间为10000单位,若不指定超时时间,递增的数字不会按照预想完整输出到99。
我们假设需要定义这样一个Filter,为每一次的异步输出的内容增加一个特殊标记:
<div>2</div>
逻辑很简单,作为示范也不需要多复杂。
再看看一个异步Filter的代码:
/**
 * 异步拦截器
 * 
 * @author yongboy
 * @date 2011-1-14
 * @version 1.0
 */
@WebFilter(
 dispatcherTypes = {
   DispatcherType.REQUEST, 
   DispatcherType.FORWARD,
   DispatcherType.INCLUDE
 }, 
 urlPatterns = { "/demoAsyncLink" }, 
 asyncSupported = true //支持异步Servlet
)
public class AsyncServletFilter implements Filter {
 private Log log = LogFactory.getLog(AsyncServletFilter.class);
 public AsyncServletFilter() {
 }
 public void destroy() {
 }
 public void doFilter(ServletRequest request, ServletResponse response,
   FilterChain chain) throws IOException, ServletException {
  log.info("it was filted now");
  MarkWapperedResponse wapper = new MarkWapperedResponse(
    (HttpServletResponse) response);
  chain.doFilter(request, wapper);
 }
 public void init(FilterConfig fConfig) throws ServletException {
 }
}
很简单,添加上asyncSupported = true属性即可。在上面Filter中包装了一个HttpServletResponse对象,目的在于返回一个定制的PrintWriter对象,简单重写flush方法(不见得方法多好):
/**
 * HttpServletResponse简单包装器,逻辑简单
 * 
 * @author yongboy
 * @date 2011-1-14
 * @version 1.0
 */
public class MarkWapperedResponse extends HttpServletResponseWrapper {
 private PrintWriter writer = null;
 private static final String MARKED_STRING = "<!--marked filter--->";
 public MarkWapperedResponse(HttpServletResponse resp) throws IOException {
  super(resp);
  
  writer = new MarkPrintWriter(super.getOutputStream());
 }
 @Override
 public PrintWriter getWriter() throws UnsupportedEncodingException {
  return writer;
 }
 private static class MarkPrintWriter extends PrintWriter{
  public MarkPrintWriter(OutputStream out) {
   super(out);
  }
  @Override
  public void flush() {
   super.flush();
   super.println(MARKED_STRING);
  }
 }
}
在浏览器端请求被包装的/demoAsyncLink链接,截图以及firebug检测截图如下:
可以在浏览器内同时请求/demoAsyncLink2前后作为对比一下。
