Spring Async DeferredResult Not Working in Tomcat

2019-04-02 04:31发布

I created an async MVC application using Spring 4.0.5 and Servlet API 3.1.0. The async behavior works well in Jetty 8.0 using Firefox 24 but I cannot get it to work in Tomcat 8.0 and Firefox 24. I'm using DeferredResult to manage the async requests. Any idea what I'm missing? It mus be some Tomcat setting or something in the web.xml since the exact same Java code works well in Jetty.

When the async request finally has result and supposedly writes to the response, I see the following messages logged:

WebAsyncManager - Dispatching request to resume processing
RequestResponseBodyMethodProcessor - Written [true] as "application/json" using MappingJacksonHttpMessageConvertor
DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'app': assuming HandlerAdapter completed request handling
DispatcherServlet - Successfully completed request

The long running request never comes back to my browser, and eventually i see this timeoutout error in the Tomcat log:

CoyoteAdapter.asyncDispatch Exception while processing an asynchronous request
java.lang.IllegalStateException: Calling [asyncTimeout()] is not valid for a request with Async state [Dispatching]

--server.xml--

    <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" 
        maxConnections="100"
               maxThreads="100"
               connectionTimeout="150000" 
               asyncTimeout="150000" />

--Tomcat web.xml--

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>  


    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- The mappings for the JSP servlet -->
    <servlet-mapping>
        <servlet-name>jsp</servlet-name>
        <url-pattern>*.jsp</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>    
</web-app>

--Spring web-app web.xml--

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">    

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>webAppRootKey</param-name>
        <param-value>my-async-app</param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/app-config.xml</param-value>
    </context-param>

    <!-- Handles all requests into the application -->
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/controllers-config.xml</param-value> 
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <!-- Maps all /app requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

1条回答
Melony?
2楼-- · 2019-04-02 05:06

This issue is related to a bug in Tomcat described here, here and here.

Possible solutions:

  1. Use a more stable Tomcat version such as Tomcat 7.0.47 where this bug has been fixed.
  2. Use more advanced dispatcher such as org.springframework.web.servlet.DispatcherServlet
  3. Override the HttpServlet as suggested here:

    public class AsyncServlet extends HttpServlet {
    
        protected void doGet(final HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            if (request.isAsyncStarted()) {
                response.getWriter().write("asyncResult=" + request.getAttribute("asyncResult"));
            } else {
                final AsyncContext asyncContext = request.startAsync(request, response);
    
                asyncContext.addListener(new AsyncListener() {
                    public void onTimeout(AsyncEvent event) throws IOException {
                        request.setAttribute("asyncResult", "timeout\n");
                        asyncContext.dispatch();
                    }
    
                    public void onStartAsync(AsyncEvent event) throws IOException {
                    }
    
                    public void onError(AsyncEvent event) throws IOException {
                    }
    
                    public void onComplete(AsyncEvent event) throws IOException {
                    }
                });
    
                asyncContext.setTimeout(5000L);
            }
        }
    }
    
查看更多
登录 后发表回答