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.
1919import java .io .ByteArrayInputStream ;
2020import java .io .IOException ;
2121import java .io .InputStream ;
22+ import java .net .URI ;
2223import java .nio .charset .Charset ;
2324import java .nio .charset .StandardCharsets ;
2425import java .util .Collections ;
2829import org .springframework .core .ResolvableType ;
2930import org .springframework .core .log .LogFormatUtils ;
3031import org .springframework .http .HttpHeaders ;
32+ import org .springframework .http .HttpMethod ;
3133import org .springframework .http .HttpStatus ;
3234import org .springframework .http .HttpStatusCode ;
3335import org .springframework .http .MediaType ;
@@ -129,35 +131,74 @@ 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(ClientHttpResponse, HttpStatusCode, URI, HttpMethod )
133135 */
134136 @ Override
135137 public void handleError (ClientHttpResponse response ) throws IOException {
136138 HttpStatusCode statusCode = response .getStatusCode ();
137- handleError (response , statusCode );
139+ handleError (response , statusCode , null , null );
140+ }
141+
142+ /**
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.
145+ * <p>The default implementation throws:
146+ * <ul>
147+ * <li>{@link HttpClientErrorException} if the status code is in the 4xx
148+ * series, or one of its sub-classes such as
149+ * {@link HttpClientErrorException.BadRequest} and others.
150+ * <li>{@link HttpServerErrorException} if the status code is in the 5xx
151+ * series, or one of its sub-classes such as
152+ * {@link HttpServerErrorException.InternalServerError} and others.
153+ * <li>{@link UnknownHttpStatusCodeException} for error status codes not in the
154+ * {@link HttpStatus} enum range.
155+ * </ul>
156+ * @throws UnknownHttpStatusCodeException in case of an unresolvable status code
157+ * @since 6.2
158+ * @see #handleError(ClientHttpResponse, HttpStatusCode, URI, HttpMethod)
159+ */
160+ @ Override
161+ public void handleError (URI url , HttpMethod method , ClientHttpResponse response ) throws IOException {
162+ HttpStatusCode statusCode = response .getStatusCode ();
163+ handleError (response , statusCode , url , method );
138164 }
139165
140166 /**
141167 * Return error message with details from the response body. For example:
142168 * <pre>
143- * 404 Not Found: [{'id': 123, 'message': 'my message'}]
169+ * 404 Not Found on GET request for "https://example.com" : [{'id': 123, 'message': 'my message'}]
144170 * </pre>
145171 */
146- private String getErrorMessage (
147- int rawStatusCode , String statusText , @ Nullable byte [] responseBody , @ Nullable Charset charset ) {
148-
149- String preface = rawStatusCode + " " + statusText + ": " ;
172+ private String getErrorMessage (int rawStatusCode , String statusText , @ Nullable byte [] responseBody , @ Nullable Charset charset ,
173+ @ Nullable URI url , @ Nullable HttpMethod method ) {
150174
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 (": " );
151192 if (ObjectUtils .isEmpty (responseBody )) {
152- return preface + "[no body]" ;
193+ msg . append ( "[no body]" ) ;
153194 }
154-
155- charset = (charset != null ? charset : StandardCharsets .UTF_8 );
156-
157- String bodyText = new String ( responseBody , charset );
158- bodyText = LogFormatUtils . formatValue (bodyText , - 1 , true );
159-
160- return preface + bodyText ;
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 );
200+ }
201+ return msg . toString () ;
161202 }
162203
163204 /**
@@ -167,16 +208,16 @@ private String getErrorMessage(
167208 * {@link HttpClientErrorException#create} for errors in the 4xx range, to
168209 * {@link HttpServerErrorException#create} for errors in the 5xx range,
169210 * or otherwise raises {@link UnknownHttpStatusCodeException}.
170- * @since 5.0
211+ * @since 6.2
171212 * @see HttpClientErrorException#create
172213 * @see HttpServerErrorException#create
173214 */
174- protected void handleError (ClientHttpResponse response , HttpStatusCode statusCode ) throws IOException {
215+ protected void handleError (ClientHttpResponse response , HttpStatusCode statusCode , @ Nullable URI url , @ Nullable HttpMethod method ) throws IOException {
175216 String statusText = response .getStatusText ();
176217 HttpHeaders headers = response .getHeaders ();
177218 byte [] body = getResponseBody (response );
178219 Charset charset = getCharset (response );
179- String message = getErrorMessage (statusCode .value (), statusText , body , charset );
220+ String message = getErrorMessage (statusCode .value (), statusText , body , charset , url , method );
180221
181222 RestClientResponseException ex ;
182223 if (statusCode .is4xxClientError ()) {
0 commit comments