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