Skip to content

Commit bff76d7

Browse files
committed
Refactor implementation of retrieve in RestClient
Closes gh-33777
1 parent 8fa99dc commit bff76d7

File tree

2 files changed

+73
-89
lines changed

2 files changed

+73
-89
lines changed

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

+62-87
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ public Builder mutate() {
206206

207207
@Nullable
208208
@SuppressWarnings({"rawtypes", "unchecked"})
209-
private <T> T readWithMessageConverters(ClientHttpResponse clientResponse, Runnable callback, Type bodyType,
210-
Class<T> bodyClass, @Nullable Observation observation) {
209+
private <T> T readWithMessageConverters(
210+
ClientHttpResponse clientResponse, Runnable callback, Type bodyType, Class<T> bodyClass) {
211211

212212
MediaType contentType = getContentType(clientResponse);
213213

@@ -257,21 +257,8 @@ else if (messageConverter.canRead(bodyClass, contentType)) {
257257
else {
258258
cause = exc;
259259
}
260-
RestClientException restClientException = new RestClientException("Error while extracting response for type [" +
260+
throw new RestClientException("Error while extracting response for type [" +
261261
ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", cause);
262-
if (observation != null) {
263-
observation.error(restClientException);
264-
}
265-
throw restClientException;
266-
}
267-
catch (RestClientException restClientException) {
268-
if (observation != null) {
269-
observation.error(restClientException);
270-
}
271-
throw restClientException;
272-
}
273-
finally {
274-
clientResponse.close();
275262
}
276263
}
277264

@@ -536,14 +523,16 @@ private void logBody(Object body, @Nullable MediaType mediaType, HttpMessageConv
536523

537524
@Override
538525
public ResponseSpec retrieve() {
539-
return exchangeInternal(DefaultResponseSpec::new, false);
526+
return new DefaultResponseSpec(this);
540527
}
541528

542529
@Override
530+
@Nullable
543531
public <T> T exchange(ExchangeFunction<T> exchangeFunction, boolean close) {
544532
return exchangeInternal(exchangeFunction, close);
545533
}
546534

535+
@Nullable
547536
private <T> T exchangeInternal(ExchangeFunction<T> exchangeFunction, boolean close) {
548537
Assert.notNull(exchangeFunction, "ExchangeFunction must not be null");
549538

@@ -578,39 +567,31 @@ private <T> T exchangeInternal(ExchangeFunction<T> exchangeFunction, boolean clo
578567
}
579568
clientResponse = clientRequest.execute();
580569
observationContext.setResponse(clientResponse);
581-
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse, observation, observationScope);
570+
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse);
582571
return exchangeFunction.exchange(clientRequest, convertibleWrapper);
583572
}
584573
catch (IOException ex) {
585574
ResourceAccessException resourceAccessException = createResourceAccessException(uri, this.httpMethod, ex);
586-
if (observationScope != null) {
587-
observationScope.close();
588-
}
589575
if (observation != null) {
590576
observation.error(resourceAccessException);
591-
observation.stop();
592577
}
593578
throw resourceAccessException;
594579
}
595580
catch (Throwable error) {
596-
if (observationScope != null) {
597-
observationScope.close();
598-
}
599581
if (observation != null) {
600582
observation.error(error);
601-
observation.stop();
602583
}
603584
throw error;
604585
}
605586
finally {
587+
if (observationScope != null) {
588+
observationScope.close();
589+
}
590+
if (observation != null) {
591+
observation.stop();
592+
}
606593
if (close && clientResponse != null) {
607594
clientResponse.close();
608-
if (observationScope != null) {
609-
observationScope.close();
610-
}
611-
if (observation != null) {
612-
observation.stop();
613-
}
614595
}
615596
}
616597
}
@@ -719,17 +700,14 @@ private interface InternalBody {
719700

720701
private class DefaultResponseSpec implements ResponseSpec {
721702

722-
private final HttpRequest clientRequest;
723-
724-
private final ClientHttpResponse clientResponse;
703+
private final RequestHeadersSpec<?> requestHeadersSpec;
725704

726705
private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
727706

728707
private final int defaultStatusHandlerCount;
729708

730-
DefaultResponseSpec(HttpRequest clientRequest, ClientHttpResponse clientResponse) {
731-
this.clientRequest = clientRequest;
732-
this.clientResponse = clientResponse;
709+
DefaultResponseSpec(RequestHeadersSpec<?> requestHeadersSpec) {
710+
this.requestHeadersSpec = requestHeadersSpec;
733711
this.statusHandlers.addAll(DefaultRestClient.this.defaultStatusHandlers);
734712
this.statusHandlers.add(StatusHandler.defaultHandler(DefaultRestClient.this.messageConverters));
735713
this.defaultStatusHandlerCount = this.statusHandlers.size();
@@ -761,15 +739,15 @@ private ResponseSpec onStatusInternal(StatusHandler statusHandler) {
761739
@Override
762740
@Nullable
763741
public <T> T body(Class<T> bodyType) {
764-
return readBody(bodyType, bodyType);
742+
return executeAndExtract((request, response) -> readBody(request, response, bodyType, bodyType));
765743
}
766744

767745
@Override
768746
@Nullable
769747
public <T> T body(ParameterizedTypeReference<T> bodyType) {
770748
Type type = bodyType.getType();
771749
Class<T> bodyClass = bodyClass(type);
772-
return readBody(type, bodyClass);
750+
return executeAndExtract((request, response) -> readBody(request, response, type, bodyClass));
773751
}
774752

775753
@Override
@@ -785,50 +763,64 @@ public <T> ResponseEntity<T> toEntity(ParameterizedTypeReference<T> bodyType) {
785763
}
786764

787765
private <T> ResponseEntity<T> toEntityInternal(Type bodyType, Class<T> bodyClass) {
788-
T body = readBody(bodyType, bodyClass);
789-
try {
790-
return ResponseEntity.status(this.clientResponse.getStatusCode())
791-
.headers(this.clientResponse.getHeaders())
792-
.body(body);
793-
}
794-
catch (IOException ex) {
795-
throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex);
796-
}
766+
ResponseEntity<T> entity = executeAndExtract((request, response) -> {
767+
T body = readBody(request, response, bodyType, bodyClass);
768+
try {
769+
return ResponseEntity.status(response.getStatusCode())
770+
.headers(response.getHeaders())
771+
.body(body);
772+
}
773+
catch (IOException ex) {
774+
throw new ResourceAccessException(
775+
"Could not retrieve response status code: " + ex.getMessage(), ex);
776+
}
777+
});
778+
Assert.state(entity != null, "No ResponseEntity");
779+
return entity;
797780
}
798781

799782
@Override
800783
public ResponseEntity<Void> toBodilessEntity() {
801-
try (this.clientResponse) {
802-
applyStatusHandlers();
803-
return ResponseEntity.status(this.clientResponse.getStatusCode())
804-
.headers(this.clientResponse.getHeaders())
805-
.build();
806-
}
807-
catch (UncheckedIOException ex) {
808-
throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex.getCause());
809-
}
810-
catch (IOException ex) {
811-
throw new ResourceAccessException("Could not retrieve response status code: " + ex.getMessage(), ex);
812-
}
784+
ResponseEntity<Void> entity = executeAndExtract((request, response) -> {
785+
try (response) {
786+
applyStatusHandlers(request, response);
787+
return ResponseEntity.status(response.getStatusCode())
788+
.headers(response.getHeaders())
789+
.build();
790+
}
791+
catch (UncheckedIOException ex) {
792+
throw new ResourceAccessException(
793+
"Could not retrieve response status code: " + ex.getMessage(), ex.getCause());
794+
}
795+
catch (IOException ex) {
796+
throw new ResourceAccessException(
797+
"Could not retrieve response status code: " + ex.getMessage(), ex);
798+
}
799+
});
800+
Assert.state(entity != null, "No ResponseEntity");
801+
return entity;
813802
}
814803

804+
@Nullable
805+
public <T> T executeAndExtract(RequestHeadersSpec.ExchangeFunction<T> exchangeFunction) {
806+
return this.requestHeadersSpec.exchange(exchangeFunction);
807+
}
815808

816809
@Nullable
817-
private <T> T readBody(Type bodyType, Class<T> bodyClass) {
818-
return DefaultRestClient.this.readWithMessageConverters(this.clientResponse, this::applyStatusHandlers,
819-
bodyType, bodyClass, getCurrentObservation());
810+
private <T> T readBody(HttpRequest request, ClientHttpResponse response, Type bodyType, Class<T> bodyClass) {
811+
return DefaultRestClient.this.readWithMessageConverters(
812+
response, () -> applyStatusHandlers(request, response), bodyType, bodyClass);
820813

821814
}
822815

823-
private void applyStatusHandlers() {
816+
private void applyStatusHandlers(HttpRequest request, ClientHttpResponse response) {
824817
try {
825-
ClientHttpResponse response = this.clientResponse;
826818
if (response instanceof DefaultConvertibleClientHttpResponse convertibleResponse) {
827819
response = convertibleResponse.delegate;
828820
}
829821
for (StatusHandler handler : this.statusHandlers) {
830822
if (handler.test(response)) {
831-
handler.handle(this.clientRequest, response);
823+
handler.handle(request, response);
832824
return;
833825
}
834826
}
@@ -838,44 +830,29 @@ private void applyStatusHandlers() {
838830
}
839831
}
840832

841-
@Nullable
842-
private Observation getCurrentObservation() {
843-
if (this.clientResponse instanceof DefaultConvertibleClientHttpResponse convertibleResponse) {
844-
return convertibleResponse.observation;
845-
}
846-
return null;
847-
}
848-
849833
}
850834

851835

852836
private class DefaultConvertibleClientHttpResponse implements RequestHeadersSpec.ConvertibleClientHttpResponse {
853837

854838
private final ClientHttpResponse delegate;
855839

856-
private final Observation observation;
857-
858-
private final Observation.Scope observationScope;
859-
860-
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate, Observation observation, Observation.Scope observationScope) {
840+
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate) {
861841
this.delegate = delegate;
862-
this.observation = observation;
863-
this.observationScope = observationScope;
864842
}
865843

866-
867844
@Nullable
868845
@Override
869846
public <T> T bodyTo(Class<T> bodyType) {
870-
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.observation);
847+
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType);
871848
}
872849

873850
@Nullable
874851
@Override
875852
public <T> T bodyTo(ParameterizedTypeReference<T> bodyType) {
876853
Type type = bodyType.getType();
877854
Class<T> bodyClass = bodyClass(type);
878-
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.observation);
855+
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass);
879856
}
880857

881858
@Override
@@ -901,8 +878,6 @@ public String getStatusText() throws IOException {
901878
@Override
902879
public void close() {
903880
this.delegate.close();
904-
this.observationScope.close();
905-
this.observation.stop();
906881
}
907882

908883
}

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,10 @@ interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
615615
S httpRequest(Consumer<ClientHttpRequest> requestConsumer);
616616

617617
/**
618-
* Proceed to declare how to extract the response. For example to extract
619-
* a {@link ResponseEntity} with status, headers, and body:
618+
* Enter the retrieve workflow and use the returned {@link ResponseSpec}
619+
* to select from a number of built-in options to extract the response.
620+
* For example:
621+
*
620622
* <pre class="code">
621623
* ResponseEntity&lt;Person&gt; entity = client.get()
622624
* .uri("/persons/1")
@@ -632,6 +634,10 @@ interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
632634
* .retrieve()
633635
* .body(Person.class);
634636
* </pre>
637+
* Note that this method does not actually execute the request until you
638+
* call one of the returned {@link ResponseSpec}. Use the
639+
* {@link #exchange(ExchangeFunction)} variants if you need to separate
640+
* request execution from response extraction.
635641
* <p>By default, 4xx response code result in a
636642
* {@link HttpClientErrorException} and 5xx response codes in a
637643
* {@link HttpServerErrorException}. To customize error handling, use
@@ -664,6 +670,7 @@ interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
664670
* @param <T> the type the response will be transformed to
665671
* @return the value returned from the exchange function
666672
*/
673+
@Nullable
667674
default <T> T exchange(ExchangeFunction<T> exchangeFunction) {
668675
return exchange(exchangeFunction, true);
669676
}
@@ -695,6 +702,7 @@ default <T> T exchange(ExchangeFunction<T> exchangeFunction) {
695702
* @param <T> the type the response will be transformed to
696703
* @return the value returned from the exchange function
697704
*/
705+
@Nullable
698706
<T> T exchange(ExchangeFunction<T> exchangeFunction, boolean close);
699707

700708

@@ -712,6 +720,7 @@ interface ExchangeFunction<T> {
712720
* @return the exchanged type
713721
* @throws IOException in case of I/O errors
714722
*/
723+
@Nullable
715724
T exchange(HttpRequest clientRequest, ConvertibleClientHttpResponse clientResponse) throws IOException;
716725
}
717726

0 commit comments

Comments
 (0)