Merge branch '1.1.x'

pull/1627/head
Andy Wilkinson 10 years ago
commit 0c63406b49

@ -955,8 +955,8 @@ Spring Boot includes auto-configuration support for the following templating eng
* http://www.thymeleaf.org[Thymeleaf] * http://www.thymeleaf.org[Thymeleaf]
* http://velocity.apache.org[Velocity] * http://velocity.apache.org[Velocity]
When you're using one of these templating engines with the default configuration, your templates When you're using one of these templating engines with the default configuration, your
will be picked up automatically from `src/main/resources/templates`. templates will be picked up automatically from `src/main/resources/templates`.
TIP: JSPs should be avoided if possible, there are several TIP: JSPs should be avoided if possible, there are several
<<boot-features-jsp-limitations, known limitations>> when using them with embedded <<boot-features-jsp-limitations, known limitations>> when using them with embedded
@ -991,36 +991,45 @@ support a uniform Java DSL for customizing the error handling. For example:
@Override @Override
public void customize(ConfigurableEmbeddedServletContainer container) { public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400")); container.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
} }
} }
---- ----
You can also use regular Spring MVC features like http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-exception-handlers[`@ExceptionHandler` You can also use regular Spring MVC features like
methods] and http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-controller-advice[`@ControllerAdvice`]. {spring-reference}/#mvc-exceptionhandlers[`@ExceptionHandler` methods] and
The `ErrorController` will then pick up any unhandled exceptions. {spring-reference}/#mvc-ann-controller-advice[`@ControllerAdvice`]. The `ErrorController`
will then pick up any unhandled exceptions.
N.B. if you register an `ErrorPage` with a path that will end up being handled by a N.B. if you register an `ErrorPage` with a path that will end up being handled by a
`Filter` (e.g. as is common with some non-Spring web frameworks, like Jersey and Wicket), `Filter` (e.g. as is common with some non-Spring web frameworks, like Jersey and Wicket),
then the `Filter` has to be explicitly registered as an `ERROR` dispatcher, e.g. then the `Filter` has to be explicitly registered as an `ERROR` dispatcher, e.g.
[source,java,indent=0,subs="verbatim,quotes,attributes"] [source,java,indent=0,subs="verbatim,quotes,attributes"]
---- ----
@Bean @Bean
public FilterRegistrationBean myFilter() { public FilterRegistrationBean myFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new MyFilter());
registration.setFilter(new MyFilter()); ...
... registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); return registration;
return registration;
} }
---- ----
(the default `FilterRegistrationBean` does not include the `ERROR` dispatcher type). (the default `FilterRegistrationBean` does not include the `ERROR` dispatcher type).
[[boot-features-error-handling-websphere]]
===== Error Handling on WebSphere Application Server
When deployed to a servlet container, a Spring Boot uses its error page filter to
forward a request with an error status to the appropriate error page. The request can
only be forwarded to the correct error page if the response has not already been
committed. By default, WebSphere Application Server 8.0 and later commits the response
upon successful completion of a servlet's service method. You should disable this
behaviour by setting `com.ibm.ws.webcontainer.invokeFlushAfterService` to `false`
[[boot-features-embedded-container]] [[boot-features-embedded-container]]

