All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.web.client.RestTemplate Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.client;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.observation.ClientHttpObservationDocumentation;
import org.springframework.http.client.observation.ClientRequestObservationContext;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
import org.springframework.http.client.support.InterceptingHttpAccessor;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import org.springframework.web.util.UriTemplateHandler;

/**
 * Synchronous client to perform HTTP requests, exposing a simple, template
 * method API over underlying HTTP client libraries such as the JDK
 * {@code HttpURLConnection}, Apache HttpComponents, and others. RestTemplate
 * offers templates for common scenarios by HTTP method, in addition to the
 * generalized {@code exchange} and {@code execute} methods that support of
 * less frequent cases.
 *
 * 

RestTemplate is typically used as a shared component. However, its * configuration does not support concurrent modification, and as such its * configuration is typically prepared on startup. If necessary, you can create * multiple, differently configured RestTemplate instances on startup. Such * instances may use the same the underlying {@link ClientHttpRequestFactory} * if they need to share HTTP client resources. * *

NOTE: As of 5.0 this class is in maintenance mode, with * only minor requests for changes and bugs to be accepted going forward. Please, * consider using the {@code org.springframework.web.reactive.client.WebClient} * which has a more modern API and supports sync, async, and streaming scenarios. * * @author Arjen Poutsma * @author Brian Clozel * @author Roy Clarkson * @author Juergen Hoeller * @author Sam Brannen * @author Sebastien Deleuze * @since 3.0 * @see HttpMessageConverter * @see RequestCallback * @see ResponseExtractor * @see ResponseErrorHandler */ public class RestTemplate extends InterceptingHttpAccessor implements RestOperations { private static final boolean romePresent; private static final boolean jaxb2Present; private static final boolean jackson2Present; private static final boolean jackson2XmlPresent; private static final boolean jackson2SmilePresent; private static final boolean jackson2CborPresent; private static final boolean gsonPresent; private static final boolean jsonbPresent; private static final boolean kotlinSerializationCborPresent; private static final boolean kotlinSerializationJsonPresent; private static final boolean kotlinSerializationProtobufPresent; private static final ClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention(); static { ClassLoader classLoader = RestTemplate.class.getClassLoader(); romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader); jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader); kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader); kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader); kotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader); } private final List> messageConverters = new ArrayList<>(); private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler(); private UriTemplateHandler uriTemplateHandler; private final ResponseExtractor headersExtractor = new HeadersExtractor(); private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; @Nullable private ClientRequestObservationConvention observationConvention; /** * Create a new instance of the {@link RestTemplate} using default settings. * Default {@link HttpMessageConverter HttpMessageConverters} are initialized. */ public RestTemplate() { this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); this.messageConverters.add(new ResourceHttpMessageConverter(false)); this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { this.messageConverters.add(new AtomFeedHttpMessageConverter()); this.messageConverters.add(new RssChannelHttpMessageConverter()); } if (jackson2XmlPresent) { this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter()); } else if (jaxb2Present) { this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (kotlinSerializationProtobufPresent) { this.messageConverters.add(new KotlinSerializationProtobufHttpMessageConverter()); } if (kotlinSerializationJsonPresent) { this.messageConverters.add(new KotlinSerializationJsonHttpMessageConverter()); } if (jackson2Present) { this.messageConverters.add(new MappingJackson2HttpMessageConverter()); } else if (gsonPresent) { this.messageConverters.add(new GsonHttpMessageConverter()); } else if (jsonbPresent) { this.messageConverters.add(new JsonbHttpMessageConverter()); } if (jackson2SmilePresent) { this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter()); } if (jackson2CborPresent) { this.messageConverters.add(new MappingJackson2CborHttpMessageConverter()); } else if (kotlinSerializationCborPresent) { this.messageConverters.add(new KotlinSerializationCborHttpMessageConverter()); } updateErrorHandlerConverters(); this.uriTemplateHandler = initUriTemplateHandler(); } /** * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}. * @param requestFactory the HTTP request factory to use * @see org.springframework.http.client.SimpleClientHttpRequestFactory * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory */ public RestTemplate(ClientHttpRequestFactory requestFactory) { this(); setRequestFactory(requestFactory); } /** * Create a new instance of the {@link RestTemplate} using the given list of * {@link HttpMessageConverter} to use. * @param messageConverters the list of {@link HttpMessageConverter} to use * @since 3.2.7 */ public RestTemplate(List> messageConverters) { validateConverters(messageConverters); this.messageConverters.addAll(messageConverters); this.uriTemplateHandler = initUriTemplateHandler(); updateErrorHandlerConverters(); } private void updateErrorHandlerConverters() { if (this.errorHandler instanceof DefaultResponseErrorHandler handler) { handler.setMessageConverters(this.messageConverters); } } private static DefaultUriBuilderFactory initUriTemplateHandler() { DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); uriFactory.setEncodingMode(EncodingMode.URI_COMPONENT); // for backwards compatibility.. return uriFactory; } /** * Set the message body converters to use. *

