From 9252e741e17d2a810bf084235016692779518ecc Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 22 Oct 2024 15:45:54 +0100 Subject: [PATCH] Default webmvc handling of disconnected client errors Closes gh-33753 --- .../AbstractHandlerExceptionResolver.java | 11 +++++++-- .../DefaultHandlerExceptionResolver.java | 24 +++++++++++++++++++ .../ResponseEntityExceptionHandlerTests.java | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java index 8e6dda2dc34..a8ccaf220f9 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java @@ -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)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java index 83fcb94f198..7de262138cb 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java @@ -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)} + *

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. *

The default implementation sets status and the headers of the response diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java index 600b91f239e..3b47c09bc7f 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -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())