diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index 5f7e04535ea..d5d1fe3876d 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -775,7 +775,7 @@ final class DefaultRestClient implements RestClient { DefaultResponseSpec(RequestHeadersSpec requestHeadersSpec) { this.requestHeadersSpec = requestHeadersSpec; this.statusHandlers.addAll(DefaultRestClient.this.defaultStatusHandlers); - this.statusHandlers.add(StatusHandler.defaultHandler(DefaultRestClient.this.messageConverters)); + this.statusHandlers.add(StatusHandler.createDefaultStatusHandler(DefaultRestClient.this.messageConverters)); this.defaultStatusHandlerCount = this.statusHandlers.size(); } @@ -886,7 +886,10 @@ final class DefaultRestClient implements RestClient { return this.requestHeadersSpec.exchange(exchangeFunction); } - private @Nullable T readBody(HttpRequest request, ClientHttpResponse response, Type bodyType, Class bodyClass, @Nullable Map hints) { + private @Nullable T readBody( + HttpRequest request, ClientHttpResponse response, Type bodyType, Class bodyClass, + @Nullable Map hints) { + return DefaultRestClient.this.readWithMessageConverters( response, () -> applyStatusHandlers(request, response), bodyType, bodyClass, hints); @@ -922,28 +925,6 @@ final class DefaultRestClient implements RestClient { this.hints = hints; } - @Override - public @Nullable T bodyTo(Class bodyType) { - return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.hints); - } - - @Override - public @Nullable T bodyTo(ParameterizedTypeReference bodyType) { - Type type = bodyType.getType(); - Class bodyClass = bodyClass(type); - return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.hints); - } - - @Override - public InputStream getBody() throws IOException { - return this.delegate.getBody(); - } - - @Override - public HttpHeaders getHeaders() { - return this.delegate.getHeaders(); - } - @Override public HttpStatusCode getStatusCode() throws IOException { return this.delegate.getStatusCode(); @@ -954,10 +935,32 @@ final class DefaultRestClient implements RestClient { return this.delegate.getStatusText(); } + @Override + public HttpHeaders getHeaders() { + return this.delegate.getHeaders(); + } + + @Override + public InputStream getBody() throws IOException { + return this.delegate.getBody(); + } + @Override public void close() { this.delegate.close(); } + + @Override + public @Nullable T bodyTo(Class bodyType) { + return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.hints); + } + + @Override + public @Nullable T bodyTo(ParameterizedTypeReference bodyType) { + Type type = bodyType.getType(); + Class bodyClass = bodyClass(type); + return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.hints); + } } } diff --git a/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java b/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java index b94293775f3..96a7a5eb3d8 100644 --- a/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/StatusHandler.java @@ -39,68 +39,122 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; /** - * Used by {@link DefaultRestClient} and {@link DefaultRestClientBuilder}. + * Simple container for an error response Predicate and an error response handler + * to support the status handling mechanism of {@link RestClient.ResponseSpec}. * * @author Arjen Poutsma + * @author Rossen Stoyanchev * @since 6.1 */ final class StatusHandler { private final ResponsePredicate predicate; - private final RestClient.ResponseSpec.ErrorHandler errorHandler; + private final RestClient.ResponseSpec.ErrorHandler handler; - private StatusHandler(ResponsePredicate predicate, RestClient.ResponseSpec.ErrorHandler errorHandler) { + private StatusHandler(ResponsePredicate predicate, RestClient.ResponseSpec.ErrorHandler handler) { this.predicate = predicate; - this.errorHandler = errorHandler; + this.handler = handler; } - public static StatusHandler of(Predicate predicate, - RestClient.ResponseSpec.ErrorHandler errorHandler) { + /** + * Test whether the response has any errors. + */ + public boolean test(ClientHttpResponse response) throws IOException { + return this.predicate.test(response); + } + + /** + * Handle the error in the given response. + *

This method is only called when {@link #test(ClientHttpResponse)} + * has returned {@code true}. +ß */ + public void handle(HttpRequest request, ClientHttpResponse response) throws IOException { + this.handler.handle(request, response); + } + + + /** + * Create a StatusHandler from a RestClient {@link RestClient.ResponseSpec.ErrorHandler}. + */ + public static StatusHandler of( + Predicate predicate, RestClient.ResponseSpec.ErrorHandler errorHandler) { + Assert.notNull(predicate, "Predicate must not be null"); Assert.notNull(errorHandler, "ErrorHandler must not be null"); return new StatusHandler(response -> predicate.test(response.getStatusCode()), errorHandler); } + /** + * Create a StatusHandler from a {@link ResponseErrorHandler}. + */ public static StatusHandler fromErrorHandler(ResponseErrorHandler errorHandler) { Assert.notNull(errorHandler, "ResponseErrorHandler must not be null"); - return new StatusHandler(errorHandler::hasError, (request, response) -> - errorHandler.handleError(request.getURI(), request.getMethod(), response)); + return new StatusHandler(errorHandler::hasError, + (request, response) -> errorHandler.handleError(request.getURI(), request.getMethod(), response)); } - public static StatusHandler defaultHandler(List> messageConverters) { + /** + * Create a StatusHandler for default error response handling. + */ + public static StatusHandler createDefaultStatusHandler(List> converters) { return new StatusHandler(response -> response.getStatusCode().isError(), (request, response) -> { - HttpStatusCode statusCode = response.getStatusCode(); - String statusText = response.getStatusText(); - HttpHeaders headers = response.getHeaders(); - byte[] body = RestClientUtils.getBody(response); - Charset charset = RestClientUtils.getCharset(response); - String message = getErrorMessage(statusCode.value(), statusText, body, charset); - RestClientResponseException ex; - - if (statusCode.is4xxClientError()) { - ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset); - } - else if (statusCode.is5xxServerError()) { - ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset); - } - else { - ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset); - } - if (!CollectionUtils.isEmpty(messageConverters)) { - ex.setBodyConvertFunction(initBodyConvertFunction(response, body, messageConverters)); - } - throw ex; + throw createException(response, converters); }); } + private static RestClientResponseException createException( + ClientHttpResponse response, List> converters) throws IOException { + + HttpStatusCode statusCode = response.getStatusCode(); + String statusText = response.getStatusText(); + HttpHeaders headers = response.getHeaders(); + byte[] body = RestClientUtils.getBody(response); + Charset charset = RestClientUtils.getCharset(response); + String message = getErrorMessage(statusCode.value(), statusText, body, charset); + RestClientResponseException ex; + + if (statusCode.is4xxClientError()) { + ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset); + } + else if (statusCode.is5xxServerError()) { + ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset); + } + else { + ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset); + } + if (!CollectionUtils.isEmpty(converters)) { + ex.setBodyConvertFunction(initBodyConvertFunction(response, body, converters)); + } + return ex; + } + + private static String getErrorMessage( + int rawStatusCode, String statusText, byte @Nullable [] responseBody, @Nullable Charset charset) { + + String preface = rawStatusCode + " " + statusText + ": "; + + if (ObjectUtils.isEmpty(responseBody)) { + return preface + "[no body]"; + } + + charset = (charset != null ? charset : StandardCharsets.UTF_8); + + String bodyText = new String(responseBody, charset); + bodyText = LogFormatUtils.formatValue(bodyText, -1, true); + + return preface + bodyText; + } + @SuppressWarnings("NullAway") - private static Function initBodyConvertFunction(ClientHttpResponse response, byte[] body, List> messageConverters) { + private static Function initBodyConvertFunction( + ClientHttpResponse response, byte[] body, List> messageConverters) { + Assert.state(!CollectionUtils.isEmpty(messageConverters), "Expected message converters"); return resolvableType -> { try { @@ -121,34 +175,6 @@ final class StatusHandler { } - private static String getErrorMessage(int rawStatusCode, String statusText, byte @Nullable [] responseBody, - @Nullable Charset charset) { - - String preface = rawStatusCode + " " + statusText + ": "; - - if (ObjectUtils.isEmpty(responseBody)) { - return preface + "[no body]"; - } - - charset = (charset != null ? charset : StandardCharsets.UTF_8); - - String bodyText = new String(responseBody, charset); - bodyText = LogFormatUtils.formatValue(bodyText, -1, true); - - return preface + bodyText; - } - - - - public boolean test(ClientHttpResponse response) throws IOException { - return this.predicate.test(response); - } - - public void handle(HttpRequest request, ClientHttpResponse response) throws IOException { - this.errorHandler.handle(request, response); - } - - @FunctionalInterface private interface ResponsePredicate {