Skip to content

Commit 29ce668

Browse files
committed
Add handleFailure property to FrameworkServlet
Issue: SPR-17100
1 parent 78eda96 commit 29ce668

File tree

4 files changed

+74
-9
lines changed

4 files changed

+74
-9
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ public abstract class FrameworkServlet extends HttpServletBean implements Applic
209209
/** Should we dispatch an HTTP TRACE request to {@link #doService}?. */
210210
private boolean dispatchTraceRequest = false;
211211

212+
/** Should we set the status to 500 for unhandled failures? */
213+
private boolean shouldHandleFailure = false;
214+
212215
/** WebApplicationContext for this servlet. */
213216
@Nullable
214217
private WebApplicationContext webApplicationContext;
@@ -472,6 +475,17 @@ public void setDispatchTraceRequest(boolean dispatchTraceRequest) {
472475
this.dispatchTraceRequest = dispatchTraceRequest;
473476
}
474477

478+
/**
479+
* Whether to handle failures wth {@code response.sendError(500)} as opposed
480+
* to letting them propagate to the container which may log a stacktrace
481+
* even if an error dispatch (e.g. Spring Boot app) handles the exception.
482+
* @param shouldHandleFailure whether to handle failures or propagate
483+
* @since 5.1
484+
*/
485+
public void setShouldHandleFailure(boolean shouldHandleFailure) {
486+
this.shouldHandleFailure = shouldHandleFailure;
487+
}
488+
475489
/**
476490
* Whether to log request params at DEBUG level, and headers at TRACE level.
477491
* Both may contain sensitive information.
@@ -998,12 +1012,16 @@ protected final void processRequest(HttpServletRequest request, HttpServletRespo
9981012
doService(request, response);
9991013
}
10001014
catch (ServletException | IOException ex) {
1001-
failureCause = ex;
1002-
throw ex;
1015+
if (!handleFailure(request, response, ex)) {
1016+
failureCause = ex;
1017+
throw ex;
1018+
}
10031019
}
10041020
catch (Throwable ex) {
1005-
failureCause = ex;
1006-
throw new NestedServletException("Request processing failed", ex);
1021+
if (!handleFailure(request, response, ex)) {
1022+
failureCause = ex;
1023+
throw new NestedServletException("Request processing failed", ex);
1024+
}
10071025
}
10081026

10091027
finally {
@@ -1069,6 +1087,24 @@ private void resetContextHolders(HttpServletRequest request,
10691087
RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
10701088
}
10711089

1090+
private boolean handleFailure(HttpServletRequest request, HttpServletResponse response, Throwable ex) {
1091+
if (this.shouldHandleFailure) {
1092+
try {
1093+
response.sendError(500);
1094+
}
1095+
catch (IOException ex2) {
1096+
if (logger.isDebugEnabled()) {
1097+
logger.debug("Handling of failure failed: " + ex2);
1098+
}
1099+
return false;
1100+
}
1101+
request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, 500);
1102+
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
1103+
return true;
1104+
}
1105+
return false;
1106+
}
1107+
10721108
private void logResult(HttpServletRequest request, HttpServletResponse response,
10731109
@Nullable Throwable failureCause, WebAsyncManager asyncManager) {
10741110

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,7 @@ else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICAT
281281
inputMessage, outputMessage);
282282
if (body != null) {
283283
if (logger.isDebugEnabled()) {
284-
Object formatted = (body instanceof CharSequence ? "\"" + body + "\"" : body);
285-
logger.debug("Writing [" + formatted + "]");
284+
logger.debug("Writing [" + formatValue(body) + "]");
286285
}
287286
addContentDispositionHeader(inputMessage, outputMessage);
288287
if (genericConverter != null) {
@@ -399,6 +398,10 @@ private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produ
399398
return (MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceTypeToUse) <= 0 ? acceptType : produceTypeToUse);
400399
}
401400

401+
static String formatValue(Object body) {
402+
return (body instanceof CharSequence ? "\"" + body + "\"" : body.toString());
403+
}
404+
402405
/**
403406
* Check if the path has a file extension and whether the extension is
404407
* either {@link #WHITELISTED_EXTENSIONS whitelisted} or explicitly

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,8 +871,8 @@ protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
871871
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
872872
asyncManager.clearConcurrentResult();
873873
if (logger.isDebugEnabled()) {
874-
logger.debug("Resume with async result [" +
875-
(result instanceof CharSequence ? "\"" + result + "\"" : result) + "]");
874+
String formatted = AbstractMessageConverterMethodProcessor.formatValue(result);
875+
logger.debug("Resume with async result [" + formatted + "]");
876876
}
877877
invocableMethod = invocableMethod.wrapConcurrentResult(result);
878878
}

spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -614,6 +614,32 @@ public void noHandlerFoundExceptionMessage() {
614614
assertTrue(!ex.toString().contains("bar"));
615615
}
616616

617+
@Test // SPR-17100
618+
public void shouldHandleFailure() throws ServletException, IOException {
619+
620+
IllegalStateException ex = new IllegalStateException("dummy");
621+
@SuppressWarnings("serial")
622+
FrameworkServlet servlet = new FrameworkServlet() {
623+
@Override
624+
protected void doService(HttpServletRequest request, HttpServletResponse response) {
625+
throw ex;
626+
}
627+
};
628+
servlet.setShouldHandleFailure(true);
629+
630+
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/error");
631+
MockHttpServletResponse response = new MockHttpServletResponse();
632+
633+
servlet.service(request, response);
634+
635+
assertEquals(500, response.getStatus());
636+
assertEquals(500, request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE));
637+
assertEquals(ex.getClass(), request.getAttribute(WebUtils.ERROR_EXCEPTION_TYPE_ATTRIBUTE));
638+
assertEquals(ex.getMessage(), request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE));
639+
assertEquals(ex, request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE));
640+
assertEquals(request.getRequestURI(), request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE));
641+
}
642+
617643
@Test
618644
public void cleanupAfterIncludeWithRemove() throws ServletException, IOException {
619645
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/main.do");

0 commit comments

Comments
 (0)