Introduce SmartHttpMessageConverter
SmartHttpMessageConverter is similar to GenericHttpMessageConverter, but more consistent with WebFlux Encoder and Decoder contracts, with the following differences: - A ResolvableType parameter is used instead of the Type one - The MethodParameter can be retrieved via the ResolvableType source - No contextClass parameter - `@Nullable Map<String, Object> hints` additional parameter for write and read methods This commit also refines RestTemplate#canReadResponse in order to use the most specific converter contract when possible. Closes gh-33118
This commit is contained in:
@@ -25,6 +25,7 @@ import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -33,12 +34,14 @@ import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@@ -166,6 +169,26 @@ class HttpMessageConverterExtractorTests {
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void smartConverter() throws IOException {
|
||||
responseHeaders.setContentType(contentType);
|
||||
String expected = "Foo";
|
||||
ParameterizedTypeReference<List<String>> reference = new ParameterizedTypeReference<>() {};
|
||||
ResolvableType resolvableType = ResolvableType.forType(reference.getType());
|
||||
|
||||
SmartHttpMessageConverter<String> converter = mock();
|
||||
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor<List<String>>(resolvableType.getType(), List.of(converter));
|
||||
|
||||
given(response.getStatusCode()).willReturn(HttpStatus.OK);
|
||||
given(response.getHeaders()).willReturn(responseHeaders);
|
||||
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.getBytes()));
|
||||
given(converter.canRead(resolvableType, contentType)).willReturn(true);
|
||||
given(converter.read(eq(resolvableType), any(HttpInputMessage.class), isNull())).willReturn(expected);
|
||||
|
||||
Object result = extractor.extractData(response);
|
||||
assertThat(result).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // SPR-13592
|
||||
void converterThrowsIOException() throws IOException {
|
||||
responseHeaders.setContentType(contentType);
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
@@ -50,6 +51,7 @@ import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
@@ -683,6 +685,41 @@ class RestTemplateTests {
|
||||
verify(response).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("rawtypes")
|
||||
void exchangeParameterizedTypeWithSmartConverter() throws Exception {
|
||||
SmartHttpMessageConverter converter = mock();
|
||||
template.setMessageConverters(Collections.singletonList(converter));
|
||||
ParameterizedTypeReference<List<Integer>> intList = new ParameterizedTypeReference<>() {};
|
||||
given(converter.canRead(ResolvableType.forType(intList.getType()), null)).willReturn(true);
|
||||
given(converter.getSupportedMediaTypes(any())).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(converter.canWrite(ResolvableType.forClass(String.class), String.class, null)).willReturn(true);
|
||||
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
mockSentRequest(POST, "https://example.com", requestHeaders);
|
||||
List<Integer> expected = Collections.singletonList(42);
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
responseHeaders.setContentLength(10);
|
||||
mockResponseStatus(HttpStatus.OK);
|
||||
given(response.getHeaders()).willReturn(responseHeaders);
|
||||
given(response.getBody()).willReturn(new ByteArrayInputStream(Integer.toString(42).getBytes()));
|
||||
given(converter.canRead(ResolvableType.forType(intList.getType()), MediaType.TEXT_PLAIN)).willReturn(true);
|
||||
given(converter.read(eq(ResolvableType.forType(intList.getType())), any(HttpInputMessage.class), eq(null))).willReturn(expected);
|
||||
|
||||
HttpHeaders entityHeaders = new HttpHeaders();
|
||||
entityHeaders.set("MyHeader", "MyValue");
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>("Hello World", entityHeaders);
|
||||
ResponseEntity<List<Integer>> result = template.exchange("https://example.com", POST, requestEntity, intList);
|
||||
assertThat(result.getBody()).as("Invalid POST result").isEqualTo(expected);
|
||||
assertThat(result.getHeaders().getContentType()).as("Invalid Content-Type").isEqualTo(MediaType.TEXT_PLAIN);
|
||||
assertThat(requestHeaders.getFirst("Accept")).as("Invalid Accept header").isEqualTo(MediaType.TEXT_PLAIN_VALUE);
|
||||
assertThat(requestHeaders.getFirst("MyHeader")).as("Invalid custom header").isEqualTo("MyValue");
|
||||
assertThat(result.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK);
|
||||
|
||||
verify(response).close();
|
||||
}
|
||||
|
||||
@Test // SPR-15066
|
||||
void requestInterceptorCanAddExistingHeaderValueWithoutBody() throws Exception {
|
||||
ClientHttpRequestInterceptor interceptor = (request, body, execution) -> {
|
||||
|
||||
Reference in New Issue
Block a user