diff --git a/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java b/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java index d4c9102fd15..41826d1bdbd 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/support/ErrorPageFilter.java @@ -18,8 +18,12 @@ package org.springframework.boot.web.support; import java.io.IOException; import java.io.PrintWriter; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -40,6 +44,7 @@ import org.springframework.boot.web.servlet.ErrorPageRegistrar; import org.springframework.boot.web.servlet.ErrorPageRegistry; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.util.ClassUtils; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.NestedServletException; @@ -77,6 +82,14 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { private static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code"; + private static final Set> CLIENT_ABORT_EXCEPTIONS; + static { + Set> clientAbortExceptions = new HashSet<>(); + addClassIfPresent(clientAbortExceptions, + "org.apache.catalina.connector.ClientAbortException"); + CLIENT_ABORT_EXCEPTIONS = Collections.unmodifiableSet(clientAbortExceptions); + } + private String global; private final Map statuses = new HashMap(); @@ -164,7 +177,6 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { handleCommittedResponse(request, ex); return; } - forwardToErrorPage(errorPath, request, wrapped, ex); } @@ -200,6 +212,9 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { } private void handleCommittedResponse(HttpServletRequest request, Throwable ex) { + if (isClientAbortException(ex)) { + return; + } String message = "Cannot forward to error page for request " + getDescription(request) + " as the response has already been" + " committed. As a result, the response may have the wrong status" @@ -216,6 +231,18 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { } } + private boolean isClientAbortException(Throwable ex) { + if (ex == null) { + return false; + } + for (Class candidate : CLIENT_ABORT_EXCEPTIONS) { + if (candidate.isInstance(ex)) { + return true; + } + } + return isClientAbortException(ex.getCause()); + } + private String getErrorPath(Map map, Integer status) { if (map.containsKey(status)) { return map.get(status); @@ -276,6 +303,15 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { public void destroy() { } + private static void addClassIfPresent(Collection> collection, + String className) { + try { + collection.add(ClassUtils.forName(className, null)); + } + catch (Throwable ex) { + } + } + private static class ErrorWrapperResponse extends HttpServletResponseWrapper { private int status; diff --git a/spring-boot/src/test/java/org/springframework/boot/web/support/ErrorPageFilterTests.java b/spring-boot/src/test/java/org/springframework/boot/web/support/ErrorPageFilterTests.java index 15eb5d471c3..ed60e84ca8c 100644 --- a/spring-boot/src/test/java/org/springframework/boot/web/support/ErrorPageFilterTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/web/support/ErrorPageFilterTests.java @@ -28,6 +28,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; +import org.apache.catalina.connector.ClientAbortException; import org.junit.Rule; import org.junit.Test; @@ -150,6 +151,25 @@ public class ErrorPageFilterTests { assertThat(this.response.isCommitted()).isTrue(); } + @Test + public void responseCommittedWhenFromClientAbortException() throws Exception { + this.filter.addErrorPages(new ErrorPage("/error")); + this.response.setCommitted(true); + this.chain = new MockFilterChain() { + + @Override + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + super.doFilter(request, response); + throw new ClientAbortException(); + } + + }; + this.filter.doFilter(this.request, this.response, this.chain); + assertThat(this.response.isCommitted()).isTrue(); + assertThat(this.output.toString()).doesNotContain("Cannot forward"); + } + @Test public void responseUncommittedWithoutErrorPage() throws Exception { this.chain = new MockFilterChain() {