Skip to content

Commit 6fd3042

Browse files
committed
Align servlet container error handling with executable jar/war behavior
Previously, when an exception was thrown by a Controller in an application deployed to a servlet container the exception that was handled would be Spring Framework’s NestedServletException rather than the exception thrown by the application. Furthermore, when an exception was thrown or the response was used to send an error, the javax.servlet.error.request_uri request attribute would not be set. This differed from the behaviour in an executable jar/war where the exception would be the one thrown by the application, and the request_uri attribute would be set. This commit updates ErrorPageFilter, which is only involved in a servlet container, to unwrap a NestedServletException so that it’s the application’s exception that’s handled, and to set the request_uri attribute in the event of an exception being thrown or an error being sent. Closes gh-3249
1 parent 5ed2a96 commit 6fd3042

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

spring-boot/src/main/java/org/springframework/boot/context/web/ErrorPageFilter.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.core.annotation.Order;
4040
import org.springframework.stereotype.Component;
4141
import org.springframework.web.filter.OncePerRequestFilter;
42+
import org.springframework.web.util.NestedServletException;
4243

4344
/**
4445
* A special {@link AbstractConfigurableEmbeddedServletContainer} for non-embedded
@@ -69,6 +70,8 @@ public class ErrorPageFilter extends AbstractConfigurableEmbeddedServletContaine
6970

7071
private static final String ERROR_MESSAGE = "javax.servlet.error.message";
7172

73+
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
74+
7275
private static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
7376

7477
private String global;
@@ -121,7 +124,11 @@ else if (!request.isAsyncStarted() && !response.isCommitted()) {
121124
}
122125
}
123126
catch (Throwable ex) {
124-
handleException(request, response, wrapped, ex);
127+
Throwable exceptionToHandle = ex;
128+
if (ex instanceof NestedServletException) {
129+
exceptionToHandle = ((NestedServletException) ex).getRootCause();
130+
}
131+
handleException(request, response, wrapped, exceptionToHandle);
125132
response.flushBuffer();
126133
}
127134
}
@@ -225,9 +232,10 @@ private String getErrorPath(Class<?> type) {
225232
return this.global;
226233
}
227234

228-
private void setErrorAttributes(ServletRequest request, int status, String message) {
235+
private void setErrorAttributes(HttpServletRequest request, int status, String message) {
229236
request.setAttribute(ERROR_STATUS_CODE, status);
230237
request.setAttribute(ERROR_MESSAGE, message);
238+
request.setAttribute(ERROR_REQUEST_URI, request.getRequestURI());
231239
}
232240

233241
private void rethrow(Throwable ex) throws IOException, ServletException {

spring-boot/src/test/java/org/springframework/boot/context/web/ErrorPageFilterTests.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.web.context.request.async.StandardServletAsyncWebRequest;
3939
import org.springframework.web.context.request.async.WebAsyncManager;
4040
import org.springframework.web.context.request.async.WebAsyncUtils;
41+
import org.springframework.web.util.NestedServletException;
4142

4243
import static org.hamcrest.Matchers.containsString;
4344
import static org.hamcrest.Matchers.equalTo;
@@ -62,7 +63,8 @@ public class ErrorPageFilterTests {
6263

6364
private ErrorPageFilter filter = new ErrorPageFilter();
6465

65-
private MockHttpServletRequest request = new MockHttpServletRequest();
66+
private MockHttpServletRequest request = new MockHttpServletRequest("GET",
67+
"/test/path");
6668

6769
private MockHttpServletResponse response = new MockHttpServletResponse();
6870

@@ -199,6 +201,9 @@ public void doFilter(ServletRequest request, ServletResponse response)
199201
equalTo((Object) 400));
200202
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE),
201203
equalTo((Object) "BAD"));
204+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI),
205+
equalTo((Object) "/test/path"));
206+
202207
assertTrue(this.response.isCommitted());
203208
assertThat(this.response.getForwardedUrl(), equalTo("/error"));
204209
}
@@ -221,6 +226,8 @@ public void doFilter(ServletRequest request, ServletResponse response)
221226
equalTo((Object) 400));
222227
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE),
223228
equalTo((Object) "BAD"));
229+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI),
230+
equalTo((Object) "/test/path"));
224231
assertTrue(this.response.isCommitted());
225232
assertThat(this.response.getForwardedUrl(), equalTo("/400"));
226233
}
@@ -264,6 +271,8 @@ public void doFilter(ServletRequest request, ServletResponse response)
264271
equalTo((Object) "BAD"));
265272
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE),
266273
equalTo((Object) RuntimeException.class.getName()));
274+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI),
275+
equalTo((Object) "/test/path"));
267276
assertTrue(this.response.isCommitted());
268277
assertThat(this.response.getForwardedUrl(), equalTo("/500"));
269278
}
@@ -319,6 +328,8 @@ public void doFilter(ServletRequest request, ServletResponse response)
319328
equalTo((Object) "BAD"));
320329
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE),
321330
equalTo((Object) IllegalStateException.class.getName()));
331+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI),
332+
equalTo((Object) "/test/path"));
322333
assertTrue(this.response.isCommitted());
323334
}
324335

@@ -465,6 +476,32 @@ public void doFilter(ServletRequest request, ServletResponse response)
465476
assertThat(this.output.toString(), containsString("request [/test/alpha]"));
466477
}
467478

479+
@Test
480+
public void nestedServletExceptionIsUnwrapped() throws Exception {
481+
this.filter.addErrorPages(new ErrorPage(RuntimeException.class, "/500"));
482+
this.chain = new MockFilterChain() {
483+
@Override
484+
public void doFilter(ServletRequest request, ServletResponse response)
485+
throws IOException, ServletException {
486+
super.doFilter(request, response);
487+
throw new NestedServletException("Wrapper", new RuntimeException("BAD"));
488+
}
489+
};
490+
this.filter.doFilter(this.request, this.response, this.chain);
491+
assertThat(((HttpServletResponseWrapper) this.chain.getResponse()).getStatus(),
492+
equalTo(500));
493+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE),
494+
equalTo((Object) 500));
495+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_MESSAGE),
496+
equalTo((Object) "BAD"));
497+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE),
498+
equalTo((Object) RuntimeException.class.getName()));
499+
assertThat(this.request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI),
500+
equalTo((Object) "/test/path"));
501+
assertTrue(this.response.isCommitted());
502+
assertThat(this.response.getForwardedUrl(), equalTo("/500"));
503+
}
504+
468505
private void setUpAsyncDispatch() throws Exception {
469506
this.request.setAsyncSupported(true);
470507
this.request.setAsyncStarted(true);

0 commit comments

Comments
 (0)