Merge branch '1.1.x'
This commit is contained in:
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
|
||||||
|
|
@ -997,9 +997,10 @@ support a uniform Java DSL for customizing the error handling. For example:
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
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),
|
||||||
|
|
@ -1009,18 +1010,26 @@ then the `Filter` has to be explicitly registered as an `ERROR` dispatcher, e.g.
|
||||||
----
|
----
|
||||||
@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,35 +151,51 @@ class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContainer imple
|
||||||
rethrow(ex);
|
rethrow(ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (logger.isErrorEnabled()) {
|
if (response.isCommitted()) {
|
||||||
String message = "Forwarding to error page from request ["
|
handleCommittedResponse(request, ex);
|
||||||
+ request.getServletPath() + request.getPathInfo()
|
return;
|
||||||
+ "] due to exception [" + ex.getMessage() + "]";
|
|
||||||
logger.error(message, ex);
|
|
||||||
}
|
}
|
||||||
setErrorAttributes(request, 500, ex.getMessage());
|
|
||||||
request.setAttribute(ERROR_EXCEPTION, ex);
|
|
||||||
request.setAttribute(ERROR_EXCEPTION_TYPE, type.getName());
|
|
||||||
forwardToErrorPage(errorPath, request, wrapped, ex);
|
forwardToErrorPage(errorPath, request, wrapped, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forwardToErrorPage(String path, HttpServletRequest request,
|
private void forwardToErrorPage(String path, HttpServletRequest request,
|
||||||
HttpServletResponse response, Throwable ex) throws ServletException,
|
HttpServletResponse response, Throwable ex) throws ServletException,
|
||||||
IOException {
|
IOException {
|
||||||
if (response.isCommitted()) {
|
|
||||||
String message = "Cannot forward to error page for" + request.getRequestURI()
|
if (logger.isErrorEnabled()) {
|
||||||
+ " (response is committed), so this response may have "
|
String message = "Forwarding to error page from request ["
|
||||||
+ "the wrong status code";
|
+ request.getServletPath() + request.getPathInfo()
|
||||||
// User might see the error page without all the data here but throwing the
|
+ "] due to exception [" + ex.getMessage() + "]";
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setErrorAttributes(request, 500, ex.getMessage());
|
||||||
|
request.setAttribute(ERROR_EXCEPTION, ex);
|
||||||
|
request.setAttribute(ERROR_EXCEPTION_TYPE, ex.getClass().getName());
|
||||||
|
|
||||||
response.reset();
|
response.reset();
|
||||||
response.sendError(500, ex.getMessage());
|
response.sendError(500, ex.getMessage());
|
||||||
request.getRequestDispatcher(path).forward(request, response);
|
request.getRequestDispatcher(path).forward(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCommittedResponse(HttpServletRequest request, Throwable ex) {
|
||||||
|
String message = "Cannot forward to error page for request to "
|
||||||
|
+ request.getRequestURI() + " as the response has already been"
|
||||||
|
+ " committed. As a result, the response may have the wrong status"
|
||||||
|
+ " code. If your application is running on WebSphere Application"
|
||||||
|
+ " Server you may be able to resolve this problem by setting "
|
||||||
|
+ " 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
|
||||||
|
// exception isn't going to help anyone (we'll log it to be on the safe side)
|
||||||
|
logger.error(message, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String getErrorPath(Map<Integer, String> map, Integer status) {
|
private String getErrorPath(Map<Integer, String> map, Integer status) {
|
||||||
if (map.containsKey(status)) {
|
if (map.containsKey(status)) {
|
||||||
return map.get(status);
|
return map.get(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…
Reference in New Issue