Skip to content

Commit 51245d4

Browse files
author
jerzykrlk
committed
http error details in the rest client exception message
1 parent 5115deb commit 51245d4

File tree

4 files changed

+82
-8
lines changed

4 files changed

+82
-8
lines changed

Diff for: spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java

+53-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.ByteArrayInputStream;
2020
import java.io.IOException;
2121
import java.io.InputStream;
22+
import java.net.URI;
2223
import java.nio.charset.Charset;
2324
import java.nio.charset.StandardCharsets;
2425
import java.util.Collections;
@@ -28,6 +29,7 @@
2829
import org.springframework.core.ResolvableType;
2930
import org.springframework.core.log.LogFormatUtils;
3031
import org.springframework.http.HttpHeaders;
32+
import org.springframework.http.HttpMethod;
3133
import org.springframework.http.HttpStatus;
3234
import org.springframework.http.HttpStatusCode;
3335
import org.springframework.http.MediaType;
@@ -129,12 +131,33 @@ protected boolean hasError(int statusCode) {
129131
* {@link HttpStatus} enum range.
130132
* </ul>
131133
* @throws UnknownHttpStatusCodeException in case of an unresolvable status code
132-
* @see #handleError(ClientHttpResponse, HttpStatusCode)
134+
* @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatusCode)
133135
*/
134136
@Override
135137
public void handleError(ClientHttpResponse response) throws IOException {
138+
handleError(null, null, response);
139+
}
140+
141+
/**
142+
* Handle the error in the given response with the given resolved status code.
143+
* <p>The default implementation throws:
144+
* <ul>
145+
* <li>{@link HttpClientErrorException} if the status code is in the 4xx
146+
* series, or one of its sub-classes such as
147+
* {@link HttpClientErrorException.BadRequest} and others.
148+
* <li>{@link HttpServerErrorException} if the status code is in the 5xx
149+
* series, or one of its sub-classes such as
150+
* {@link HttpServerErrorException.InternalServerError} and others.
151+
* <li>{@link UnknownHttpStatusCodeException} for error status codes not in the
152+
* {@link HttpStatus} enum range.
153+
* </ul>
154+
* @throws UnknownHttpStatusCodeException in case of an unresolvable status code
155+
* @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatusCode)
156+
*/
157+
@Override
158+
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
136159
HttpStatusCode statusCode = response.getStatusCode();
137-
handleError(response, statusCode);
160+
handleError(url, method, response, statusCode);
138161
}
139162

140163
/**
@@ -144,10 +167,9 @@ public void handleError(ClientHttpResponse response) throws IOException {
144167
* </pre>
145168
*/
146169
private String getErrorMessage(
147-
int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) {
148-
149-
String preface = rawStatusCode + " " + statusText + ": ";
170+
int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset, @Nullable URI url, @Nullable HttpMethod method) {
150171

172+
String preface = getPreface(rawStatusCode, statusText, url, method);
151173
if (ObjectUtils.isEmpty(responseBody)) {
152174
return preface + "[no body]";
153175
}
@@ -160,6 +182,15 @@ private String getErrorMessage(
160182
return preface + bodyText;
161183
}
162184

185+
private String getPreface(int rawStatusCode, String statusText, @Nullable URI url, @Nullable HttpMethod method) {
186+
StringBuilder preface = new StringBuilder(rawStatusCode + " " + statusText);
187+
if (!ObjectUtils.isEmpty(method) && !ObjectUtils.isEmpty(url)) {
188+
preface.append(" after ").append(method).append(" ").append(url).append(" ");
189+
}
190+
preface.append(": ");
191+
return preface.toString();
192+
}
193+
163194
/**
164195
* Handle the error based on the resolved status code.
165196
*
@@ -172,11 +203,27 @@ private String getErrorMessage(
172203
* @see HttpServerErrorException#create
173204
*/
174205
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode) throws IOException {
206+
handleError(null, null, response, statusCode);
207+
}
208+
209+
/**
210+
* Handle the error based on the resolved status code.
211+
*
212+
* <p>The default implementation delegates to
213+
* {@link HttpClientErrorException#create} for errors in the 4xx range, to
214+
* {@link HttpServerErrorException#create} for errors in the 5xx range,
215+
* or otherwise raises {@link UnknownHttpStatusCodeException}.
216+
* @since 5.0
217+
* @see HttpClientErrorException#create
218+
* @see HttpServerErrorException#create
219+
*/
220+
protected void handleError(@Nullable URI url, @Nullable HttpMethod method, ClientHttpResponse response,
221+
HttpStatusCode statusCode) throws IOException {
175222
String statusText = response.getStatusText();
176223
HttpHeaders headers = response.getHeaders();
177224
byte[] body = getResponseBody(response);
178225
Charset charset = getCharset(response);
179-
String message = getErrorMessage(statusCode.value(), statusText, body, charset);
226+
String message = getErrorMessage(statusCode.value(), statusText, body, charset, url, method);
180227

181228
RestClientResponseException ex;
182229
if (statusCode.is4xxClientError()) {

Diff for: spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.springframework.web.client;
1818

1919
import java.io.IOException;
20+
import java.net.URI;
2021
import java.util.Collections;
2122
import java.util.LinkedHashMap;
2223
import java.util.List;
2324
import java.util.Map;
2425

26+
import org.springframework.http.HttpMethod;
2527
import org.springframework.http.HttpStatus;
2628
import org.springframework.http.HttpStatusCode;
2729
import org.springframework.http.client.ClientHttpResponse;
@@ -148,8 +150,13 @@ public void handleError(ClientHttpResponse response, HttpStatusCode statusCode)
148150
}
149151
}
150152

