Author: markt
Date: Fri Dec  5 10:54:21 2014
New Revision: 1643232

URL: http://svn.apache.org/viewvc?rev=1643232&view=rev
Log:
Fix https://issues.apache.org/bugzilla/show_bug.cgi?id=57252
Provide application configured error pages with a chance to handle an
async error before the built-in error reporting.

Modified:
    tomcat/tc8.0.x/trunk/   (props changed)
    tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardHostValve.java
    tomcat/tc8.0.x/trunk/java/org/apache/catalina/valves/ErrorReportValve.java
    tomcat/tc8.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java
    tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml

Propchange: tomcat/tc8.0.x/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Dec  5 10:54:21 2014
@@ -1 +1 @@
-/tomcat/trunk:1636524,1637156,1637176,1637188,1637331,1637684,1637695,1638720-1638725,1639653,1640010,1640084,1640088,1640275,1640322,1640347,1640361,1640365,1640403,1640410,1640652,1640655-1640658,1640688,1640700-1640883,1640903,1640976,1640978,1641000,1641026,1641038-1641039,1641051-1641052,1641058,1641064,1641300,1641369,1641374,1641380,1641486,1641634,1641656-1641692,1641704,1641707-1641718,1641720-1641722,1641735,1641981,1642668,1642679,1642697,1642699,1643002,1643066,1643121,1643206,1643216
+/tomcat/trunk:1636524,1637156,1637176,1637188,1637331,1637684,1637695,1638720-1638725,1639653,1640010,1640084,1640088,1640275,1640322,1640347,1640361,1640365,1640403,1640410,1640652,1640655-1640658,1640688,1640700-1640883,1640903,1640976,1640978,1641000,1641026,1641038-1641039,1641051-1641052,1641058,1641064,1641300,1641369,1641374,1641380,1641486,1641634,1641656-1641692,1641704,1641707-1641718,1641720-1641722,1641735,1641981,1642668,1642679,1642697,1642699,1643002,1643066,1643121,1643206,1643209-1643210,1643216

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardHostValve.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardHostValve.java?rev=1643232&r1=1643231&r2=1643232&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardHostValve.java 
(original)
+++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/core/StandardHostValve.java 
Fri Dec  5 10:54:21 2014
@@ -121,9 +121,6 @@ final class StandardHostValve extends Va
 
         boolean asyncAtStart = request.isAsync();
         boolean asyncDispatching = request.isAsyncDispatching();
-        // An async error page may dispatch to another resource. This flag 
helps
-        // ensure an infinite error handling loop is not entered
-        boolean errorAtStart = response.isError();
 
         try {
             context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
@@ -136,18 +133,26 @@ final class StandardHostValve extends Va
                 return;
             }
 
