Browse Source

Refactor implementation of retrieve in RestClient

Closes gh-33777
pull/33793/head
rstoyanchev 1 year ago
parent
commit
bff76d756b
  1. 149
      spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java
  2. 13
      spring-web/src/main/java/org/springframework/web/client/RestClient.java

149
spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java

@ -206,8 +206,8 @@ final class DefaultRestClient implements RestClient {
@Nullable @Nullable
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
private <T> T readWithMessageConverters(ClientHttpResponse clientResponse, Runnable callback, Type bodyType, private <T> T readWithMessageConverters(
Class<T> bodyClass, @Nullable Observation observation) { ClientHttpResponse clientResponse, Runnable callback, Type bodyType, Class<T> bodyClass) {
MediaType contentType = getContentType(clientResponse); MediaType contentType = getContentType(clientResponse);
@ -257,21 +257,8 @@ final class DefaultRestClient implements RestClient {
else { else {
cause = exc; cause = exc;
} }
RestClientException restClientException = new RestClientException("Error while extracting response for type [" + throw new RestClientException("Error while extracting response for type [" +
ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", cause); ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", cause);
if (observation != null) {
observation.error(restClientException);
}
throw restClientException;
}
catch (RestClientException restClientException) {
if (observation != null) {
observation.error(restClientException);
}
throw restClientException;
}
finally {
clientResponse.close();
} }
} }
@ -536,14 +523,16 @@ final class DefaultRestClient implements RestClient {
@Override @Override
public ResponseSpec retrieve() { public ResponseSpec retrieve() {
return exchangeInternal(DefaultResponseSpec::new, false); return new DefaultResponseSpec(this);
} }
@Override @Override
@Nullable
public <T> T exchange(ExchangeFunction<T> exchangeFunction, boolean close) { public <T> T exchange(ExchangeFunction<T> exchangeFunction, boolean close) {
return exchangeInternal(exchangeFunction, close); return exchangeInternal(exchangeFunction, close);
} }
@Nullable
private <T> T exchangeInternal(ExchangeFunction<T> exchangeFunction, boolean close) { private <T> T exchangeInternal(ExchangeFunction<T> exchangeFunction, boolean close) {
Assert.notNull(exchangeFunction, "ExchangeFunction must not be null"); Assert.notNull(exchangeFunction, "ExchangeFunction must not be null");
@ -578,39 +567,31 @@ final class DefaultRestClient implements RestClient {
} }
clientResponse = clientRequest.execute(); clientResponse = clientRequest.execute();
observationContext.setResponse(clientResponse); observationContext.setResponse(clientResponse);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse, observation, observationScope); ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse);
return exchangeFunction.exchange(clientRequest, convertibleWrapper); return exchangeFunction.exchange(clientRequest, convertibleWrapper);
} }
catch (IOException ex) { catch (IOException ex) {
ResourceAccessException resourceAccessException = createResourceAccessException(uri, this.httpMethod, ex); ResourceAccessException resourceAccessException = createResourceAccessException(uri, this.httpMethod, ex);
if (observationScope != null) {
observationScope.close();
}
if (observation != null) { if (observation != null) {
observation.error(resourceAccessException); observation.error(resourceAccessException);
observation.stop();
} }
throw resourceAccessException; throw resourceAccessException;
} }
catch (Throwable error) { catch (Throwable error) {
if (observationScope != null) {
observationScope.close();
}
if (observation != null) { if (observation != null) {
observation.error(error); observation.error(error);
observation.stop();
} }
throw error; throw error;
} }
finally { finally {
if (observationScope != null) {
observationScope.close();
}
if (observation != null) {
observation.stop();
}
if (close && clientResponse != null) { if (close && clientResponse != null) {
clientResponse.close(); clientResponse.close();
if (observationScope != null) {
observationScope.close();
}
if (observation != null) {
observation.stop();
}
} }
} }
} }
@ -719,17 +700,14 @@ final class DefaultRestClient implements RestClient {
private class DefaultResponseSpec implements ResponseSpec { private class DefaultResponseSpec implements ResponseSpec {
private final HttpRequest clientRequest; private final RequestHeadersSpec<?> requestHeadersSpec;
private final ClientHttpResponse clientResponse;
private final List<StatusHandler> statusHandlers = new ArrayList<>(1); private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
private final int defaultStatusHandlerCount; private final int defaultStatusHandlerCount;
DefaultResponseSpec(HttpRequest clientRequest, ClientHttpResponse clientResponse) { DefaultResponseSpec(RequestHeadersSpec<?> requestHeadersSpec) {
this.clientRequest = clientRequest; this.requestHeadersSpec = requestHeadersSpec;
this.clientResponse = clientResponse;
this.statusHandlers.addAll(DefaultRestClient.this.defaultStatusHandlers); this.statusHandlers.addAll(DefaultRestClient.this.defaultStatusHandlers);
this.statusHandlers.add(StatusHandler.defaultHandler(DefaultRestClient.this.messageConverters)); this.statusHandlers.add(StatusHandler.defaultHandler(DefaultRestClient.this.messageConverters));
this.defaultStatusHandlerCount = this.statusHandlers.size(); this.defaultStatusHandlerCount = this.statusHandlers.size();
@ -761,7 +739,7 @@ final class DefaultRestClient implements RestClient {
@Override @Override
@Nullable @Nullable
public <T> T body(Class<T> bodyType) { public <T> T body(Class<T> bodyType) {
return readBody(bodyType, bodyType); return executeAndExtract((request, response) -> readBody(request, response, bodyType, bodyType));
} }
@Override @Override
@ -769,7 +747,7 @@ final class DefaultRestClient implements RestClient {
public <T> T body(ParameterizedTypeReference<T> bodyType) { public <T> T body(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType(); Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type); Class<T> bodyClass = bodyClass(type);
return readBody(type, bodyClass); return executeAndExtract((request, response) -> readBody(request, response, type, bodyClass));
} }
@Override @Override
@ -785,50 +763,64 @@ final class DefaultRestClient implements RestClient {
} }
private <T> ResponseEntity<T> toEntityInternal(Type bodyType, Class<T> bodyClass) { private <T> ResponseEntity<T> toEntityInternal(Type bodyType, Class<T> bodyClass) {
T body = readBody(bodyType, bodyClass); ResponseEntity<T> entity = executeAndExtract((request, response) -> {
try { T body = readBody(request, response, bodyType, bodyClass);
return ResponseEntity.status(this.clientResponse.getStatusCode()) try {
.headers(this.clientResponse.getHeaders()) return ResponseEntity.status(response.getStatusCode())
.body(body); .headers(response.getHeaders())
} .body(body);
catch (IOException ex) { }
throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex); catch (IOException ex) {
} throw new ResourceAccessException(
"Could not retrieve response status code: " + ex.getMessage(), ex);
}
});
Assert.state(entity != null, "No ResponseEntity");
return entity;
} }
@Override @Override
public ResponseEntity<Void> toBodilessEntity() { public ResponseEntity<Void> toBodilessEntity() {
try (this.clientResponse) { ResponseEntity<Void> entity = executeAndExtract((request, response) -> {
applyStatusHandlers(); try (response) {
return ResponseEntity.status(this.clientResponse.getStatusCode()) applyStatusHandlers(request, response);
.headers(this.clientResponse.getHeaders()) return ResponseEntity.status(response.getStatusCode())
.build(); .headers(response.getHeaders())
} .build();
catch (UncheckedIOException ex) { }
throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex.getCause()); catch (UncheckedIOException ex) {
} throw new ResourceAccessException(
catch (IOException ex) { "Could not retrieve response status code: " + ex.getMessage(), ex.getCause());
throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex); }
} catch (IOException ex) {
throw new ResourceAccessException(
"Could not retrieve response status code: " + ex.getMessage(), ex);
}
});
Assert.state(entity != null, "No ResponseEntity");
return entity;
} }
@Nullable
public <T> T executeAndExtract(RequestHeadersSpec.ExchangeFunction<T> exchangeFunction) {
return this.requestHeadersSpec.exchange(exchangeFunction);
}
@Nullable @Nullable
private <T> T readBody(Type bodyType, Class<T> bodyClass) { private <T> T readBody(HttpRequest request, ClientHttpResponse response, Type bodyType, Class<T> bodyClass) {
return DefaultRestClient.this.readWithMessageConverters(this.clientResponse, this::applyStatusHandlers, return DefaultRestClient.this.readWithMessageConverters(
bodyType, bodyClass, getCurrentObservation()); response, () -> applyStatusHandlers(request, response), bodyType, bodyClass);
} }
private void applyStatusHandlers() { private void applyStatusHandlers(HttpRequest request, ClientHttpResponse response) {
try { try {
ClientHttpResponse response = this.clientResponse;
if (response instanceof DefaultConvertibleClientHttpResponse convertibleResponse) { if (response instanceof DefaultConvertibleClientHttpResponse convertibleResponse) {
response = convertibleResponse.delegate; response = convertibleResponse.delegate;
} }
for (StatusHandler handler : this.statusHandlers) { for (StatusHandler handler : this.statusHandlers) {
if (handler.test(response)) { if (handler.test(response)) {
handler.handle(this.clientRequest, response); handler.handle(request, response);
return; return;
} }
} }
@ -838,14 +830,6 @@ final class DefaultRestClient implements RestClient {
} }
} }
@Nullable
private Observation getCurrentObservation() {
if (this.clientResponse instanceof DefaultConvertibleClientHttpResponse convertibleResponse) {
return convertibleResponse.observation;
}
return null;
}
} }
@ -853,21 +837,14 @@ final class DefaultRestClient implements RestClient {
private final ClientHttpResponse delegate; private final ClientHttpResponse delegate;
private final Observation observation; public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate) {
private final Observation.Scope observationScope;
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate, Observation observation, Observation.Scope observationScope) {
this.delegate = delegate; this.delegate = delegate;
this.observation = observation;
this.observationScope = observationScope;
} }
@Nullable @Nullable
@Override @Override
public <T> T bodyTo(Class<T> bodyType) { public <T> T bodyTo(Class<T> bodyType) {
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.observation); return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType);
} }
@Nullable @Nullable
@ -875,7 +852,7 @@ final class DefaultRestClient implements RestClient {
public <T> T bodyTo(ParameterizedTypeReference<T> bodyType) { public <T> T bodyTo(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType(); Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type); Class<T> bodyClass = bodyClass(type);
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.observation); return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass);
} }
@Override @Override
@ -901,8 +878,6 @@ final class DefaultRestClient implements RestClient {
@Override @Override
public void close() { public void close() {
this.delegate.close(); this.delegate.close();
this.observationScope.close();
this.observation.stop();
} }
} }