@ -52,6 +52,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson
*/ */
@Component @Component
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE)
@ -124,6 +125,12 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
private void handleErrorStatus(HttpServletRequest request, private void handleErrorStatus(HttpServletRequest request,
HttpServletResponse response, int status, String message) HttpServletResponse response, int status, String message)
throws ServletException, IOException { throws ServletException, IOException {
if (response.isCommitted()) {
handleCommittedResponse(request, null);
return;
}
String errorPath = getErrorPath(this.statuses, status); String errorPath = getErrorPath(this.statuses, status);
if (errorPath == null) { if (errorPath == null) {
response.sendError(status, message); response.sendError(status, message);
@ -132,6 +139,7 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
response.setStatus(status); response.setStatus(status);
setErrorAttributes(request, status, message); setErrorAttributes(request, status, message);
request.getRequestDispatcher(errorPath).forward(request, response); request.getRequestDispatcher(errorPath).forward(request, response);
} }
private void handleException(HttpServletRequest request, private void handleException(HttpServletRequest request,
@ -143,33 +151,49 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
rethrow(ex); rethrow(ex);
return; return;
} }
if (response.isCommitted()) {
handleCommittedResponse(request, ex);
return;
}
forwardToErrorPage(errorPath, request, wrapped, ex);
}
private void forwardToErrorPage(String path, HttpServletRequest request,
HttpServletResponse response, Throwable ex) throws ServletException,
IOException {
if (logger.isErrorEnabled()) { if (logger.isErrorEnabled()) {
String message = "Forwarding to error page from request [" String message = "Forwarding to error page from request ["
+ request.getServletPath() + request.getPathInfo() + request.getServletPath() + request.getPathInfo()
+ "] due to exception [" + ex.getMessage() + "]"; + "] due to exception [" + ex.getMessage() + "]";
logger.error(message, ex); logger.error(message, ex);
} }
setErrorAttributes(request, 500, ex.getMessage()); setErrorAttributes(request, 500, ex.getMessage());
request.setAttribute(ERROR_EXCEPTION, ex); request.setAttribute(ERROR_EXCEPTION, ex);
request.setAttribute(ERROR_EXCEPTION_TYPE, type.getName()); request.setAttribute(ERROR_EXCEPTION_TYPE, ex.getClass().getName());
forwardToErrorPage(errorPath, request, wrapped, ex);
response.reset();
response.sendError(500, ex.getMessage());
request.getRequestDispatcher(path).forward(request, response);
} }
private void forwardToErrorPage(String path, HttpServletRequest request, private void handleCommittedResponse(HttpServletRequest request, Throwable ex) {
HttpServletResponse response, Throwable ex) throws ServletException, String message = "Cannot forward to error page for request to "
IOException { + request.getRequestURI() + " as the response has already been"
if (response.isCommitted()) { + " committed. As a result, the response may have the wrong status"
String message = "Cannot forward to error page for" + request.getRequestURI() + " code. If your application is running on WebSphere Application"
+ " (response is committed), so this response may have " + " Server you may be able to resolve this problem by setting "
+ "the wrong status code"; + " com.ibm.ws.webcontainer.invokeFlushAfterService to false";
if (ex == null) {
logger.error(message);
}
else {
// User might see the error page without all the data here but throwing the // User might see the error page without all the data here but throwing the
// exception isn't going to help anyone (we'll log it to be on the safe side) // exception isn't going to help anyone (we'll log it to be on the safe side)
logger.error(message, ex); logger.error(message, ex);
return;
} }
response.reset();
response.sendError(500, ex.getMessage());
request.getRequestDispatcher(path).forward(request, response);
} }
private String getErrorPath(Map<Integer, String> map, Integer status) { private String getErrorPath(Map<Integer, String> map, Integer status) {
@ -243,6 +267,8 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
private String message; private String message;
private boolean errorToSend;
public ErrorWrapperResponse(HttpServletResponse response) { public ErrorWrapperResponse(HttpServletResponse response) {
super(response); super(response);
this.status = response.getStatus(); this.status = response.getStatus();
@ -257,6 +283,8 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
public void sendError(int status, String message) throws IOException { public void sendError(int status, String message) throws IOException {
this.status = status; this.status = status;
this.message = message; this.message = message;
this.errorToSend = true;
} }
@Override @Override
@ -264,6 +292,15 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
return this.status; return this.status;
} }
@Override
public void flushBuffer() throws IOException {
if (this.errorToSend && !isCommitted()) {
((HttpServletResponse) getResponse())
.sendError(this.status, this.message);
}
super.flushBuffer();
}
public String getMessage() { public String getMessage() {
return this.message; return this.message;
} }

@ -34,6 +34,8 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -43,6 +45,7 @@ import static org.junit.Assert.assertTrue;
* Tests for {@link ErrorPageFilter}. * Tests for {@link ErrorPageFilter}.
* *
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson
*/ */
public class ErrorPageFilterTests { public class ErrorPageFilterTests {
@ -61,6 +64,7 @@ public class ErrorPageFilterTests {
assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getResponse(), assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getResponse(),
equalTo((ServletResponse) this.response)); equalTo((ServletResponse) this.response));
assertTrue(this.response.isCommitted()); assertTrue(this.response.isCommitted());
assertThat(this.response.getForwardedUrl(), is(nullValue()));
} }
@Test @Test
@ -83,6 +87,7 @@ public class ErrorPageFilterTests {
assertThat(wrapper.getStatus(), equalTo(401)); assertThat(wrapper.getStatus(), equalTo(401));
// The real response has to be 401 as well... // The real response has to be 401 as well...
assertThat(this.response.getStatus(), equalTo(401)); assertThat(this.response.getStatus(), equalTo(401));
assertThat(this.response.getForwardedUrl(), equalTo("/error"));
} }
@Test @Test
@ -103,11 +108,12 @@ public class ErrorPageFilterTests {
equalTo((ServletResponse) this.response)); equalTo((ServletResponse) this.response));
assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getStatus(), assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getStatus(),
equalTo(400)); equalTo(400));
assertThat(this.response.getForwardedUrl(), is(nullValue()));
assertTrue(this.response.isCommitted()); assertTrue(this.response.isCommitted());
} }
@Test @Test
public void responseUncommitted() throws Exception { public void responseUncommittedWithoutErrorPage() throws Exception {
this.chain = new MockFilterChain() { this.chain = new MockFilterChain() {
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response) public void doFilter(ServletRequest request, ServletResponse response)
@ -122,6 +128,7 @@ public class ErrorPageFilterTests {
equalTo((ServletResponse) this.response)); equalTo((ServletResponse) this.response));
assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getStatus(), assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getStatus(),
equalTo(400)); equalTo(400));
assertThat(this.response.getForwardedUrl(), is(nullValue()));
assertTrue(this.response.isCommitted()); assertTrue(this.response.isCommitted());
} }
@ -159,6 +166,7 @@ public class ErrorPageFilterTests {
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE), assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE),
equalTo((Object) "BAD")); equalTo((Object) "BAD"));
assertTrue(this.response.isCommitted()); assertTrue(this.response.isCommitted());
assertThat(this.response.getForwardedUrl(), equalTo("/error"));
} }
@Test @Test
@ -180,6 +188,26 @@ public class ErrorPageFilterTests {
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE), assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE),
equalTo((Object) "BAD")); equalTo((Object) "BAD"));
assertTrue(this.response.isCommitted()); assertTrue(this.response.isCommitted());
assertThat(this.response.getForwardedUrl(), equalTo("/400"));
}
@Test
public void statusErrorWithCommittedResponse() throws Exception {
this.filter.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
this.chain = new MockFilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
((HttpServletResponse) response).sendError(400, "BAD");
response.flushBuffer();
super.doFilter(request, response);
}
};
this.filter.doFilter(this.request, this.response, this.chain);
assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getStatus(),
equalTo(400));
assertTrue(this.response.isCommitted());
assertThat(this.response.getForwardedUrl(), is(nullValue()));
} }
@Test @Test
@ -203,6 +231,23 @@ public class ErrorPageFilterTests {
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE), assertThat(this.request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE),
equalTo((Object) RuntimeException.class.getName())); equalTo((Object) RuntimeException.class.getName()));
assertTrue(this.response.isCommitted()); assertTrue(this.response.isCommitted());
assertThat(this.response.getForwardedUrl(), equalTo("/500"));
}
@Test
public void exceptionErrorWithCommittedResponse() throws Exception {
this.filter.addErrorPages(new ErrorPage(RuntimeException.class, "/500"));
this.chain = new MockFilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
super.doFilter(request, response);
response.flushBuffer();
throw new RuntimeException("BAD");
}
};
this.filter.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getForwardedUrl(), is(nullValue()));
} }
@Test @Test

Loading…
Cancel
Save