-            // Ask this Context to process this request
+            // Ask this Context to process this request. Requests that are in
+            // async mode and are not being dispatched to this resource must be
+            // in error and have been routed here to check for application
+            // defined error pages.
             try {
                 if (!asyncAtStart || asyncDispatching) {
                     context.getPipeline().getFirst().invoke(request, response);
                 } else {
-                    if (!errorAtStart) {
+                    // Make sure this request/response is here because an error
+                    // report is required.
+                    if (!response.isErrorReportRequired()) {
                         throw new 
IllegalStateException(sm.getString("standardHost.asyncStateError"));
                     }
                 }
             } catch (Throwable t) {
                 ExceptionUtils.handleThrowable(t);
-                if (errorAtStart) {
+                // If a new error occurred while trying to report a previous
+                // error simply log the new error and allow the original error
+                // to be reported.
+                if (response.isErrorReportRequired()) {
                     container.getLogger().error("Exception Processing " +
                             request.getRequestURI(), t);
                 } else {
@@ -158,28 +163,27 @@ final class StandardHostValve extends Va
 
             Throwable t = (Throwable) 
request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
 
-            // If the request was async at the start and an error occurred
-            // then the async error handling will kick-in and that will fire
-            // the request destroyed event *after* the error handling has
-            // taken place.
-            if (!(request.isAsync() || (asyncAtStart && t != null))) {
-                // Protect against NPEs if context was destroyed during a
-                // long running request.
-                if (context.getState().isAvailable()) {
-                    if (!errorAtStart) {
-                        // Error page processing
-                        response.setSuspended(false);
-
-                        if (t != null) {
-                            throwable(request, response, t);
-                        } else {
-                            status(request, response);
-                        }
-                    }
+            // Protect against NPEs if the context was destroyed during a
+            // long running request.
+            if (!context.getState().isAvailable()) {
+                return;
+            }
+
+            // Look for (and render if found) an application level error page
+            if (response.isErrorReportRequired()) {
+                // Error page processing
+                response.setSuspended(false);
 
-                    context.fireRequestDestroyEvent(request);
+                if (t != null) {
+                    throwable(request, response, t);
+                } else {
+                    status(request, response);
                 }
             }
+
+            if (!request.isAsync() && !response.isErrorReportRequired()) {
+                context.fireRequestDestroyEvent(request);
+            }
         } finally {
             // Access a session (if present) to update last accessed time, 
based
             // on a strict interpretation of the specification

Modified: 
tomcat/tc8.0.x/trunk/java/org/apache/catalina/valves/ErrorReportValve.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/java/org/apache/catalina/valves/ErrorReportValve.java?rev=1643232&r1=1643231&r2=1643232&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/java/org/apache/catalina/valves/ErrorReportValve.java 
(original)
+++ tomcat/tc8.0.x/trunk/java/org/apache/catalina/valves/ErrorReportValve.java 
Fri Dec  5 10:54:21 2014
@@ -97,8 +97,10 @@ public class ErrorReportValve extends Va
 
         Throwable throwable = (Throwable) 
request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
 
-        if (request.isAsyncStarted() && ((response.getStatus() < 400 &&
-                throwable == null) || request.isAsyncDispatching())) {
+        // If an async request is in progress and is not going to end once this
+        // container thread finishes, do not trigger error page handling - it
+        // will be triggered later if required.
+        if (request.isAsync() && !request.isAsyncCompleting()) {
             return;
         }
 
@@ -122,11 +124,6 @@ public class ErrorReportValve extends Va
         } catch (Throwable tt) {
             ExceptionUtils.handleThrowable(tt);
         }
-
-        if (request.isAsyncStarted() && !request.isAsyncCompleting() &&
-                !request.isAsyncDispatching()) {
-            request.getAsyncContext().complete();
-        }
     }
 
 

Modified: 
tomcat/tc8.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java?rev=1643232&r1=1643231&r2=1643232&view=diff
==============================================================================
--- 
tomcat/tc8.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java 
(original)
+++ 
tomcat/tc8.0.x/trunk/test/org/apache/catalina/core/TestAsyncContextImpl.java 
Fri Dec  5 10:54:21 2014
@@ -1243,15 +1243,25 @@ public class TestAsyncContextImpl extend
 
     @Test
     public void testBug51197a() throws Exception {
-        doTestBug51197(false);
+        doTestBug51197(false, false);
     }
 
     @Test
     public void testBug51197b() throws Exception {
-        doTestBug51197(true);
+        doTestBug51197(true, false);
     }
 
-    private void doTestBug51197(boolean threaded) throws Exception {
+    @Test
+    public void testBug51197c() throws Exception {
+        doTestBug51197(false, true);
+    }
+
+    @Test
+    public void testBug51197d() throws Exception {
+        doTestBug51197(true, true);
+    }
+
+    private void doTestBug51197(boolean threaded, boolean customError) throws 
Exception {
         // Setup Tomcat instance
         Tomcat tomcat = getTomcatInstance();
 
@@ -1265,6 +1275,17 @@ public class TestAsyncContextImpl extend
         wrapper.setAsyncSupported(true);
         ctx.addServletMapping("/asyncErrorServlet", "asyncErrorServlet");
 
+        if (customError) {
+            CustomErrorServlet customErrorServlet = new CustomErrorServlet();
+            Tomcat.addServlet(ctx, "customErrorServlet", customErrorServlet);
+            ctx.addServletMapping("/customErrorServlet", "customErrorServlet");
+
+            ErrorPage ep = new ErrorPage();
+            ep.setLocation("/customErrorServlet");
+
+            ctx.addErrorPage(ep);
+        }
+
         TesterAccessLogValve alv = new TesterAccessLogValve();
         ctx.getPipeline().addValve(alv);
 
@@ -1284,7 +1305,14 @@ public class TestAsyncContextImpl extend
         // responsibility when an error occurs on an application thread.
         // Calling sendError() followed by complete() and expecting the 
standard
         // error page mechanism to kick in could be viewed as handling the 
error
-        assertTrue(res.getLength() > 0);
+        String responseBody = res.toString();
+        Assert.assertNotNull(responseBody);
+        assertTrue(responseBody.length() > 0);
+        if (customError) {
+            assertTrue(responseBody, 
responseBody.contains(CustomErrorServlet.ERROR_MESSAGE));
+        } else {
+            assertTrue(responseBody, 
responseBody.contains(AsyncErrorServlet.ERROR_MESSAGE));
+        }
 
         // Without this test may complete before access log has a chance to log
         // the request
@@ -1295,6 +1323,21 @@ public class TestAsyncContextImpl extend
                 REQUEST_TIME);
     }
 
+    private static class CustomErrorServlet extends GenericServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        public static final String ERROR_MESSAGE = "Custom error page";
+
+        @Override
+        public void service(ServletRequest req, ServletResponse res)
+                throws ServletException, IOException {
+            res.getWriter().println(ERROR_MESSAGE);
+        }
+
+    }
+
+
     private static class AsyncErrorServlet extends HttpServlet {
 
         private static final long serialVersionUID = 1L;
@@ -1310,7 +1353,7 @@ public class TestAsyncContextImpl extend
         }
 
         @Override
-        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+        protected void doGet(HttpServletRequest req, final HttpServletResponse 
resp)
                 throws ServletException, IOException {
 
             final AsyncContext actxt = req.startAsync();
@@ -1320,11 +1363,7 @@ public class TestAsyncContextImpl extend
                     @Override
                     public void run() {
                         try {
-                            HttpServletResponse resp =
-                                    (HttpServletResponse) actxt.getResponse();
                             resp.sendError(status, ERROR_MESSAGE);
-                            // Complete so there is no delay waiting for the
-                            // timeout
                             actxt.complete();
                         } catch (IOException e) {
                             // Ignore
@@ -1332,7 +1371,8 @@ public class TestAsyncContextImpl extend
                     }
                 });
             } else {
-                resp.sendError(status);
+                resp.sendError(status, ERROR_MESSAGE);
+                actxt.complete();
             }
         }
     }

Modified: tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml?rev=1643232&r1=1643231&r2=1643232&view=diff
==============================================================================
--- tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc8.0.x/trunk/webapps/docs/changelog.xml Fri Dec  5 10:54:21 2014
@@ -98,6 +98,11 @@
         thread can not change the async state until the container thread has
         completed. (markt)
       </fix>
+      <fix>
+        <bug>57252</bug>: Provide application configured error pages with a
+        chance to handle an async error before the built-in error reporting.
+        (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