These converters are used to convert from and to HTTP requests and responses. */ public void setMessageConverters(List> messageConverters) { validateConverters(messageConverters); // Take getMessageConverters() List as-is when passed in here if (this.messageConverters != messageConverters) { this.messageConverters.clear(); this.messageConverters.addAll(messageConverters); } updateErrorHandlerConverters(); } private void validateConverters(List> messageConverters) { Assert.notEmpty(messageConverters, "At least one HttpMessageConverter is required"); Assert.noNullElements(messageConverters, "The HttpMessageConverter list must not contain null elements"); } /** * Return the list of message body converters. *

The returned {@link List} is active and may get appended to. */ public List> getMessageConverters() { return this.messageConverters; } /** * Set the error handler. *

By default, RestTemplate uses a {@link DefaultResponseErrorHandler}. */ public void setErrorHandler(ResponseErrorHandler errorHandler) { Assert.notNull(errorHandler, "ResponseErrorHandler must not be null"); this.errorHandler = errorHandler; updateErrorHandlerConverters(); } /** * Return the error handler. */ public ResponseErrorHandler getErrorHandler() { return this.errorHandler; } /** * Configure default URI variable values. This is a shortcut for: *

	 * DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
	 * handler.setDefaultUriVariables(...);
	 *
	 * RestTemplate restTemplate = new RestTemplate();
	 * restTemplate.setUriTemplateHandler(handler);
	 * 
* @param uriVars the default URI variable values * @since 4.3 */ public void setDefaultUriVariables(Map uriVars) { if (this.uriTemplateHandler instanceof DefaultUriBuilderFactory factory) { factory.setDefaultUriVariables(uriVars); } else { throw new IllegalArgumentException( "This property is not supported with the configured UriTemplateHandler."); } } /** * Configure a strategy for expanding URI templates. *

By default, {@link DefaultUriBuilderFactory} is used and for * backwards compatibility, the encoding mode is set to * {@link EncodingMode#URI_COMPONENT URI_COMPONENT}. As of 5.0.8, prefer * using {@link EncodingMode#TEMPLATE_AND_VALUES TEMPLATE_AND_VALUES}. * @param handler the URI template handler to use */ public void setUriTemplateHandler(UriTemplateHandler handler) { Assert.notNull(handler, "UriTemplateHandler must not be null"); this.uriTemplateHandler = handler; } /** * Return the configured URI template handler. */ public UriTemplateHandler getUriTemplateHandler() { return this.uriTemplateHandler; } /** * Configure an {@link ObservationRegistry} for collecting spans and metrics * for request execution. By default, {@link Observation observations} are no-ops. * @param observationRegistry the observation registry to use * @since 6.0 */ public void setObservationRegistry(ObservationRegistry observationRegistry) { Assert.notNull(observationRegistry, "observationRegistry must not be null"); this.observationRegistry = observationRegistry; } /** * Configure an {@link ObservationConvention} that sets the name of the * {@link Observation observation} as well as its {@link io.micrometer.common.KeyValues} * extracted from the {@link ClientRequestObservationContext}. * If none set, the {@link DefaultClientRequestObservationConvention default convention} will be used. * @param observationConvention the observation convention to use * @since 6.0 * @see #setObservationRegistry(ObservationRegistry) */ public void setObservationConvention(ClientRequestObservationConvention observationConvention) { Assert.notNull(observationConvention, "observationConvention must not be null"); this.observationConvention = observationConvention; } // GET @Override @Nullable public T getForObject(String url, Class responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public T getForObject(String url, Class responseType, Map uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public T getForObject(URI url, Class responseType) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor); } @Override public ResponseEntity getForEntity(String url, Class responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity getForEntity(String url, Class responseType, Map uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity getForEntity(URI url, Class responseType) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.GET, requestCallback, responseExtractor)); } // HEAD @Override public HttpHeaders headForHeaders(String url, Object... uriVariables) throws RestClientException { return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables)); } @Override public HttpHeaders headForHeaders(String url, Map uriVariables) throws RestClientException { return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor(), uriVariables)); } @Override public HttpHeaders headForHeaders(URI url) throws RestClientException { return nonNull(execute(url, HttpMethod.HEAD, null, headersExtractor())); } // POST @Override @Nullable public URI postForLocation(String url, @Nullable Object request, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables); return (headers != null ? headers.getLocation() : null); } @Override @Nullable public URI postForLocation(String url, @Nullable Object request, Map uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor(), uriVariables); return (headers != null ? headers.getLocation() : null); } @Override @Nullable public URI postForLocation(URI url, @Nullable Object request) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, headersExtractor()); return (headers != null ? headers.getLocation() : null); } @Override @Nullable public T postForObject(String url, @Nullable Object request, Class responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public T postForObject(String url, @Nullable Object request, Class responseType, Map uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public T postForObject(URI url, @Nullable Object request, Class responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters()); return execute(url, HttpMethod.POST, requestCallback, responseExtractor); } @Override public ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity postForEntity(String url, @Nullable Object request, Class responseType, Map uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity postForEntity(URI url, @Nullable Object request, Class responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, HttpMethod.POST, requestCallback, responseExtractor)); } // PUT @Override public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); } @Override public void put(String url, @Nullable Object request, Map uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); } @Override public void put(URI url, @Nullable Object request) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request); execute(url, HttpMethod.PUT, requestCallback, null); } // PATCH @Override @Nullable public T patchForObject(String url, @Nullable Object request, Class responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public T patchForObject(String url, @Nullable Object request, Class responseType, Map uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor, uriVariables); } @Override @Nullable public T patchForObject(URI url, @Nullable Object request, Class responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(request, responseType); HttpMessageConverterExtractor responseExtractor = new HttpMessageConverterExtractor<>(responseType, getMessageConverters()); return execute(url, HttpMethod.PATCH, requestCallback, responseExtractor); } // DELETE @Override public void delete(String url, Object... uriVariables) throws RestClientException { execute(url, HttpMethod.DELETE, null, null, uriVariables); } @Override public void delete(String url, Map uriVariables) throws RestClientException { execute(url, HttpMethod.DELETE, null, null, uriVariables); } @Override public void delete(URI url) throws RestClientException { execute(url, HttpMethod.DELETE, null, null); } // OPTIONS @Override public Set optionsForAllow(String url, Object... uriVariables) throws RestClientException { ResponseExtractor headersExtractor = headersExtractor(); HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables); return (headers != null ? headers.getAllow() : Collections.emptySet()); } @Override public Set optionsForAllow(String url, Map uriVariables) throws RestClientException { ResponseExtractor headersExtractor = headersExtractor(); HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, uriVariables); return (headers != null ? headers.getAllow() : Collections.emptySet()); } @Override public Set optionsForAllow(URI url) throws RestClientException { ResponseExtractor headersExtractor = headersExtractor(); HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor); return (headers != null ? headers.getAllow() : Collections.emptySet()); } // exchange @Override public ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity, Class responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity, Class responseType, Map uriVariables) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity exchange(URI url, HttpMethod method, @Nullable HttpEntity requestEntity, Class responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(execute(url, method, requestCallback, responseExtractor)); } @Override public ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity, ParameterizedTypeReference responseType, Object... uriVariables) throws RestClientException { Type type = responseType.getType(); RequestCallback requestCallback = httpEntityCallback(requestEntity, type); ResponseExtractor> responseExtractor = responseEntityExtractor(type); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity exchange(String url, HttpMethod method, @Nullable HttpEntity requestEntity, ParameterizedTypeReference responseType, Map uriVariables) throws RestClientException { Type type = responseType.getType(); RequestCallback requestCallback = httpEntityCallback(requestEntity, type); ResponseExtractor> responseExtractor = responseEntityExtractor(type); return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables)); } @Override public ResponseEntity exchange(URI url, HttpMethod method, @Nullable HttpEntity requestEntity, ParameterizedTypeReference responseType) throws RestClientException { Type type = responseType.getType(); RequestCallback requestCallback = httpEntityCallback(requestEntity, type); ResponseExtractor> responseExtractor = responseEntityExtractor(type); return nonNull(execute(url, method, requestCallback, responseExtractor)); } @Override public ResponseEntity exchange(RequestEntity entity, Class responseType) throws RestClientException { RequestCallback requestCallback = httpEntityCallback(entity, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); return nonNull(doExecute(resolveUrl(entity), resolveUriTemplate(entity), entity.getMethod(), requestCallback, responseExtractor)); } @Override public ResponseEntity exchange(RequestEntity entity, ParameterizedTypeReference responseType) throws RestClientException { Type type = responseType.getType(); RequestCallback requestCallback = httpEntityCallback(entity, type); ResponseExtractor> responseExtractor = responseEntityExtractor(type); return nonNull(doExecute(resolveUrl(entity), resolveUriTemplate(entity), entity.getMethod(), requestCallback, responseExtractor)); } private URI resolveUrl(RequestEntity entity) { if (entity instanceof RequestEntity.UriTemplateRequestEntity ext) { if (ext.getVars() != null) { return this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVars()); } else if (ext.getVarsMap() != null) { return this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVarsMap()); } else { throw new IllegalStateException("No variables specified for URI template: " + ext.getUriTemplate()); } } else { return entity.getUrl(); } } @Nullable private String resolveUriTemplate(RequestEntity entity) { if (entity instanceof RequestEntity.UriTemplateRequestEntity templated) { return templated.getUriTemplate(); } else { return null; } } // General execution /** * {@inheritDoc} *

To provide a {@code RequestCallback} or {@code ResponseExtractor} only, * but not both, consider using: *

    *
  • {@link #acceptHeaderRequestCallback(Class)} *
  • {@link #httpEntityCallback(Object)} *
  • {@link #httpEntityCallback(Object, Type)} *
  • {@link #responseEntityExtractor(Type)} *
*/ @Override @Nullable public T execute(String uriTemplate, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor, Object... uriVariables) throws RestClientException { URI url = getUriTemplateHandler().expand(uriTemplate, uriVariables); return doExecute(url, uriTemplate, method, requestCallback, responseExtractor); } /** * {@inheritDoc} *

To provide a {@code RequestCallback} or {@code ResponseExtractor} only, * but not both, consider using: *

    *
  • {@link #acceptHeaderRequestCallback(Class)} *
  • {@link #httpEntityCallback(Object)} *
  • {@link #httpEntityCallback(Object, Type)} *
  • {@link #responseEntityExtractor(Type)} *
*/ @Override @Nullable public T execute(String uriTemplate, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor, Map uriVariables) throws RestClientException { URI url = getUriTemplateHandler().expand(uriTemplate, uriVariables); return doExecute(url, uriTemplate, method, requestCallback, responseExtractor); } /** * {@inheritDoc} *

To provide a {@code RequestCallback} or {@code ResponseExtractor} only, * but not both, consider using: *

    *
  • {@link #acceptHeaderRequestCallback(Class)} *
  • {@link #httpEntityCallback(Object)} *
  • {@link #httpEntityCallback(Object, Type)} *
  • {@link #responseEntityExtractor(Type)} *
*/ @Override @Nullable public T execute(URI url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor) throws RestClientException { return doExecute(url, null, method, requestCallback, responseExtractor); } /** * Execute the given method on the provided URI. *

The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} * @deprecated in favor of {@link #doExecute(URI, String, HttpMethod, RequestCallback, ResponseExtractor)} */ @Nullable @Deprecated protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor) throws RestClientException { return doExecute(url, null, method, requestCallback, responseExtractor); } /** * Execute the given method on the provided URI. *

The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param uriTemplate the URI template that was used for creating the expanded URL * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} * @since 6.0 */ @Nullable @SuppressWarnings("try") protected T doExecute(URI url, @Nullable String uriTemplate, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor) throws RestClientException { Assert.notNull(url, "url is required"); Assert.notNull(method, "HttpMethod is required"); ClientHttpRequest request; try { request = createRequest(url, method); } catch (IOException ex) { ResourceAccessException exception = createResourceAccessException(url, method, ex); throw exception; } ClientRequestObservationContext observationContext = new ClientRequestObservationContext(request); observationContext.setUriTemplate(uriTemplate); Observation observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).start(); ClientHttpResponse response = null; try { if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); observationContext.setResponse(response); handleResponse(url, method, response); return (responseExtractor != null ? responseExtractor.extractData(response) : null); } catch (IOException ex) { ResourceAccessException exception = createResourceAccessException(url, method, ex); observation.error(exception); throw exception; } catch (RestClientException exc) { observation.error(exc); throw exc; } finally { if (response != null) { response.close(); } observation.stop(); } } private static ResourceAccessException createResourceAccessException(URI url, HttpMethod method, IOException ex) { String resource = url.toString(); resource = (url.getRawQuery() != null ? resource.substring(0, resource.indexOf('?')) : resource); return new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } /** * Handle the given response, performing appropriate logging and * invoking the {@link ResponseErrorHandler} if necessary. *

Can be overridden in subclasses. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param response the resulting {@link ClientHttpResponse} * @throws IOException if propagated from {@link ResponseErrorHandler} * @since 4.1.6 * @see #setErrorHandler */ protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { ResponseErrorHandler errorHandler = getErrorHandler(); boolean hasError = errorHandler.hasError(response); if (logger.isDebugEnabled()) { try { HttpStatusCode statusCode = response.getStatusCode(); logger.debug("Response " + statusCode); } catch (IOException ex) { // ignore } } if (hasError) { errorHandler.handleError(url, method, response); } } /** * Return a {@code RequestCallback} that sets the request {@code Accept} * header based on the given response type, cross-checked against the * configured message converters. */ public RequestCallback acceptHeaderRequestCallback(Class responseType) { return new AcceptHeaderRequestCallback(responseType); } /** * Return a {@code RequestCallback} implementation that writes the given * object to the request stream. */ public RequestCallback httpEntityCallback(@Nullable Object requestBody) { return new HttpEntityRequestCallback(requestBody); } /** * Return a {@code RequestCallback} implementation that: *

    *
  1. Sets the request {@code Accept} header based on the given response * type, cross-checked against the configured message converters. *
  2. Writes the given object to the request stream. *
*/ public RequestCallback httpEntityCallback(@Nullable Object requestBody, Type responseType) { return new HttpEntityRequestCallback(requestBody, responseType); } /** * Return a {@code ResponseExtractor} that prepares a {@link ResponseEntity}. */ public ResponseExtractor> responseEntityExtractor(Type responseType) { return new ResponseEntityResponseExtractor<>(responseType); } /** * Return a response extractor for {@link HttpHeaders}. */ protected ResponseExtractor headersExtractor() { return this.headersExtractor; } private static T nonNull(@Nullable T result) { Assert.state(result != null, "No result"); return result; } /** * Request callback implementation that prepares the request's accept headers. */ private class AcceptHeaderRequestCallback implements RequestCallback { @Nullable private final Type responseType; public AcceptHeaderRequestCallback(@Nullable Type responseType) { this.responseType = responseType; } @Override public void doWithRequest(ClientHttpRequest request) throws IOException { if (this.responseType != null) { List allSupportedMediaTypes = getMessageConverters().stream() .filter(converter -> canReadResponse(this.responseType, converter)) .flatMap((HttpMessageConverter converter) -> getSupportedMediaTypes(this.responseType, converter)) .distinct() .collect(Collectors.toList()); MimeTypeUtils.sortBySpecificity(allSupportedMediaTypes); if (logger.isDebugEnabled()) { logger.debug("Accept=" + allSupportedMediaTypes); } request.getHeaders().setAccept(allSupportedMediaTypes); } } private boolean canReadResponse(Type responseType, HttpMessageConverter converter) { Class responseClass = (responseType instanceof Class clazz ? clazz : null); if (responseClass != null) { return converter.canRead(responseClass, null); } else if (converter instanceof GenericHttpMessageConverter genericConverter) { return genericConverter.canRead(responseType, null, null); } return false; } private Stream getSupportedMediaTypes(Type type, HttpMessageConverter converter) { Type rawType = (type instanceof ParameterizedType parameterizedType ? parameterizedType.getRawType() : type); Class clazz = (rawType instanceof Class ? (Class) rawType : null); return (clazz != null ? converter.getSupportedMediaTypes(clazz) : converter.getSupportedMediaTypes()) .stream() .map(mediaType -> { if (mediaType.getCharset() != null) { return new MediaType(mediaType.getType(), mediaType.getSubtype()); } return mediaType; }); } } /** * Request callback implementation that writes the given object to the request stream. */ private class HttpEntityRequestCallback extends AcceptHeaderRequestCallback { private final HttpEntity requestEntity; public HttpEntityRequestCallback(@Nullable Object requestBody) { this(requestBody, null); } public HttpEntityRequestCallback(@Nullable Object requestBody, @Nullable Type responseType) { super(responseType); if (requestBody instanceof HttpEntity httpEntity) { this.requestEntity = httpEntity; } else if (requestBody != null) { this.requestEntity = new HttpEntity<>(requestBody); } else { this.requestEntity = HttpEntity.EMPTY; } } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { super.doWithRequest(httpRequest); Object requestBody = this.requestEntity.getBody(); if (requestBody == null) { HttpHeaders httpHeaders = httpRequest.getHeaders(); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); if (!requestHeaders.isEmpty()) { requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); } if (httpHeaders.getContentLength() < 0) { httpHeaders.setContentLength(0L); } } else { Class requestBodyClass = requestBody.getClass(); // The following pattern variable cannot be named "requestEntity" due to lacking // support in Checkstyle: https://github.com/checkstyle/checkstyle/issues/10969 Type requestBodyType = (this.requestEntity instanceof RequestEntity _requestEntity ? _requestEntity.getType() : requestBodyClass); HttpHeaders httpHeaders = httpRequest.getHeaders(); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); MediaType requestContentType = requestHeaders.getContentType(); for (HttpMessageConverter messageConverter : getMessageConverters()) { if (messageConverter instanceof GenericHttpMessageConverter genericConverter) { if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); } logBody(requestBody, requestContentType, genericConverter); genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest); return; } } else if (messageConverter.canWrite(requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values))); } logBody(requestBody, requestContentType, messageConverter); ((HttpMessageConverter) messageConverter).write( requestBody, requestContentType, httpRequest); return; } } String message = "No HttpMessageConverter for " + requestBodyClass.getName(); if (requestContentType != null) { message += " and content type \"" + requestContentType + "\""; } throw new RestClientException(message); } } private void logBody(Object body, @Nullable MediaType mediaType, HttpMessageConverter converter) { if (logger.isDebugEnabled()) { if (mediaType != null) { logger.debug("Writing [" + body + "] as \"" + mediaType + "\""); } else { logger.debug("Writing [" + body + "] with " + converter.getClass().getName()); } } } } /** * Response extractor for {@link HttpEntity}. */ private class ResponseEntityResponseExtractor implements ResponseExtractor> { @Nullable private final HttpMessageConverterExtractor delegate; public ResponseEntityResponseExtractor(@Nullable Type responseType) { if (responseType != null && Void.class != responseType) { this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger); } else { this.delegate = null; } } @Override public ResponseEntity extractData(ClientHttpResponse response) throws IOException { if (this.delegate != null) { T body = this.delegate.extractData(response); return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(body); } else { return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).build(); } } } /** * Response extractor that extracts the response {@link HttpHeaders}. */ private static class HeadersExtractor implements ResponseExtractor { @Override public HttpHeaders extractData(ClientHttpResponse response) { return response.getHeaders(); } } }