Skip to content

Commit f85c4e1

Browse files
committed
Polish "Include URL and HTTP method in DefaultResponseErrorHandler"
See gh-28958
1 parent 4972d18 commit f85c4e1

File tree

4 files changed

+90
-68
lines changed

4 files changed

+90
-68
lines changed

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

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -131,15 +131,17 @@ protected boolean hasError(int statusCode) {
131131
* {@link HttpStatus} enum range.
132132
* </ul>
133133
* @throws UnknownHttpStatusCodeException in case of an unresolvable status code
134-
* @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatusCode)
134+
* @see #handleError(ClientHttpResponse, HttpStatusCode, URI, HttpMethod)
135135
*/
136136
@Override
137137
public void handleError(ClientHttpResponse response) throws IOException {
138-
handleError(null, null, response);
138+
HttpStatusCode statusCode = response.getStatusCode();
139+
handleError(response, statusCode, null, null);
139140
}
140141

141142
/**
142-
* Handle the error in the given response with the given resolved status code.
143+
* Handle the error in the given response with the given resolved status code
144+
* and extra information providing access to the request URL and HTTP method.
143145
* <p>The default implementation throws:
144146
* <ul>
145147
* <li>{@link HttpClientErrorException} if the status code is in the 4xx
@@ -152,58 +154,51 @@ public void handleError(ClientHttpResponse response) throws IOException {
152154
* {@link HttpStatus} enum range.
153155
* </ul>
154156
* @throws UnknownHttpStatusCodeException in case of an unresolvable status code
155-
* @see #handleError(URI, HttpMethod, ClientHttpResponse, HttpStatusCode)
157+
* @since 6.2
158+
* @see #handleError(ClientHttpResponse, HttpStatusCode, URI, HttpMethod)
156159
*/
157160
@Override
158161
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
159162
HttpStatusCode statusCode = response.getStatusCode();
160-
handleError(url, method, response, statusCode);
163+
handleError(response, statusCode, url, method);
161164
}
162165

163166
/**
164167
* Return error message with details from the response body. For example:
165168
* <pre>
166-
* 404 Not Found: [{'id': 123, 'message': 'my message'}]
169+
* 404 Not Found on GET request for "https://example.com": [{'id': 123, 'message': 'my message'}]
167170
* </pre>
168171
*/
169-
private String getErrorMessage(
170-
int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset, @Nullable URI url, @Nullable HttpMethod method) {
172+
private String getErrorMessage(int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset,
173+
@Nullable URI url, @Nullable HttpMethod method) {
171174

172-
String preface = getPreface(rawStatusCode, statusText, url, method);
175+
StringBuilder msg = new StringBuilder(rawStatusCode + " " + statusText);
176+
if (method != null) {
177+
msg.append(" on ").append(method).append(" request");
178+
}
179+
if (url != null) {
180+
msg.append(" for \"");
181+
String urlString = url.toString();
182+
int idx = urlString.indexOf('?');
183+
if (idx != -1) {
184+
msg.append(urlString, 0, idx);
185+
}
186+
else {
187+
msg.append(urlString);
188+
}
189+
msg.append("\"");
190+
}
191+
msg.append(": ");
173192
if (ObjectUtils.isEmpty(responseBody)) {
174-
return preface + "[no body]";
193+
msg.append("[no body]");
175194
}
176-
177-
charset = (charset != null ? charset : StandardCharsets.UTF_8);
178-
179-
String bodyText = new String(responseBody, charset);
180-
bodyText = LogFormatUtils.formatValue(bodyText, -1, true);
181-
182-
return preface + bodyText;
183-
}
184-
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(" ");
195+
else {
196+
charset = (charset != null ? charset : StandardCharsets.UTF_8);
197+
String bodyText = new String(responseBody, charset);
198+
bodyText = LogFormatUtils.formatValue(bodyText, -1, true);
199+
msg.append(bodyText);
189200
}
190-
preface.append(": ");
191-
return preface.toString();
192-
}
193-
194-
/**
195-
* Handle the error based on the resolved status code.
196-
*
197-
* <p>The default implementation delegates to
198-
* {@link HttpClientErrorException#create} for errors in the 4xx range, to
199-
* {@link HttpServerErrorException#create} for errors in the 5xx range,
200-
* or otherwise raises {@link UnknownHttpStatusCodeException}.
201-
* @since 5.0
202-
* @see HttpClientErrorException#create
203-
* @see HttpServerErrorException#create
204-
*/
205-
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode) throws IOException {
206-
handleError(null, null, response, statusCode);
201+
return msg.toString();
207202
}
208203

