Default webmvc handling of disconnected client errors

Closes gh-33753
This commit is contained in:
rstoyanchev 2024-10-22 15:45:54 +01:00
parent 5271f5b8a1
commit 9252e741e1
3 changed files with 34 additions and 2 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,6 +31,7 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.DisconnectedClientHelper;
/**
* Abstract base class for {@link HandlerExceptionResolver} implementations.
@ -48,6 +49,12 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
private static final String HEADER_CACHE_CONTROL = "Cache-Control";
private static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
"org.springframework.web.servlet.handler.DisconnectedClient";
private static final DisconnectedClientHelper disconnectedClientHelper =
new DisconnectedClientHelper(DISCONNECTED_CLIENT_LOG_CATEGORY);
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@ -173,7 +180,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
if (result != null && !disconnectedClientHelper.checkAndLogClientDisconnectedException(ex)) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));

View File

@ -51,6 +51,7 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import org.springframework.web.util.DisconnectedClientHelper;
import org.springframework.web.util.WebUtils;
/**
@ -246,6 +247,9 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
return handleAsyncRequestNotUsableException(
(AsyncRequestNotUsableException) ex, request, response, handler);
}
else if (DisconnectedClientHelper.isClientDisconnectedException(ex)) {
return handleDisconnectedClientException(ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
@ -514,6 +518,26 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
return new ModelAndView();
}
/**
* Handle an Exception that indicates the client has gone away. This is
* typically an {@link IOException} of a specific subtype or with a message
* specific to the underlying Servlet container. Those are detected through
* {@link DisconnectedClientHelper#isClientDisconnectedException(Throwable)}
* <p>By default, do nothing since the response is not usable.
* @param ex the {@code Exception} 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
* @since 6.2
*/
protected ModelAndView handleDisconnectedClientException(
Exception ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) {
return new ModelAndView();
}
/**
* Handle an {@link ErrorResponse} exception.
* <p>The default implementation sets status and the headers of the response

View File

@ -107,6 +107,7 @@ class ResponseEntityExceptionHandlerTests {
Arrays.stream(DefaultHandlerExceptionResolver.class.getDeclaredMethods())
.filter(method -> method.getName().startsWith("handle") && (method.getParameterCount() == 4))
.filter(method -> !method.getName().equals("handleErrorResponse"))
.filter(method -> !method.getName().equals("handleDisconnectedClientException"))
.map(method -> method.getParameterTypes()[0])
.forEach(exceptionType -> assertThat(annotation.value())
.as("@ExceptionHandler is missing declaration for " + exceptionType.getName())