Attempt to reset Servlet response in DispatcherServlet
This is a follow-up change related to gh-31104. This change reverts the changes previously made in `ExceptionHandlerExceptionResolver` and instead attempts to reset the response directly in `DispatcherServlet` in order to cover all types or exception handling. Unlike the previous change, we decided to continue even if the response was already committed: exception handlers will have a chance to be called, even if it means they'll have to operate on a garbled response. This change will cause less disruption, in case existing exception handlers were relying on this behavior. See gh-31104
This commit is contained in:
parent
32f128b6ba
commit
0d7c9b7c93
|
@ -1338,6 +1338,13 @@ public class DispatcherServlet extends FrameworkServlet {
|
|||
|
||||
// Success and error responses may use different content types
|
||||
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
|
||||
// Reset the response if the response is not committed already
|
||||
try {
|
||||
response.reset();
|
||||
}
|
||||
catch (IllegalStateException illegalStateException) {
|
||||
// the response is already committed, leave it to exception handlers anyway
|
||||
}
|
||||
|
||||
// Check registered HandlerExceptionResolvers...
|
||||
ModelAndView exMv = null;
|
||||
|
|
|
@ -401,9 +401,9 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
|
|||
|
||||
ServletWebRequest webRequest = new ServletWebRequest(request, response);
|
||||
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
|
||||
|
||||
ArrayList<Throwable> exceptions = new ArrayList<>();
|
||||
try {
|
||||
response.reset();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
|
||||
}
|
||||
|
|
|
@ -70,12 +70,15 @@ import org.springframework.web.util.pattern.PathPatternParser;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DispatcherServlet}.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
|
@ -887,6 +890,39 @@ public class DispatcherServletTests {
|
|||
assertThat(response.getContentAsString()).isEqualTo("body");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldResetResponseIfNotCommitted() throws Exception {
|
||||
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||
context.setServletContext(getServletContext());
|
||||
context.registerSingleton("/error", ErrorController.class);
|
||||
DispatcherServlet servlet = new DispatcherServlet(context);
|
||||
servlet.init(servletConfig);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/error");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
assertThatThrownBy(() -> servlet.service(request, response)).isInstanceOf(ServletException.class)
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(response.getContentAsByteArray()).isEmpty();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAttemptToResetResponseIfCommitted() throws Exception {
|
||||
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||
context.setServletContext(getServletContext());
|
||||
context.registerSingleton("/error", ErrorController.class);
|
||||
DispatcherServlet servlet = new DispatcherServlet(context);
|
||||
servlet.init(servletConfig);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/error");
|
||||
request.setAttribute("commit", true);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
assertThatThrownBy(() -> servlet.service(request, response)).isInstanceOf(ServletException.class)
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class);
|
||||
assertThat(response.getContentAsByteArray()).isNotEmpty();
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
}
|
||||
|
||||
|
||||
public static class ControllerFromParent implements Controller {
|
||||
|
||||
|
@ -934,4 +970,16 @@ public class DispatcherServletTests {
|
|||
}
|
||||
}
|
||||
|
||||
private static class ErrorController implements Controller {
|
||||
@Override
|
||||
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
response.setStatus(400);
|
||||
if (request.getAttribute("commit") != null) {
|
||||
response.flushBuffer();
|
||||
}
|
||||
response.getWriter().write("error");
|
||||
throw new IllegalArgumentException("test error");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -385,38 +385,6 @@ public class ExceptionHandlerExceptionResolverTests {
|
|||
assertExceptionHandledAsBody(mav, "DefaultTestExceptionResolver: IllegalStateException");
|
||||
}
|
||||
|
||||
@Test // gh-30702
|
||||
void attemptToResetResponseBeforeResolveException() throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
|
||||
this.resolver.setMappedHandlerClasses(HttpRequestHandler.class);
|
||||
this.resolver.setApplicationContext(ctx);
|
||||
this.resolver.afterPropertiesSet();
|
||||
|
||||
IllegalStateException ex = new IllegalStateException();
|
||||
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
|
||||
this.response.getWriter().print("test");
|
||||
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handler, ex);
|
||||
|
||||
assertExceptionHandledAsBody(mav, "DefaultTestExceptionResolver: IllegalStateException");
|
||||
}
|
||||
|
||||
@Test // gh-30702
|
||||
void attemptToResetResponseBeforeResolveExceptionFails() throws Exception {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
|
||||
this.resolver.setMappedHandlerClasses(HttpRequestHandler.class);
|
||||
this.resolver.setApplicationContext(ctx);
|
||||
this.resolver.afterPropertiesSet();
|
||||
|
||||
IllegalStateException ex = new IllegalStateException();
|
||||
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
|
||||
this.response.getWriter().print("test");
|
||||
this.response.setCommitted(true);
|
||||
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handler, ex);
|
||||
|
||||
assertThat(mav).as("Exception was handled").isNull();
|
||||
assertThat(this.response.getContentAsString()).isEqualTo("test");
|
||||
}
|
||||
|
||||
|
||||
private void assertMethodProcessorCount(int resolverCount, int handlerCount) {
|
||||
assertThat(this.resolver.getArgumentResolvers().getResolvers()).hasSize(resolverCount);
|
||||
|
|
Loading…
Reference in New Issue