13
spring-web/src/main/java/org/springframework/web/client/RestClient.java

@ -615,8 +615,10 @@ public interface RestClient {
S httpRequest(Consumer<ClientHttpRequest> requestConsumer); S httpRequest(Consumer<ClientHttpRequest> requestConsumer);
/** /**
* Proceed to declare how to extract the response. For example to extract * Enter the retrieve workflow and use the returned {@link ResponseSpec}
* a {@link ResponseEntity} with status, headers, and body: * to select from a number of built-in options to extract the response.
* For example:
*
* <pre class="code"> * <pre class="code">
* ResponseEntity&lt;Person&gt; entity = client.get() * ResponseEntity&lt;Person&gt; entity = client.get()
* .uri("/persons/1") * .uri("/persons/1")
@ -632,6 +634,10 @@ public interface RestClient {
* .retrieve() * .retrieve()
* .body(Person.class); * .body(Person.class);
* </pre> * </pre>
* Note that this method does not actually execute the request until you
* call one of the returned {@link ResponseSpec}. Use the
* {@link #exchange(ExchangeFunction)} variants if you need to separate
* request execution from response extraction.
* <p>By default, 4xx response code result in a * <p>By default, 4xx response code result in a
* {@link HttpClientErrorException} and 5xx response codes in a * {@link HttpClientErrorException} and 5xx response codes in a
* {@link HttpServerErrorException}. To customize error handling, use * {@link HttpServerErrorException}. To customize error handling, use
@ -664,6 +670,7 @@ public interface RestClient {
* @param <T> the type the response will be transformed to * @param <T> the type the response will be transformed to
* @return the value returned from the exchange function * @return the value returned from the exchange function
*/ */
@Nullable
default <T> T exchange(ExchangeFunction<T> exchangeFunction) { default <T> T exchange(ExchangeFunction<T> exchangeFunction) {
return exchange(exchangeFunction, true); return exchange(exchangeFunction, true);
} }
@ -695,6 +702,7 @@ public interface RestClient {
* @param <T> the type the response will be transformed to * @param <T> the type the response will be transformed to
* @return the value returned from the exchange function * @return the value returned from the exchange function
*/ */
@Nullable
<T> T exchange(ExchangeFunction<T> exchangeFunction, boolean close); <T> T exchange(ExchangeFunction<T> exchangeFunction, boolean close);
@ -712,6 +720,7 @@ public interface RestClient {
* @return the exchanged type * @return the exchanged type
* @throws IOException in case of I/O errors * @throws IOException in case of I/O errors
*/ */
@Nullable
T exchange(HttpRequest clientRequest, ConvertibleClientHttpResponse clientResponse) throws IOException; T exchange(HttpRequest clientRequest, ConvertibleClientHttpResponse clientResponse) throws IOException;
} }

Loading…
Cancel
Save