From d204dd2dbefb682d2c4504b1b098985d6979bafd Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 1 Dec 2023 14:22:58 +0100 Subject: [PATCH] Use IntrospectingClientHttpResponse in RestClient This commit ensures that the RestClient uses the IntrospectingClientHttpResponse to verify whether the response has a body, and return null if it does not. See gh-12671 Closes gh-31719 --- .../web/client/DefaultRestClient.java | 21 ++++++++++++++----- .../client/RestClientIntegrationTests.java | 17 +++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) 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 702ffc854e..b87c3b8e6a 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 @@ -184,32 +184,40 @@ final class DefaultRestClient implements RestClient { return new DefaultRestClientBuilder(this.builder); } + @Nullable @SuppressWarnings({"rawtypes", "unchecked"}) - private T readWithMessageConverters(ClientHttpResponse clientResponse, Runnable callback, Type bodyType, Class bodyClass) { + private T readWithMessageConverters(ClientHttpResponse clientResponse, Runnable callback, Type bodyType, + Class bodyClass) { + MediaType contentType = getContentType(clientResponse); try (clientResponse) { callback.run(); + IntrospectingClientHttpResponse responseWrapper = new IntrospectingClientHttpResponse(clientResponse); + if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { + return null; + } + for (HttpMessageConverter messageConverter : this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter genericHttpMessageConverter) { if (genericHttpMessageConverter.canRead(bodyType, null, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading to [" + ResolvableType.forType(bodyType) + "]"); } - return (T) genericHttpMessageConverter.read(bodyType, null, clientResponse); + return (T) genericHttpMessageConverter.read(bodyType, null, responseWrapper); } } if (messageConverter.canRead(bodyClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading to [" + bodyClass.getName() + "] as \"" + contentType + "\""); } - return (T) messageConverter.read((Class)bodyClass, clientResponse); + return (T) messageConverter.read((Class)bodyClass, responseWrapper); } } throw new UnknownContentTypeException(bodyType, contentType, - clientResponse.getStatusCode(), clientResponse.getStatusText(), - clientResponse.getHeaders(), RestClientUtils.getBody(clientResponse)); + responseWrapper.getStatusCode(), responseWrapper.getStatusText(), + responseWrapper.getHeaders(), RestClientUtils.getBody(responseWrapper)); } catch (UncheckedIOException | IOException | HttpMessageNotReadableException ex) { throw new RestClientException("Error while extracting response for type [" + @@ -585,11 +593,13 @@ final class DefaultRestClient implements RestClient { } @Override + @Nullable public T body(Class bodyType) { return readBody(bodyType, bodyType); } @Override + @Nullable public T body(ParameterizedTypeReference bodyType) { Type type = bodyType.getType(); Class bodyClass = bodyClass(type); @@ -637,6 +647,7 @@ final class DefaultRestClient implements RestClient { } + @Nullable private T readBody(Type bodyType, Class bodyClass) { return DefaultRestClient.this.readWithMessageConverters(this.clientResponse, this::applyStatusHandlers, bodyType, bodyClass); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index de4b46a5c2..0ac02898fc 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -348,6 +348,23 @@ class RestClientIntegrationTests { assertThat(result).isNull(); } + @ParameterizedRestClientTest + @SuppressWarnings({ "rawtypes", "unchecked" }) + void retrieveJsonEmpty(ClientHttpRequestFactory requestFactory) { + startServer(requestFactory); + + prepareResponse(response -> response + .setResponseCode(200) + .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)); + + Pojo result = this.restClient.get() + .uri("/null") + .retrieve() + .body(Pojo.class); + + assertThat(result).isNull(); + } + @ParameterizedRestClientTest void retrieve404(ClientHttpRequestFactory requestFactory) { startServer(requestFactory);