@ -17,6 +17,8 @@
package org.springframework.web.client ;
package org.springframework.web.client ;
import java.io.IOException ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.UncheckedIOException ;
import java.lang.reflect.ParameterizedType ;
import java.lang.reflect.ParameterizedType ;
import java.lang.reflect.Type ;
import java.lang.reflect.Type ;
import java.net.URI ;
import java.net.URI ;
@ -185,6 +187,61 @@ final class DefaultRestClient implements RestClient {
return new DefaultRestClientBuilder ( this . builder ) ;
return new DefaultRestClientBuilder ( this . builder ) ;
}
}
@SuppressWarnings ( { "rawtypes" , "unchecked" } )
private < T > T readWithMessageConverters ( ClientHttpResponse clientResponse , Runnable callback , Type bodyType , Class < T > bodyClass ) {
MediaType contentType = getContentType ( clientResponse ) ;
try ( clientResponse ) {
callback . run ( ) ;
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 ) ;
}
}
if ( messageConverter . canRead ( bodyClass , contentType ) ) {
if ( logger . isDebugEnabled ( ) ) {
logger . debug ( "Reading to [" + bodyClass . getName ( ) + "] as \"" + contentType + "\"" ) ;
}
return ( T ) messageConverter . read ( ( Class ) bodyClass , clientResponse ) ;
}
}
throw new UnknownContentTypeException ( bodyType , contentType ,
clientResponse . getStatusCode ( ) , clientResponse . getStatusText ( ) ,
clientResponse . getHeaders ( ) , RestClientUtils . getBody ( clientResponse ) ) ;
}
catch ( UncheckedIOException | IOException | HttpMessageNotReadableException ex ) {
throw new RestClientException ( "Error while extracting response for type [" +
ResolvableType . forType ( bodyType ) + "] and content type [" + contentType + "]" , ex ) ;
}
}
private static MediaType getContentType ( ClientHttpResponse clientResponse ) {
MediaType contentType = clientResponse . getHeaders ( ) . getContentType ( ) ;
if ( contentType = = null ) {
contentType = MediaType . APPLICATION_OCTET_STREAM ;
}
return contentType ;
}
@SuppressWarnings ( "unchecked" )
private static < T > Class < T > bodyClass ( Type type ) {
if ( type instanceof Class < ? > clazz ) {
return ( Class < T > ) clazz ;
}
if ( type instanceof ParameterizedType parameterizedType & &
parameterizedType . getRawType ( ) instanceof Class < ? > rawType ) {
return ( Class < T > ) rawType ;
}
return ( Class < T > ) Object . class ;
}
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
@ -409,7 +466,8 @@ final class DefaultRestClient implements RestClient {
}
}
clientResponse = clientRequest . execute ( ) ;
clientResponse = clientRequest . execute ( ) ;
observationContext . setResponse ( clientResponse ) ;
observationContext . setResponse ( clientResponse ) ;
return exchangeFunction . exchange ( clientRequest , clientResponse ) ;
ConvertibleClientHttpResponse convertibleWrapper = new DefaultConvertibleClientHttpResponse ( clientResponse ) ;
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 ) ;
@ -542,14 +600,14 @@ final class DefaultRestClient implements RestClient {
@Override
@Override
public < T > T body ( Class < T > bodyType ) {
public < T > T body ( Class < T > bodyType ) {
return readWithMessageConverters ( bodyType , bodyType ) ;
return readBody ( bodyType , bodyType ) ;
}
}
@Override
@Override
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 readWithMessageConverters ( type , bodyClass ) ;
return readBody ( type , bodyClass ) ;
}
}
@Override
@Override
@ -565,7 +623,7 @@ 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 = readWithMessageConverters ( bodyType , bodyClass ) ;
T body = readBody ( bodyType , bodyClass ) ;
try {
try {
return ResponseEntity . status ( this . clientResponse . getStatusCode ( ) )
return ResponseEntity . status ( this . clientResponse . getStatusCode ( ) )
. headers ( this . clientResponse . getHeaders ( ) )
. headers ( this . clientResponse . getHeaders ( ) )
@ -579,77 +637,96 @@ final class DefaultRestClient implements RestClient {
@Override
@Override
public ResponseEntity < Void > toBodilessEntity ( ) {
public ResponseEntity < Void > toBodilessEntity ( ) {
try ( this . clientResponse ) {
try ( this . clientResponse ) {
applyStatusHandlers ( this . clientRequest , this . clientResponse ) ;
applyStatusHandlers ( ) ;
return ResponseEntity . status ( this . clientResponse . getStatusCode ( ) )
return ResponseEntity . status ( this . clientResponse . getStatusCode ( ) )
. headers ( this . clientResponse . getHeaders ( ) )
. headers ( this . clientResponse . getHeaders ( ) )
. build ( ) ;
. build ( ) ;
}
}
catch ( UncheckedIOException ex ) {
throw new ResourceAccessException ( "Could not retrieve response status code: " + ex . getMessage ( ) , ex . getCause ( ) ) ;
}
catch ( IOException ex ) {
catch ( IOException ex ) {
throw new ResourceAccessException ( "Could not retrieve response status code: " + ex . getMessage ( ) , ex ) ;
throw new ResourceAccessException ( "Could not retrieve response status code: " + ex . getMessage ( ) , ex ) ;
}
}
}
}
@SuppressWarnings ( "unchecked" )
private static < T > Class < T > bodyClass ( Type type ) {
if ( type instanceof Class < ? > clazz ) {
return ( Class < T > ) clazz ;
}
if ( type instanceof ParameterizedType parameterizedType & &
parameterizedType . getRawType ( ) instanceof Class < ? > rawType ) {
return ( Class < T > ) rawType ;
}
return ( Class < T > ) Object . class ;
}
@SuppressWarnings ( { "rawtypes" , "unchecked" } )
private < T > T readBody ( Type bodyType , Class < T > bodyClass ) {
private < T > T readWithMessageConverters ( Type bodyType , Class < T > bodyClass ) {
return DefaultRestClient . this . readWithMessageConverters ( this . clientResponse , this : : applyStatusHandlers ,
MediaType contentType = getContentType ( ) ;
bodyType , bodyClass ) ;
try ( this . clientResponse ) {
}
applyStatusHandlers ( this . clientRequest , this . clientResponse ) ;
private void applyStatusHandlers ( ) {
for ( HttpMessageConverter < ? > messageConverter : DefaultRestClient . this . messageConverters ) {
try {
if ( messageConverter instanceof GenericHttpMessageConverter genericHttpMessageConverter ) {
ClientHttpResponse response = this . clientResponse ;
if ( genericHttpMessageConverter . canRead ( bodyType , null , contentType ) ) {
if ( response instanceof DefaultConvertibleClientHttpResponse convertibleResponse ) {
if ( logger . isDebugEnabled ( ) ) {
response = convertibleResponse . delegate ;
logger . debug ( "Reading to [" + ResolvableType . forType ( bodyType ) + "]" ) ;
}
}
for ( StatusHandler handler : this . statusHandlers ) {
return ( T ) genericHttpMessageConverter . read ( bodyType , null , this . clientResponse ) ;
if ( handler . test ( response ) ) {
}
handler . handle ( this . clientRequest , response ) ;
}
return ;
if ( messageConverter . canRead ( bodyClass , contentType ) ) {
if ( logger . isDebugEnabled ( ) ) {
logger . debug ( "Reading to [" + bodyClass . getName ( ) + "] as \"" + contentType + "\"" ) ;
}
return ( T ) messageConverter . read ( ( Class ) bodyClass , this . clientResponse ) ;
}
}
}
}
throw new UnknownContentTypeException ( bodyType , contentType ,
this . clientResponse . getStatusCode ( ) , this . clientResponse . getStatusText ( ) ,
this . clientResponse . getHeaders ( ) , RestClientUtils . getBody ( this . clientResponse ) ) ;
}
}
catch ( IOException | HttpMessageNotReadableException ex ) {
catch ( IOException ex ) {
throw new RestClientException ( "Error while extracting response for type [" +
throw new UncheckedIOException ( ex ) ;
ResolvableType . forType ( bodyType ) + "] and content type [" + contentType + "]" , ex ) ;
}
}
}
}
}
private MediaType getContentType ( ) {
MediaType contentType = this . clientResponse . getHeaders ( ) . getContentType ( ) ;
private class DefaultConvertibleClientHttpResponse implements RequestHeadersSpec . ConvertibleClientHttpResponse {
if ( contentType = = null ) {
contentType = MediaType . APPLICATION_OCTET_STREAM ;
private final ClientHttpResponse delegate ;
}
return contentType ;
public DefaultConvertibleClientHttpResponse ( ClientHttpResponse delegate ) {
this . delegate = delegate ;
}
}
private void applyStatusHandlers ( HttpRequest request , ClientHttpResponse response ) throws IOException {
for ( StatusHandler handler : this . statusHandlers ) {
@Nullable
if ( handler . test ( response ) ) {
@Override
handler . handle ( request , response ) ;
public < T > T bodyTo ( Class < T > bodyType ) {
return ;
return readWithMessageConverters ( this . delegate , ( ) - > { } , bodyType , bodyType ) ;
}
}
}
@Nullable
@Override
public < T > T bodyTo ( ParameterizedTypeReference < T > bodyType ) {
Type type = bodyType . getType ( ) ;
Class < T > bodyClass = bodyClass ( type ) ;
return readWithMessageConverters ( this . delegate , ( ) - > { } , type , bodyClass ) ;
}
@Override
public InputStream getBody ( ) throws IOException {
return this . delegate . getBody ( ) ;
}
@Override
public HttpHeaders getHeaders ( ) {
return this . delegate . getHeaders ( ) ;
}
@Override
public HttpStatusCode getStatusCode ( ) throws IOException {
return this . delegate . getStatusCode ( ) ;
}
}
@Override
public String getStatusText ( ) throws IOException {
return this . delegate . getStatusText ( ) ;
}
@Override
public void close ( ) {
this . delegate . close ( ) ;
}
}
}
}
}