Skip to content

Commit 92472a6

Browse files
committed
Merge branch '6.2.x'
2 parents a257697 + 233f755 commit 92472a6

File tree

6 files changed

+155
-23
lines changed

6 files changed

+155
-23
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
2525

2626
/**
2727
* A basic, no operation {@link ResponseErrorHandler} implementation suitable
28-
* for ignoring any error using the {@link RestTemplate}.
28+
* for ignoring any error using the {@link RestTemplate} or {@link RestClient}.
2929
* <p>This implementation is not suitable with the {@link RestClient} as it uses
3030
* a list of candidates where the first matching is invoked. If you want to
3131
* disable default status handlers with the {@code RestClient}, consider

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,8 +23,11 @@
2323
import org.springframework.http.client.ClientHttpResponse;
2424

2525
/**
26-
* Strategy interface used by the {@link RestTemplate} to determine
27-
* whether a particular response has an error or not.
26+
* Strategy interface used by the {@link RestTemplate} and {@link RestClient} to
27+
* determine whether a particular response has an error or not.
28+
*
29+
* <p>Note that {@code RestClient} also supports and recommends use of
30+
* {@link RestClient.ResponseSpec#onStatus status handlers}.
2831
*
2932
* @author Arjen Poutsma
3033
* @since 3.0

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,14 +19,16 @@
1919
import org.jspecify.annotations.Nullable;
2020

2121
import org.springframework.core.NestedRuntimeException;
22-
import org.springframework.http.client.ClientHttpResponse;
2322

2423
/**
25-
* Base class for exceptions thrown by {@link RestTemplate} in case a request
26-
* fails because of a server error response, as determined via
27-
* {@link ResponseErrorHandler#hasError(ClientHttpResponse)}, failure to decode
24+
* Base class for exceptions thrown by {@link RestClient} and {@link RestTemplate}
25+
* in case a request fails because of a server error response, a failure to decode
2826
* the response, or a low level I/O error.
2927
*
28+
* <p>Server error responses are determined by
29+
* {@link RestClient.ResponseSpec#onStatus status handlers} for {@code RestClient},
30+
* and by {@link ResponseErrorHandler} for {@code RestTemplate}.
31+
*
3032
* @author Arjen Poutsma
3133
* @since 3.0
3234
*/

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
178178

179179

