Enhance DisconnectedClientHelper exception type checks
We now look for the target exception types in cause chain as well, but return false if we encounter a RestClient or WebClient exception in the chain. Closes gh-34264
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.web.util;
|
package org.springframework.web.util;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -24,21 +25,20 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
|
|
||||||
import org.springframework.core.NestedExceptionUtils;
|
import org.springframework.core.NestedExceptionUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods to assist with identifying and logging exceptions that indicate
|
* Utility methods to assist with identifying and logging exceptions that
|
||||||
* the client has gone away. Such exceptions fill logs with unnecessary stack
|
* indicate the server response connection is lost, for example because the
|
||||||
* traces. The utility methods help to log a single line message at DEBUG level,
|
* client has gone away. This class helps to identify such exceptions and
|
||||||
* and a full stacktrace at TRACE level.
|
* minimize logging to a single line at DEBUG level, while making the full
|
||||||
|
* error stacktrace at TRACE level.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 6.1
|
* @since 6.1
|
||||||
*/
|
*/
|
||||||
public class DisconnectedClientHelper {
|
public class DisconnectedClientHelper {
|
||||||
|
|
||||||
// Look for server response connection issues (aborted), not onward connections
|
|
||||||
// to other servers (500 errors).
|
|
||||||
|
|
||||||
private static final Set<String> EXCEPTION_PHRASES =
|
private static final Set<String> EXCEPTION_PHRASES =
|
||||||
Set.of("broken pipe", "connection reset by peer");
|
Set.of("broken pipe", "connection reset by peer");
|
||||||
|
|
||||||
@@ -46,6 +46,22 @@ public class DisconnectedClientHelper {
|
|||||||
Set.of("AbortedException", "ClientAbortException",
|
Set.of("AbortedException", "ClientAbortException",
|
||||||
"EOFException", "EofException", "AsyncRequestNotUsableException");
|
"EOFException", "EofException", "AsyncRequestNotUsableException");
|
||||||
|
|
||||||
|
private static final Set<Class<?>> CLIENT_EXCEPTION_TYPES = new HashSet<>(2);
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
ClassLoader classLoader = DisconnectedClientHelper.class.getClassLoader();
|
||||||
|
CLIENT_EXCEPTION_TYPES.add(ClassUtils.forName(
|
||||||
|
"org.springframework.web.client.RestClientException", classLoader));
|
||||||
|
CLIENT_EXCEPTION_TYPES.add(ClassUtils.forName(
|
||||||
|
"org.springframework.web.reactive.function.client.WebClientException", classLoader));
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ex) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private final Log logger;
|
private final Log logger;
|
||||||
|
|
||||||
|
|
||||||
@@ -85,6 +101,22 @@ public class DisconnectedClientHelper {
|
|||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public static boolean isClientDisconnectedException(Throwable ex) {
|
public static boolean isClientDisconnectedException(Throwable ex) {
|
||||||
|
Throwable currentEx = ex;
|
||||||
|
Throwable lastEx = null;
|
||||||
|
while (currentEx != null && currentEx != lastEx) {
|
||||||
|
// Ignore onward connection issues to other servers (500 error)
|
||||||
|
for (Class<?> exceptionType : CLIENT_EXCEPTION_TYPES) {
|
||||||
|
if (exceptionType.isInstance(currentEx)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (EXCEPTION_TYPE_NAMES.contains(currentEx.getClass().getSimpleName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
lastEx = currentEx;
|
||||||
|
currentEx = currentEx.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
|
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
String text = message.toLowerCase(Locale.ROOT);
|
String text = message.toLowerCase(Locale.ROOT);
|
||||||
@@ -94,7 +126,8 @@ public class DisconnectedClientHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EXCEPTION_TYPE_NAMES.contains(ex.getClass().getSimpleName());
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import reactor.netty.channel.AbortedException;
|
import reactor.netty.channel.AbortedException;
|
||||||
|
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
|
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
|
||||||
|
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -66,4 +69,25 @@ public class DisconnectedClientHelperTests {
|
|||||||
new EOFException(), new EofException(), new AsyncRequestNotUsableException(""));
|
new EOFException(), new EofException(), new AsyncRequestNotUsableException(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // gh-33064
|
||||||
|
void nestedDisconnectedException() {
|
||||||
|
Exception ex = new HttpMessageNotReadableException(
|
||||||
|
"I/O error while reading input message", new ClientAbortException(),
|
||||||
|
new MockHttpInputMessage(new byte[0]));
|
||||||
|
|
||||||
|
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // gh-34264
|
||||||
|
void onwardClientDisconnectedExceptionPhrase() {
|
||||||
|
Exception ex = new ResourceAccessException("I/O error", new EOFException("Connection reset by peer"));
|
||||||
|
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void onwardClientDisconnectedExceptionType() {
|
||||||
|
Exception ex = new ResourceAccessException("I/O error", new EOFException());
|
||||||
|
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user