Фильтр Tomcat не поддерживает асинхронность

#java #asynchronous #tomcat #filter

Вопрос:

Например, название

 @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();
     
    }

}
 

Поэтому, если в фильтре Tomcat есть трудоемкие задачи (такие как RPC или HTTP), они должны ждать и не могут быть асинхронными

 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:

Объект ServletRequest , ServletResponse и FilterChain действителен только в потоке, в котором doFilter вызывается. Если вы хотите использовать их асинхронно, вам необходимо включить асинхронную обработку (см. Руководство по Jakarta EE).

ServletRequest.startAsync() переводит ServletRequest и ServletResponse в асинхронный режим, но FilterChain может использоваться только в исходном потоке:

service Метод должен выполняться в том же потоке, что и все фильтры, применяемые к сервлету.

(см. Спецификацию сервлета)

Поэтому вам нужно действовать следующим образом:

  1. Когда новый запрос проходит через фильтр, вы вызываете ServletRequest.startAsync() и запускаете свой новый поток или используете любого другого исполнителя для асинхронной фильтрации (например AsyncContext.start(Runnable) ),
  2. Когда асинхронная задача будет завершена, вы запишете результаты в качестве атрибутов запроса и вызовете AsyncContext.dispatch() : это перезапустит цепочку фильтров с самого начала,
  3. При doFilter повторном вызове вы используете атрибуты запроса для выполнения логики фильтрации и вызова FilterChain.doFilter

Например, вы можете использовать что-то вроде этого:

 @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 :
      }
   }