180180
/**
181-
* Create a new instance of the {@link RestTemplate} using default settings.
181+
* Create a new instance with default settings.
182182
* Default {@link HttpMessageConverter HttpMessageConverters} are initialized.
183183
*/
184184
public RestTemplate() {
@@ -237,7 +237,7 @@ else if (kotlinSerializationCborPresent) {
237237
}
238238

239239
/**
240-
* Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
240+
* Create a new instance with the given {@link ClientHttpRequestFactory}.
241241
* @param requestFactory the HTTP request factory to use
242242
* @see org.springframework.http.client.SimpleClientHttpRequestFactory
243243
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
@@ -248,9 +248,8 @@ public RestTemplate(ClientHttpRequestFactory requestFactory) {
248248
}
249249

250250
/**
251-
* Create a new instance of the {@link RestTemplate} using the given list of
252-
* {@link HttpMessageConverter} to use.
253-
* @param messageConverters the list of {@link HttpMessageConverter} to use
251+
* Create a new instance with the given message converters.
252+
* @param messageConverters the list of converters to use
254253
* @since 3.2.7
255254
*/
256255
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {

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

+43-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.util;
1818

19+
import java.util.HashSet;
1920
import java.util.Locale;
2021
import java.util.Set;
2122

@@ -24,25 +25,43 @@
2425

2526
import org.springframework.core.NestedExceptionUtils;
2627
import org.springframework.util.Assert;
28+
import org.springframework.util.ClassUtils;
2729

2830
/**
29-
* Utility methods to assist with identifying and logging exceptions that indicate
30-
* the client has gone away. Such exceptions fill logs with unnecessary stack
31-
* traces. The utility methods help to log a single line message at DEBUG level,
32-
* and a full stacktrace at TRACE level.
31+
* Utility methods to assist with identifying and logging exceptions that
32+
* indicate the server response connection is lost, for example because the
33+
* client has gone away. This class helps to identify such exceptions and
34+
* minimize logging to a single line at DEBUG level, while making the full
35+
* error stacktrace at TRACE level.
3336
*
3437
* @author Rossen Stoyanchev
3538
* @since 6.1
3639
*/
3740
public class DisconnectedClientHelper {
3841

3942
private static final Set<String> EXCEPTION_PHRASES =
40-
Set.of("broken pipe", "connection reset");
43+
Set.of("broken pipe", "connection reset by peer");
4144

4245
private static final Set<String> EXCEPTION_TYPE_NAMES =
4346
Set.of("AbortedException", "ClientAbortException",
4447
"EOFException", "EofException", "AsyncRequestNotUsableException");
4548

49+
private static final Set<Class<?>> CLIENT_EXCEPTION_TYPES = new HashSet<>(2);
50+
51+
static {
52+
try {
53+
ClassLoader classLoader = DisconnectedClientHelper.class.getClassLoader();
54+
CLIENT_EXCEPTION_TYPES.add(ClassUtils.forName(
55+
"org.springframework.web.client.RestClientException", classLoader));
56+
CLIENT_EXCEPTION_TYPES.add(ClassUtils.forName(
57+
"org.springframework.web.reactive.function.client.WebClientException", classLoader));
58+
}
59+
catch (ClassNotFoundException ex) {
60+
// ignore
61+
}
62+
}
63+
64+
4665
private final Log logger;
4766

4867

@@ -79,10 +98,25 @@ else if (logger.isDebugEnabled()) {
7998
* <li>ClientAbortException or EOFException for Tomcat
8099
* <li>EofException for Jetty
81100
* <li>IOException "Broken pipe" or "connection reset by peer"
82-
* <li>SocketException "Connection reset"
83101
* </ul>
84102
*/
85103
public static boolean isClientDisconnectedException(Throwable ex) {
104+
Throwable currentEx = ex;
105+
Throwable lastEx = null;
106+
while (currentEx != null && currentEx != lastEx) {
107+
// Ignore onward connection issues to other servers (500 error)
108+
for (Class<?> exceptionType : CLIENT_EXCEPTION_TYPES) {
109+
if (exceptionType.isInstance(currentEx)) {
110+
return false;
111+
}
112+
}
113+
if (EXCEPTION_TYPE_NAMES.contains(currentEx.getClass().getSimpleName())) {
114+
return true;
115+
}
116+
lastEx = currentEx;
117+
currentEx = currentEx.getCause();
118+
}
119+
86120
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
87121
if (message != null) {
88122
String text = message.toLowerCase(Locale.ROOT);
@@ -92,7 +126,8 @@ public static boolean isClientDisconnectedException(Throwable ex) {
92126
}
93127
}
94128
}
95-
return EXCEPTION_TYPE_NAMES.contains(ex.getClass().getSimpleName());
129+
130+
return false;
96131
}
97132

98133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.util;
18+
19+
import java.io.EOFException;
20+
import java.io.IOException;
21+
import java.util.List;
22+
23+
import org.apache.catalina.connector.ClientAbortException;
24+
import org.eclipse.jetty.io.EofException;
25+
import org.junit.jupiter.api.Test;
26+
import org.junit.jupiter.params.ParameterizedTest;
27+
import org.junit.jupiter.params.provider.MethodSource;
28+
import org.junit.jupiter.params.provider.ValueSource;
29+
import reactor.netty.channel.AbortedException;
30+
31+
import org.springframework.http.converter.HttpMessageNotReadableException;
32+
import org.springframework.web.client.ResourceAccessException;
33+
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
34+
import org.springframework.web.testfixture.http.MockHttpInputMessage;
35+
36+
import static org.assertj.core.api.Assertions.assertThat;
37+
38+
/**
39+
* Unit tests for {@link DisconnectedClientHelper}.
40+
* @author Rossen Stoyanchev
41+
*/
42+
public class DisconnectedClientHelperTests {
43+
44+
@ParameterizedTest
45+
@ValueSource(strings = {"broKen pipe", "connection reset By peer"})
46+
void exceptionPhrases(String phrase) {
47+
Exception ex = new IOException(phrase);
48+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
49+
50+
ex = new IOException(ex);
51+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
52+
}
53+
54+
@Test
55+
void connectionResetExcluded() {
56+
Exception ex = new IOException("connection reset");
57+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
58+
}
59+
60+
@ParameterizedTest
61+
@MethodSource("disconnectedExceptions")
62+
void name(Exception ex) {
63+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
64+
}
65+
66+
static List<Exception> disconnectedExceptions() {
67+
return List.of(
68+
new AbortedException(""), new ClientAbortException(""),
69+
new EOFException(), new EofException(), new AsyncRequestNotUsableException(""));
70+
}
71+
72+
@Test // gh-33064
73+
void nestedDisconnectedException() {
74+
Exception ex = new HttpMessageNotReadableException(
75+
"I/O error while reading input message", new ClientAbortException(),
76+
new MockHttpInputMessage(new byte[0]));
77+
78+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isTrue();
79+
}
80+
81+
@Test // gh-34264
82+
void onwardClientDisconnectedExceptionPhrase() {
83+
Exception ex = new ResourceAccessException("I/O error", new EOFException("Connection reset by peer"));
84+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
85+
}
86+
87+
@Test
88+
void onwardClientDisconnectedExceptionType() {
89+
Exception ex = new ResourceAccessException("I/O error", new EOFException());
90+
assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse();
91+
}
92+
93+
}

0 commit comments

Comments
 (0)