Browse Source

Merge branch '6.2.x'

pull/34295/head
rstoyanchev 1 year ago
parent
commit
92472a6b62
  1. 4
      spring-web/src/main/java/org/springframework/web/client/NoOpResponseErrorHandler.java
  2. 9
      spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java
  3. 12
      spring-web/src/main/java/org/springframework/web/client/RestClientException.java
  4. 9
      spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
  5. 51
      spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java
  6. 93
      spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java

4
spring-web/src/main/java/org/springframework/web/client/NoOpResponseErrorHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -25,7 +25,7 @@ import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler; @@ -25,7 +25,7 @@ import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler;
/**
* A basic, no operation {@link ResponseErrorHandler} implementation suitable
* for ignoring any error using the {@link RestTemplate}.
* for ignoring any error using the {@link RestTemplate} or {@link RestClient}.
* <p>This implementation is not suitable with the {@link RestClient} as it uses
* a list of candidates where the first matching is invoked. If you want to
* disable default status handlers with the {@code RestClient}, consider

9
spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -23,8 +23,11 @@ import org.springframework.http.HttpMethod; @@ -23,8 +23,11 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpResponse;
/**
* Strategy interface used by the {@link RestTemplate} to determine
* whether a particular response has an error or not.
* Strategy interface used by the {@link RestTemplate} and {@link RestClient} to
* determine whether a particular response has an error or not.
*
* <p>Note that {@code RestClient} also supports and recommends use of
* {@link RestClient.ResponseSpec#onStatus status handlers}.
*
* @author Arjen Poutsma
* @since 3.0

12
spring-web/src/main/java/org/springframework/web/client/RestClientException.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2025 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.
@ -19,14 +19,16 @@ package org.springframework.web.client; @@ -19,14 +19,16 @@ package org.springframework.web.client;
import org.jspecify.annotations.Nullable;
import org.springframework.core.NestedRuntimeException;
import org.springframework.http.client.ClientHttpResponse;
/**
* Base class for exceptions thrown by {@link RestTemplate} in case a request
* fails because of a server error response, as determined via
* {@link ResponseErrorHandler#hasError(ClientHttpResponse)}, failure to decode
* Base class for exceptions thrown by {@link RestClient} and {@link RestTemplate}
* in case a request fails because of a server error response, a failure to decode
* the response, or a low level I/O error.
*
* <p>Server error responses are determined by
* {@link RestClient.ResponseSpec#onStatus status handlers} for {@code RestClient},
* and by {@link ResponseErrorHandler} for {@code RestTemplate}.
*
* @author Arjen Poutsma
* @since 3.0
*/

9
spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

@ -178,7 +178,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -178,7 +178,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
/**
* Create a new instance of the {@link RestTemplate} using default settings.
* Create a new instance with default settings.
* Default {@link HttpMessageConverter HttpMessageConverters} are initialized.
*/
public RestTemplate() {
@ -237,7 +237,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -237,7 +237,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
/**
* Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
* Create a new instance with the given {@link ClientHttpRequestFactory}.
* @param requestFactory the HTTP request factory to use
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
@ -248,9 +248,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -248,9 +248,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
/**
* Create a new instance of the {@link RestTemplate} using the given list of
* {@link HttpMessageConverter} to use.
* @param messageConverters the list of {@link HttpMessageConverter} to use
* Create a new instance with the given message converters.
* @param messageConverters the list of converters to use
* @since 3.2.7
*/
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {

51
spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.web.util;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
@ -24,12 +25,14 @@ import org.apache.commons.logging.LogFactory; @@ -24,12 +25,14 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Utility methods to assist with identifying and logging exceptions that indicate
* the client has gone away. Such exceptions fill logs with unnecessary stack
* traces. The utility methods help to log a single line message at DEBUG level,
* and a full stacktrace at TRACE level.
* Utility methods to assist with identifying and logging exceptions that
* indicate the server response connection is lost, for example because the
* client has gone away. This class helps to identify such exceptions and
* minimize logging to a single line at DEBUG level, while making the full
* error stacktrace at TRACE level.
*
* @author Rossen Stoyanchev
* @since 6.1
@ -37,12 +40,28 @@ import org.springframework.util.Assert; @@ -37,12 +40,28 @@ import org.springframework.util.Assert;
public class DisconnectedClientHelper {
private static final Set<String> EXCEPTION_PHRASES =
Set.of("broken pipe", "connection reset");
Set.of("broken pipe", "connection reset by peer");
private static final Set<String> EXCEPTION_TYPE_NAMES =
Set.of("AbortedException", "ClientAbortException",
"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;
@ -79,10 +98,25 @@ public class DisconnectedClientHelper { @@ -79,10 +98,25 @@ public class DisconnectedClientHelper {
* <li>ClientAbortException or EOFException for Tomcat
* <li>EofException for Jetty
* <li>IOException "Broken pipe" or "connection reset by peer"
* <li>SocketException "Connection reset"
* </ul>
*/
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();
if (message != null) {
String text = message.toLowerCase(Locale.ROOT);
@ -92,7 +126,8 @@ public class DisconnectedClientHelper { @@ -92,7 +126,8 @@ public class DisconnectedClientHelper {
}
}
}
return EXCEPTION_TYPE_NAMES.contains(ex.getClass().getSimpleName());
return false;
}
}

93
spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.util;
import java.io.EOFException;
import java.io.IOException;
import java.util.List;
import org.apache.catalina.connector.ClientAbortException;
import org.eclipse.jetty.io.EofException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
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.testfixture.http.MockHttpInputMessage;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link DisconnectedClientHelper}.
* @author Rossen Stoyanchev
*/
public class DisconnectedClientHelperTests {
@ParameterizedTest
@ValueSource(strings = {"broKen pipe", "connection reset By peer"})
void exceptionPhrases(String phrase) {
Exception ex = new IOException(phrase);
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
ex = new IOException(ex);
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
}
@Test
void connectionResetExcluded() {
Exception ex = new IOException("connection reset");
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
}
@ParameterizedTest
@MethodSource("disconnectedExceptions")
void name(Exception ex) {
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
}
static List<Exception> disconnectedExceptions() {
return List.of(
new AbortedException(""), new ClientAbortException(""),
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();
}
}
Loading…
Cancel
Save