Browse Source

Merge branch '6.1.x'

pull/32674/head
Brian Clozel 2 years ago
parent
commit
0893cf948d
  1. 50
      spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java
  2. 71
      spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java

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

@ -196,7 +196,7 @@ final class DefaultRestClient implements RestClient { @@ -196,7 +196,7 @@ final class DefaultRestClient implements RestClient {
@Nullable
@SuppressWarnings({"rawtypes", "unchecked"})
private <T> T readWithMessageConverters(ClientHttpResponse clientResponse, Runnable callback, Type bodyType,
Class<T> bodyClass) {
Class<T> bodyClass, @Nullable Observation observation) {
MediaType contentType = getContentType(clientResponse);
@ -224,9 +224,13 @@ final class DefaultRestClient implements RestClient { @@ -224,9 +224,13 @@ final class DefaultRestClient implements RestClient {
return (T) messageConverter.read((Class)bodyClass, responseWrapper);
}
}
throw new UnknownContentTypeException(bodyType, contentType,
UnknownContentTypeException unknownContentTypeException = new UnknownContentTypeException(bodyType, contentType,
responseWrapper.getStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), RestClientUtils.getBody(responseWrapper));
if (observation != null) {
observation.error(unknownContentTypeException);
}
throw unknownContentTypeException;
}
catch (UncheckedIOException | IOException | HttpMessageNotReadableException ex) {
Throwable cause;
@ -236,8 +240,17 @@ final class DefaultRestClient implements RestClient { @@ -236,8 +240,17 @@ final class DefaultRestClient implements RestClient {
else {
cause = ex;
}
throw new RestClientException("Error while extracting response for type [" +
RestClientException restClientException = new RestClientException("Error while extracting response for type [" +
ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", cause);
if (observation != null) {
observation.error(restClientException);
}
throw restClientException;
}
finally {
if (observation != null) {
observation.stop();
}
}
}
@ -476,28 +489,30 @@ final class DefaultRestClient implements RestClient { @@ -476,28 +489,30 @@ final class DefaultRestClient implements RestClient {
}
clientResponse = clientRequest.execute();
observationContext.setResponse(clientResponse);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse);
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse(clientResponse, observation);
return exchangeFunction.exchange(clientRequest, convertibleWrapper);
}
catch (IOException ex) {
ResourceAccessException resourceAccessException = createResourceAccessException(uri, this.httpMethod, ex);
if (observation != null) {
observation.error(resourceAccessException);
observation.stop();
}
throw resourceAccessException;
}
catch (Throwable error) {
if (observation != null) {
observation.error(error);
observation.stop();
}
throw error;
}
finally {
if (close && clientResponse != null) {
clientResponse.close();
}
if (observation != null) {
observation.stop();
if (observation != null) {
observation.stop();
}
}
}
}
@ -667,7 +682,7 @@ final class DefaultRestClient implements RestClient { @@ -667,7 +682,7 @@ final class DefaultRestClient implements RestClient {
@Nullable
private <T> T readBody(Type bodyType, Class<T> bodyClass) {
return DefaultRestClient.this.readWithMessageConverters(this.clientResponse, this::applyStatusHandlers,
bodyType, bodyClass);
bodyType, bodyClass, getCurrentObservation());
}
@ -688,6 +703,15 @@ final class DefaultRestClient implements RestClient { @@ -688,6 +703,15 @@ final class DefaultRestClient implements RestClient {
throw new UncheckedIOException(ex);
}
}
@Nullable
private Observation getCurrentObservation() {
if (this.clientResponse instanceof DefaultConvertibleClientHttpResponse convertibleResponse) {
return convertibleResponse.observation;
}
return null;
}
}
@ -695,16 +719,19 @@ final class DefaultRestClient implements RestClient { @@ -695,16 +719,19 @@ final class DefaultRestClient implements RestClient {
private final ClientHttpResponse delegate;
private final Observation observation;
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate) {
public DefaultConvertibleClientHttpResponse(ClientHttpResponse delegate, Observation observation) {
this.delegate = delegate;
this.observation = observation;
}
@Nullable
@Override
public <T> T bodyTo(Class<T> bodyType) {
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType);
return readWithMessageConverters(this.delegate, () -> {} , bodyType, bodyType, this.observation);
}
@Nullable
@ -712,7 +739,7 @@ final class DefaultRestClient implements RestClient { @@ -712,7 +739,7 @@ final class DefaultRestClient implements RestClient {
public <T> T bodyTo(ParameterizedTypeReference<T> bodyType) {
Type type = bodyType.getType();
Class<T> bodyClass = bodyClass(type);
return readWithMessageConverters(this.delegate, () -> {} , type, bodyClass);
return readWithMessageConverters(this.delegate, () -> {}, type, bodyClass, this.observation);
}
@Override
@ -738,6 +765,7 @@ final class DefaultRestClient implements RestClient { @@ -738,6 +765,7 @@ final class DefaultRestClient implements RestClient {
@Override
public void close() {
this.delegate.close();
this.observation.stop();
}
}

71
spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java

@ -19,6 +19,7 @@ package org.springframework.web.client; @@ -19,6 +19,7 @@ package org.springframework.web.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.UUID;
@ -40,6 +41,7 @@ import org.springframework.http.client.observation.ClientRequestObservationConte @@ -40,6 +41,7 @@ import org.springframework.http.client.observation.ClientRequestObservationConte
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -84,7 +86,7 @@ class RestClientObservationTests { @@ -84,7 +86,7 @@ class RestClientObservationTests {
}
@Test
void executeVarArgsAddsUriTemplateAsKeyValue() throws Exception {
void shouldContributeTemplateWhenUriVariables() throws Exception {
mockSentRequest(GET, "https://example.com/hotels/42/bookings/21");
mockResponseStatus(HttpStatus.OK);
@ -95,7 +97,7 @@ class RestClientObservationTests { @@ -95,7 +97,7 @@ class RestClientObservationTests {
}
@Test
void executeArgsMapAddsUriTemplateAsKeyValue() throws Exception {
void shouldContributeTemplateWhenMap() throws Exception {
mockSentRequest(GET, "https://example.com/hotels/42/bookings/21");
mockResponseStatus(HttpStatus.OK);
@ -107,9 +109,8 @@ class RestClientObservationTests { @@ -107,9 +109,8 @@ class RestClientObservationTests {
assertThatHttpObservation().hasLowCardinalityKeyValue("uri", "/hotels/{hotel}/bookings/{booking}");
}
@Test
void executeAddsSuccessAsOutcome() throws Exception {
void shouldContributeSuccessOutcome() throws Exception {
mockSentRequest(GET, "https://example.org");
mockResponseStatus(HttpStatus.OK);
mockResponseBody("Hello World", MediaType.TEXT_PLAIN);
@ -120,7 +121,7 @@ class RestClientObservationTests { @@ -120,7 +121,7 @@ class RestClientObservationTests {
}
@Test
void executeAddsServerErrorAsOutcome() throws Exception {
void shouldContributeServerErrorOutcome() throws Exception {
String url = "https://example.org";
mockSentRequest(GET, url);
mockResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR);
@ -134,7 +135,7 @@ class RestClientObservationTests { @@ -134,7 +135,7 @@ class RestClientObservationTests {
}
@Test
void executeAddsExceptionAsKeyValue() throws Exception {
void shouldContributeDecodingError() throws Exception {
mockSentRequest(POST, "https://example.org/resource");
mockResponseStatus(HttpStatus.OK);
@ -150,7 +151,7 @@ class RestClientObservationTests { @@ -150,7 +151,7 @@ class RestClientObservationTests {
}
@Test
void executeWithIoExceptionAddsUnknownOutcome() throws Exception {
void shouldContributeIOError() throws Exception {
String url = "https://example.org/resource";
mockSentRequest(GET, url);
given(request.execute()).willThrow(new IOException("Socket failure"));
@ -161,7 +162,7 @@ class RestClientObservationTests { @@ -161,7 +162,7 @@ class RestClientObservationTests {
}
@Test
void executeWithCustomConventionUsesCustomObservationName() throws Exception {
void shouldUseCustomConvention() throws Exception {
ClientRequestObservationConvention observationConvention =
new DefaultClientRequestObservationConvention("custom.requests");
RestClient restClient = this.client.mutate().observationConvention(observationConvention).build();
@ -174,6 +175,56 @@ class RestClientObservationTests { @@ -174,6 +175,56 @@ class RestClientObservationTests {
.hasObservationWithNameEqualTo("custom.requests");
}
@Test
void shouldAddClientDecodingErrorAsException() throws Exception {
String url = "https://example.org";
mockSentRequest(GET, url);
mockResponseStatus(HttpStatus.OK);
mockResponseBody("INVALID", MediaType.APPLICATION_JSON);
assertThatExceptionOfType(RestClientException.class).isThrownBy(() ->
client.get().uri(url).retrieve().body(User.class));
assertThatHttpObservation().hasLowCardinalityKeyValue("exception", "RestClientException");
}
@Test
void shouldAddUnknownContentTypeErrorAsException() throws Exception {
String url = "https://example.org";
mockSentRequest(GET, url);
mockResponseStatus(HttpStatus.OK);
mockResponseBody("Not Found", MediaType.TEXT_HTML);
assertThatExceptionOfType(RestClientException.class).isThrownBy(() ->
client.get().uri(url).retrieve().body(User.class));
assertThatHttpObservation().hasLowCardinalityKeyValue("exception", "UnknownContentTypeException");
}
@Test
void registerObservationWhenReadingBody() throws Exception {
mockSentRequest(GET, "https://example.org");
mockResponseStatus(HttpStatus.OK);
mockResponseBody("Hello World", MediaType.TEXT_PLAIN);
client.get().uri("https://example.org").exchange((request, response) -> response.bodyTo(String.class));
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS");
}
@Test
void registerObservationWhenReadingStream() throws Exception {
mockSentRequest(GET, "https://example.org");
mockResponseStatus(HttpStatus.OK);
mockResponseBody("Hello World", MediaType.TEXT_PLAIN);
client.get().uri("https://example.org").exchange((request, response) -> {
String result = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
response.close();
return result;
}, false);
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS");
}
private void mockSentRequest(HttpMethod method, String uri) throws Exception {
mockSentRequest(method, uri, new HttpHeaders());
@ -220,4 +271,8 @@ class RestClientObservationTests { @@ -220,4 +271,8 @@ class RestClientObservationTests {
}
}
record User(String name) {
}
}

Loading…
Cancel
Save