209204
/**
@@ -213,12 +208,11 @@ protected void handleError(ClientHttpResponse response, HttpStatusCode statusCod
213208
* {@link HttpClientErrorException#create} for errors in the 4xx range, to
214209
* {@link HttpServerErrorException#create} for errors in the 5xx range,
215210
* or otherwise raises {@link UnknownHttpStatusCodeException}.
216-
* @since 5.0
211+
* @since 6.2
217212
* @see HttpClientErrorException#create
218213
* @see HttpServerErrorException#create
219214
*/
220-
protected void handleError(@Nullable URI url, @Nullable HttpMethod method, ClientHttpResponse response,
221-
HttpStatusCode statusCode) throws IOException {
215+
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode, @Nullable URI url, @Nullable HttpMethod method) throws IOException {
222216
String statusText = response.getStatusText();
223217
HttpHeaders headers = response.getHeaders();
224218
byte[] body = getResponseBody(response);

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 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.
@@ -138,7 +138,12 @@ protected boolean hasError(HttpStatusCode statusCode) {
138138
}
139139

140140
@Override
141-
public void handleError(ClientHttpResponse response, HttpStatusCode statusCode) throws IOException {
141+
public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
142+
handleError(response, response.getStatusCode(), url, method);
143+
}
144+
145+
@Override
146+
protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode, @Nullable URI url, @Nullable HttpMethod method) throws IOException {
142147
if (this.statusMapping.containsKey(statusCode)) {
143148
extract(this.statusMapping.get(statusCode), response);
144149
}
@@ -147,17 +152,12 @@ public void handleError(ClientHttpResponse response, HttpStatusCode statusCode)
147152
extract(this.seriesMapping.get(series), response);
148153
}
149154
else {
150-
super.handleError(response, statusCode);
155+
super.handleError(response, statusCode, url, method);
151156
}
152157
}
153158

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

162162
if (exceptionClass == null) {
163163
return;

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

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.http.HttpStatusCode;
3030
import org.springframework.http.MediaType;
3131
import org.springframework.http.client.ClientHttpResponse;
32+
import org.springframework.lang.Nullable;
3233
import org.springframework.util.StreamUtils;
3334

3435
import static org.assertj.core.api.Assertions.assertThat;
@@ -80,19 +81,38 @@ void handleError() throws Exception {
8081
}
8182

8283
@Test
83-
public void handleErrorWithUrlAndMethod() throws Exception {
84-
HttpHeaders headers = new HttpHeaders();
85-
headers.setContentType(MediaType.TEXT_PLAIN);
84+
void handleErrorWithUrlAndMethod() throws Exception {
85+
setupClientHttpResponse(HttpStatus.NOT_FOUND, "Hello World");
86+
assertThatExceptionOfType(HttpClientErrorException.class)
87+
.isThrownBy(() -> handler.handleError(URI.create("https://example.com"), HttpMethod.GET, response))
88+
.withMessage("404 Not Found on GET request for \"https://example.com\": \"Hello World\"");
89+
}
8690

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+
@Test
92+
void handleErrorWithUrlAndQueryParameters() throws Exception {
93+
setupClientHttpResponse(HttpStatus.NOT_FOUND, "Hello World");
94+
assertThatExceptionOfType(HttpClientErrorException.class)
95+
.isThrownBy(() -> handler.handleError(URI.create("https://example.com/resource?access_token=123"), HttpMethod.GET, response))
96+
.withMessage("404 Not Found on GET request for \"https://example.com/resource\": \"Hello World\"");
97+
}
9198

99+
@Test
100+
void handleErrorWithUrlAndNoBody() throws Exception {
101+
setupClientHttpResponse(HttpStatus.NOT_FOUND, null);
92102
assertThatExceptionOfType(HttpClientErrorException.class)
93103
.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));
104+
.withMessage("404 Not Found on GET request for \"https://example.com\": [no body]");
105+
}
106+
107+
private void setupClientHttpResponse(HttpStatus status, @Nullable String textBody) throws Exception {
108+
HttpHeaders headers = new HttpHeaders();
109+
given(response.getStatusCode()).willReturn(status);
110+
given(response.getStatusText()).willReturn(status.getReasonPhrase());
111+
if (textBody != null) {
112+
headers.setContentType(MediaType.TEXT_PLAIN);
113+
given(response.getBody()).willReturn(new ByteArrayInputStream(textBody.getBytes(StandardCharsets.UTF_8)));
114+
}
115+
given(response.getHeaders()).willReturn(headers);
96116
}
97117

