Improve async request timeout handling

Rather than setting the status to 503 directly from the timeout
interceptor which no longer seems to work reliably with Servlet
containers like Jetty even performing an additional ERROR dispatch back
to the original URL, we know rather set the DeferredResult to an
AsyncTimeoutException, which results in a dispatch and standard
handling within Spring MVC. This should be a more reliable way of
dealing with timeouts.

Issue: SPR-14669
This commit is contained in:
Rossen Stoyanchev
2016-09-14 21:34:30 -04:00
parent 8ccfecc406
commit 765b47246a
8 changed files with 124 additions and 29 deletions

View File

@@ -44,6 +44,7 @@ import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.util.WebUtils;
@@ -112,7 +113,8 @@ public abstract class ResponseEntityExceptionHandler {
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
@@ -172,6 +174,11 @@ public abstract class ResponseEntityExceptionHandler {
HttpStatus status = HttpStatus.NOT_FOUND;
return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
}
else if (ex instanceof AsyncRequestTimeoutException) {
HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, headers, status, request);
}
else {
logger.warn("Unknown exception type: " + ex.getClass().getName());
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
@@ -424,4 +431,20 @@ public abstract class ResponseEntityExceptionHandler {
return handleExceptionInternal(ex, null, headers, status, request);
}
/**
* Customize the response for NoHandlerFoundException.
* <p>This method delegates to {@link #handleExceptionInternal}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return a {@code ResponseEntity} instance
* @since 4.2.8
*/
protected ResponseEntity<Object> handleAsyncRequestTimeoutException(
AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
}

View File

@@ -44,6 +44,7 @@ import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
@@ -153,6 +154,10 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerException) {
if (logger.isWarnEnabled()) {
@@ -449,6 +454,25 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
return new ModelAndView();
}
/**
* Handle the case where an async request timed out.
* <p>The default implementation sends an HTTP 503 error.
* @param ex the {@link AsyncRequestTimeoutException }to be handled
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen
* at the time of the exception (for example, if multipart resolution failed)
* @return an empty ModelAndView indicating the exception was handled
* @throws IOException potentially thrown from response.sendError()
* @since 4.2.8
*/
protected ModelAndView handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex,
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return new ModelAndView();
}
/**
* Invoked to send a server error. Sets the status to 500 and also sets the