153+
@Override
154+
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
155+
handleError(response, response.getStatusCode());
156+
}
157+
151158
private void extract(@Nullable Class<? extends RestClientException> exceptionClass,
152-
ClientHttpResponse response) throws IOException {
159+
ClientHttpResponse response) throws IOException {
153160

154161
if (exceptionClass == null) {
155162
return;

Diff for: spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818

1919
import java.io.ByteArrayInputStream;
2020
import java.io.IOException;
21+
import java.net.URI;
2122
import java.nio.charset.StandardCharsets;
2223

2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.http.HttpHeaders;
27+
import org.springframework.http.HttpMethod;
2628
import org.springframework.http.HttpStatus;
2729
import org.springframework.http.HttpStatusCode;
2830
import org.springframework.http.MediaType;
@@ -77,6 +79,22 @@ public void handleError() throws Exception {
7779
.satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers));
7880
}
7981

82+
@Test
83+
public void handleErrorWithUrlAndMethod() throws Exception {
84+
HttpHeaders headers = new HttpHeaders();
85+
headers.setContentType(MediaType.TEXT_PLAIN);
86+
87+
given(response.getStatusCode()).willReturn(HttpStatus.NOT_FOUND);
88+
given(response.getStatusText()).willReturn("Not Found");
89+
given(response.getHeaders()).willReturn(headers);
90+
given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8)));
91+
92+
assertThatExceptionOfType(HttpClientErrorException.class)
93+
.isThrownBy(() -> handler.handleError(URI.create("https://example.com"), HttpMethod.GET, response))
94+
.withMessage("404 Not Found after GET https://example.com : \"Hello World\"")
95+
.satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers));
96+
}
97+
8098
@Test
8199
public void handleErrorIOException() throws Exception {
82100
HttpHeaders headers = new HttpHeaders();

Diff for: spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ void notFound(ClientHttpRequestFactory clientHttpRequestFactory) {
242242
assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
243243
assertThat(ex.getStatusText()).isNotNull();
244244
assertThat(ex.getResponseBodyAsString()).isNotNull();
245+
assertThat(ex.getMessage()).isEqualTo("404 Client Error after GET http://localhost:" + port + "/status/notfound : [no body]");
245246
});
246247
}
247248

@@ -253,7 +254,7 @@ void badRequest(ClientHttpRequestFactory clientHttpRequestFactory) {
253254
template.execute(baseUrl + "/status/badrequest", HttpMethod.GET, null, null))
254255
.satisfies(ex -> {
255256
assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
256-
assertThat(ex.getMessage()).isEqualTo("400 Client Error: [no body]");
257+
assertThat(ex.getMessage()).isEqualTo("400 Client Error after GET http://localhost:" + port + "/status/badrequest : [no body]");
257258
});
258259
}
259260

@@ -267,6 +268,7 @@ void serverError(ClientHttpRequestFactory clientHttpRequestFactory) {
267268
assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
268269
assertThat(ex.getStatusText()).isNotNull();
269270
assertThat(ex.getResponseBodyAsString()).isNotNull();
271+
assertThat(ex.getMessage()).isEqualTo("500 Server Error after GET http://localhost:" + port + "/status/server : [no body]");
270272
});
271273
}
272274

0 commit comments

Comments
 (0)