98118
@Test

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,40 +241,48 @@ void patchForObject(ClientHttpRequestFactory clientHttpRequestFactory) {
241241
void notFound(ClientHttpRequestFactory clientHttpRequestFactory) {
242242
setUpClient(clientHttpRequestFactory);
243243

244+
String url = baseUrl + "/status/notfound";
244245
assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() ->
245-
template.execute(baseUrl + "/status/notfound", HttpMethod.GET, null, null))
246+
template.execute(url, HttpMethod.GET, null, null))
246247
.satisfies(ex -> {
247248
assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
248249
assertThat(ex.getStatusText()).isNotNull();
249250
assertThat(ex.getResponseBodyAsString()).isNotNull();
250-
assertThat(ex.getMessage()).isEqualTo("404 Client Error after GET http://localhost:" + port + "/status/notfound : [no body]");
251+
assertThat(ex.getMessage()).containsSubsequence("404", "on GET request for \"" + url + "\": [no body]");
252+
assumeFalse(clientHttpRequestFactory instanceof JdkClientHttpRequestFactory, "JDK HttpClient does not expose status text");
253+
assertThat(ex.getMessage()).isEqualTo("404 Client Error on GET request for \"" + url + "\": [no body]");
251254
});
252255
}
253256

254257
@ParameterizedRestTemplateTest
255258
void badRequest(ClientHttpRequestFactory clientHttpRequestFactory) {
256259
setUpClient(clientHttpRequestFactory);
257260

261+
String url = baseUrl + "/status/badrequest";
258262
assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() ->
259-
template.execute(baseUrl + "/status/badrequest", HttpMethod.GET, null, null))
263+
template.execute(url, HttpMethod.GET, null, null))
260264
.satisfies(ex -> {
261265
assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
266+
assertThat(ex.getMessage()).containsSubsequence("400", "on GET request for \""+url+ "\": [no body]");
262267
assumeFalse(clientHttpRequestFactory instanceof JdkClientHttpRequestFactory, "JDK HttpClient does not expose status text");
263-
assertThat(ex.getMessage()).isEqualTo("400 Client Error after GET http://localhost:" + port + "/status/badrequest : [no body]");
268+
assertThat(ex.getMessage()).isEqualTo("400 Client Error on GET request for \""+url+ "\": [no body]");
264269
});
265270
}
266271

267272
@ParameterizedRestTemplateTest
268273
void serverError(ClientHttpRequestFactory clientHttpRequestFactory) {
269274
setUpClient(clientHttpRequestFactory);
270275

276+
String url = baseUrl + "/status/server";
271277
assertThatExceptionOfType(HttpServerErrorException.class).isThrownBy(() ->
272-
template.execute(baseUrl + "/status/server", HttpMethod.GET, null, null))
278+
template.execute(url, HttpMethod.GET, null, null))
273279
.satisfies(ex -> {
274280
assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
275281
assertThat(ex.getStatusText()).isNotNull();
276282
assertThat(ex.getResponseBodyAsString()).isNotNull();
277-
assertThat(ex.getMessage()).isEqualTo("500 Server Error after GET http://localhost:" + port + "/status/server : [no body]");
283+
assertThat(ex.getMessage()).containsSubsequence("500", "on GET request for \"" + url + "\": [no body]");
284+
assumeFalse(clientHttpRequestFactory instanceof JdkClientHttpRequestFactory, "JDK HttpClient does not expose status text");
285+
assertThat(ex.getMessage()).isEqualTo("500 Server Error on GET request for \"" + url + "\": [no body]");
278286
});
279287
}
280288

0 commit comments

Comments
 (0)