Commit a06de4d9 authored by Andy Wilkinson's avatar Andy Wilkinson

Stop error page filter from commiting response prematurely

Previously, the error page filter used sendError to set the response
status when handling an exception and before forwarding the request
to the error controller. Following the fix for gh-11814, this meant
that the error controller was unable to write its response and the
containers default error page was returned instead.

This commit updates the error page filter to use setStatus rather than
sendError. This ensures that the response has the correct status code
while allowing the error controller to write its body. Tests have
been added to the Tomcat deployment test suite to verify that the
error page filter behaves as intended when dealing with a sent error
and an exception for requests accepting HTML, JSON, or anything.

Closes gh-12787
parent 3634a1d9
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
package sample; package sample;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
...@@ -27,4 +31,14 @@ public class SampleController { ...@@ -27,4 +31,14 @@ public class SampleController {
return "Hello World"; return "Hello World";
} }
@RequestMapping("/send-error")
public void sendError(HttpServletResponse response) throws IOException {
response.sendError(500);
}
@RequestMapping("/exception")
public void exception() {
throw new RuntimeException();
}
} }
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,10 +16,14 @@ ...@@ -16,10 +16,14 @@
package sample; package sample;
import java.net.URI;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -29,15 +33,83 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -29,15 +33,83 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class SampleTomcatDeployApplicationIT { public class SampleTomcatDeployApplicationIT {
private final TestRestTemplate rest = new TestRestTemplate();
private int port = Integer.valueOf(System.getProperty("port")); private int port = Integer.valueOf(System.getProperty("port"));
@Test @Test
public void testHome() throws Exception { public void testHome() throws Exception {
String url = "http://localhost:" + this.port + "/bootapp/"; String url = "http://localhost:" + this.port + "/bootapp/";
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(url, ResponseEntity<String> entity = this.rest.getForEntity(url, String.class);
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).isEqualTo("Hello World"); assertThat(entity.getBody()).isEqualTo("Hello World");
} }
@Test
public void errorFromExceptionForRequestAcceptingAnythingProducesAJsonResponse()
throws Exception {
assertThatErrorFromExceptionProducesExpectedResponse(MediaType.ALL,
MediaType.APPLICATION_JSON);
}
@Test
public void errorFromExceptionForRequestAcceptingJsonProducesAJsonResponse()
throws Exception {
assertThatErrorFromExceptionProducesExpectedResponse(MediaType.APPLICATION_JSON,
MediaType.APPLICATION_JSON);
}
@Test
public void errorFromExceptionForRequestAcceptingHtmlProducesAnHtmlResponse()
throws Exception {
assertThatErrorFromExceptionProducesExpectedResponse(MediaType.TEXT_HTML,
MediaType.TEXT_HTML);
}
@Test
public void sendErrorForRequestAcceptingAnythingProducesAJsonResponse()
throws Exception {
assertThatSendErrorProducesExpectedResponse(MediaType.ALL,
MediaType.APPLICATION_JSON);
}
@Test
public void sendErrorForRequestAcceptingJsonProducesAJsonResponse() throws Exception {
assertThatSendErrorProducesExpectedResponse(MediaType.APPLICATION_JSON,
MediaType.APPLICATION_JSON);
}
@Test
public void sendErrorForRequestAcceptingHtmlProducesAnHtmlResponse()
throws Exception {
assertThatSendErrorProducesExpectedResponse(MediaType.TEXT_HTML,
MediaType.TEXT_HTML);
}
private void assertThatSendErrorProducesExpectedResponse(MediaType accept,
MediaType contentType) {
RequestEntity<Void> request = RequestEntity
.get(URI.create("http://localhost:" + this.port + "/bootapp/send-error"))
.accept(accept).build();
ResponseEntity<String> response = this.rest.exchange(request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(contentType.isCompatibleWith(response.getHeaders().getContentType()))
.as("%s is compatible with %s", contentType,
response.getHeaders().getContentType())
.isTrue();
}
private void assertThatErrorFromExceptionProducesExpectedResponse(MediaType accept,
MediaType contentType) {
RequestEntity<Void> request = RequestEntity
.get(URI.create("http://localhost:" + this.port + "/bootapp/exception"))
.accept(accept).build();
ResponseEntity<String> response = this.rest.exchange(request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(contentType.isCompatibleWith(response.getHeaders().getContentType()))
.as("%s is compatible with %s", contentType,
response.getHeaders().getContentType())
.isTrue();
}
} }
...@@ -181,7 +181,7 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry { ...@@ -181,7 +181,7 @@ public class ErrorPageFilter implements Filter, ErrorPageRegistry {
request.setAttribute(ERROR_EXCEPTION, ex); request.setAttribute(ERROR_EXCEPTION, ex);
request.setAttribute(ERROR_EXCEPTION_TYPE, ex.getClass()); request.setAttribute(ERROR_EXCEPTION_TYPE, ex.getClass());
response.reset(); response.reset();
response.sendError(500, ex.getMessage()); response.setStatus(500);
request.getRequestDispatcher(path).forward(request, response); request.getRequestDispatcher(path).forward(request, response);
request.removeAttribute(ERROR_EXCEPTION); request.removeAttribute(ERROR_EXCEPTION);
request.removeAttribute(ERROR_EXCEPTION_TYPE); request.removeAttribute(ERROR_EXCEPTION_TYPE);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment