Author: markt
Date: Mon Jan  4 18:12:07 2016
New Revision: 1722939

URL: http://svn.apache.org/viewvc?rev=1722939&view=rev
Log:
Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=58751
Correctly handle the case where an AsyncListener dispatches to a Servlet on an 
asynchronous timeout and the Servlet uses <code>sendError()</code> to trigger 
an error page.
Test case based on code provided by Andy Wilkinson.

Modified:
    tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java
    tomcat/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java?rev=1722939&r1=1722938&r2=1722939&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java (original)
+++ tomcat/trunk/java/org/apache/catalina/core/AsyncContextImpl.java Mon Jan  4 
18:12:07 2016
@@ -97,6 +97,29 @@ public class AsyncContextImpl implements
 
     @Override
     public void fireOnComplete() {
+        // Fire the listeners
+        doFireOnComplete();
+
+        // The application doesn't know it has to stop read and/or writing 
until
+        // it receives the complete event so the request and response have to 
be
+        // closed after firing the event.
+        try {
+            // First of all ensure that any data written to the response is
+            // written to the I/O layer.
+            request.getResponse().finishResponse();
+            // Close the request and the response.
+            request.getCoyoteRequest().action(ActionCode.END_REQUEST, null);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // Catch this here and allow async context complete to continue
+            // normally so a dispatch takes place which ensures that  the
+            // request and response objects are correctly recycled.
+            log.debug(sm.getString("asyncContextImpl.finishResponseError"), t);
+        }
+    }
+
+
+    private void doFireOnComplete() {
         List<AsyncListenerWrapper> listenersCopy = new ArrayList<>();
         listenersCopy.addAll(listeners);
 
@@ -115,25 +138,9 @@ public class AsyncContextImpl implements
             clearServletRequestResponse();
             context.unbind(Globals.IS_SECURITY_ENABLED, oldCL);
         }
-
-        // The application doesn't know it has to stop read and/or writing 
until
-        // it receives the complete event so the request and response have to 
be
-        // closed after firing the event.
-        try {
-            // First of all ensure that any data written to the response is
-            // written to the I/O layer.
-            request.getResponse().finishResponse();
-            // Close the request and the response.
-            request.getCoyoteRequest().action(ActionCode.END_REQUEST, null);
-        } catch (Throwable t) {
-            ExceptionUtils.handleThrowable(t);
-            // Catch this here and allow async context complete to continue
-            // normally so a dispatch takes place which ensures that  the
-            // request and response objects are correctly recycled.
-            log.debug(sm.getString("asyncContextImpl.finishResponseError"), t);
-        }
     }
 
+
     public boolean timeout() {
         AtomicBoolean result = new AtomicBoolean();
         request.getCoyoteRequest().action(ActionCode.ASYNC_TIMEOUT, result);
@@ -383,7 +390,9 @@ public class AsyncContextImpl implements
             dispatch = null;
             runnable.run();
             if (!request.isAsync()) {
-                fireOnComplete();
+                // Uses internal method since we don't want the 
request/response
+                // to be closed. That will be handled in the adapter.
+                doFireOnComplete();
             }
         } catch (RuntimeException x) {
             // doInternalComplete(true);

Modified: tomcat/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java?rev=1722939&r1=1722938&r2=1722939&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java 
(original)
+++ tomcat/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java Mon 
Jan  4 18:12:07 2016
@@ -1347,7 +1347,7 @@ public class TestAsyncContextImpl extend
         @Override
         public void service(ServletRequest req, ServletResponse res)
                 throws ServletException, IOException {
-            res.getWriter().println(ERROR_MESSAGE);
+            res.getWriter().print(ERROR_MESSAGE);
         }
 
     }
@@ -2288,4 +2288,74 @@ public class TestAsyncContextImpl extend
             }
         }
     }
+
+
+    /*
+     * See https://bz.apache.org/bugzilla/show_bug.cgi?id=58751 comment 1
+     */
+    @Test
+    public void testTimeoutDispatchCustomErrorPage() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        Context context = tomcat.addContext("", null);
+        tomcat.addServlet("", "timeout", Bug58751AsyncServlet.class.getName())
+                .setAsyncSupported(true);
+        CustomErrorServlet customErrorServlet = new CustomErrorServlet();
+        Tomcat.addServlet(context, "customErrorServlet", customErrorServlet);
+        context.addServletMapping("/timeout", "timeout");
+        context.addServletMapping("/error", "customErrorServlet");
+        ErrorPage errorPage = new ErrorPage();
+        errorPage.setLocation("/error");
+        context.addErrorPage(errorPage);
+        tomcat.start();
+
+        ByteChunk responseBody = new ByteChunk();
+        int rc = getUrl("http://localhost:"; + getPort() + "/timeout", 
responseBody, null);
+
+        Assert.assertEquals(503, rc);
+        Assert.assertEquals(CustomErrorServlet.ERROR_MESSAGE, 
responseBody.toString());
+    }
+
+
+    public static class Bug58751AsyncServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            if (req.getAttribute("timeout") != null) {
+                resp.sendError(503);
+            }
+            else {
+                final AsyncContext context = req.startAsync();
+                context.setTimeout(5000);
+                context.addListener(new AsyncListener() {
+
+                    @Override
+                    public void onTimeout(AsyncEvent event) throws IOException 
{
+                        HttpServletResponse response = (HttpServletResponse) 
event
+                                .getSuppliedResponse();
+                        if (!response.isCommitted()) {
+                            ((HttpServletRequest) event.getSuppliedRequest())
+                                    .setAttribute("timeout", Boolean.TRUE);
+                            context.dispatch();
+                        }
+                    }
+
+                    @Override
+                    public void onStartAsync(AsyncEvent event) throws 
IOException {
+                    }
+
+                    @Override
+                    public void onError(AsyncEvent event) throws IOException {
+                    }
+
+                    @Override
+                    public void onComplete(AsyncEvent event) throws 
IOException {
+                    }
+                });
+            }
+        }
+
+    }
 }

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1722939&r1=1722938&r2=1722939&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Mon Jan  4 18:12:07 2016
@@ -164,6 +164,13 @@
         <code>org.apache.catalina.core.DefaultInstanceManager</code> class.
         (kkolinko)
       </fix>
+      <fix>
+        <bug>58751</bug>: Correctly handle the case where an
+        <code>AsyncListener</code> dispatches to a Servlet on an asynchronous
+        timeout and the Servlet uses <code>sendError()</code> to trigger an
+        error page. Includes a test case based on code provided by Andy
+        Wilkinson.(markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to