Tomcat filter does not support asynchrony

Viewed 124

Such as the title

@WebFilter("/*")
@Component
@Slf4j
public class FilterDemo1 implements Filter {

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException, IOException {
        new Thread(() -> {
            try {
                chain.doFilter(req, resp);
            } catch (Exception e) {
                log.error("a", e);
            }
        }).start();
     
    }

}

Therefore, if there are time-consuming tasks (such as RPC or HTTP) in Tomcat filter, they must wait and cannot be asynchronous

java.lang.NullPointerException: null
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.27.jar:9.0.27]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.27.jar:9.0.27]```


[error][1]

  [1]: https://i.stack.imgur.com/tFYTH.png
1 Answers

The ServletRequest, ServletResponse and FilterChain object are valid only on the thread where doFilter is called. If you want to use them asynchronously, you need to enable asynchronous processing (cf. Jakarta EE Tutorial).

ServletRequest.startAsync() puts the ServletRequest and ServletResponse into asynchronous mode, but FilterChain can only be used on the original thread:

The service method is required to run in the same thread as all filters that apply to the servlet.

(cf. Servlet Specification)

Therefore you need to proceed as follow:

  1. When a new request passes through the filter, you call ServletRequest.startAsync() and launch your new thread or use any other executor for your asynchronous filtering (e.g. AsyncContext.start(Runnable)),
  2. When the asynchronous task is finished, you write the results as request attributes and call AsyncContext.dispatch(): this will restart the filter chain from the beginning,
  3. When doFilter is called again, you use the request's attributes to perform your filtering logic and call FilterChain.doFilter

For example you can use something like this:

@WebFilter(asyncSupported = true, urlPatterns = {"/*"}, dispatcherTypes = {DispatcherType.ASYNC, DispatcherType.REQUEST})
public class Filter1 implements Filter {

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      switch (request.getDispatcherType()) {
         case REQUEST :
            // First pass: start asynchronous processing
            final AsyncContext asyncContext = request.startAsync();
            new Thread(() -> {
               try {
                  Thread.currentThread().sleep(5000);
               } catch (Exception e) {
                  request.setAttribute("filter1Error", e);
               }
               asyncContext.dispatch();
            }).start();
            break;
         case ASYNC :
            // Second pass: throw or forward
            Exception e = (Exception) request.getAttribute("filter1Error");
            if (e instanceof IOException) {
               throw (IOException) e;
            } else if (e instanceof ServletException) {
               throw (ServletException) e;
            } else if (e != null) {
               throw new ServletException(e);
            }
            chain.doFilter(request, response);
            break;
         default :
      }
   }
Related