org.springframework.web.client.DefaultRestClient Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2002-2024 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.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInitializer;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.InterceptingClientHttpRequestFactory;
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.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
/**
* The default implementation of {@link RestClient},
* as created by the static factory methods.
*
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @since 6.1
* @see RestClient#create()
* @see RestClient#create(String)
* @see RestClient#create(RestTemplate)
*/
final class DefaultRestClient implements RestClient {
private static final Log logger = LogFactory.getLog(DefaultRestClient.class);
private static final ClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention();
private static final String URI_TEMPLATE_ATTRIBUTE = RestClient.class.getName() + ".uriTemplate";
private final ClientHttpRequestFactory clientRequestFactory;
@Nullable
private volatile ClientHttpRequestFactory interceptingRequestFactory;
@Nullable
private final List initializers;
@Nullable
private final List interceptors;
private final UriBuilderFactory uriBuilderFactory;
@Nullable
private final HttpHeaders defaultHeaders;
@Nullable
private final MultiValueMap defaultCookies;
@Nullable
private final Consumer> defaultRequest;
private final List defaultStatusHandlers;
private final DefaultRestClientBuilder builder;
private final List> messageConverters;
private final ObservationRegistry observationRegistry;
@Nullable
private final ClientRequestObservationConvention observationConvention;
DefaultRestClient(ClientHttpRequestFactory clientRequestFactory,
@Nullable List interceptors,
@Nullable List initializers,
UriBuilderFactory uriBuilderFactory,
@Nullable HttpHeaders defaultHeaders,
@Nullable MultiValueMap defaultCookies,
@Nullable Consumer> defaultRequest,
@Nullable List statusHandlers,
List> messageConverters,
ObservationRegistry observationRegistry,
@Nullable ClientRequestObservationConvention observationConvention,
DefaultRestClientBuilder builder) {
this.clientRequestFactory = clientRequestFactory;
this.initializers = initializers;
this.interceptors = interceptors;
this.uriBuilderFactory = uriBuilderFactory;
this.defaultHeaders = defaultHeaders;
this.defaultCookies = defaultCookies;
this.defaultRequest = defaultRequest;
this.defaultStatusHandlers = (statusHandlers != null ? new ArrayList<>(statusHandlers) : new ArrayList<>());
this.messageConverters = messageConverters;
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
this.builder = builder;
}
@Override
public RequestHeadersUriSpec> get() {
return methodInternal(HttpMethod.GET);
}
@Override
public RequestHeadersUriSpec> head() {
return methodInternal(HttpMethod.HEAD);
}
@Override
public RequestBodyUriSpec post() {
return methodInternal(HttpMethod.POST);
}
@Override
public RequestBodyUriSpec put() {
return methodInternal(HttpMethod.PUT);
}
@Override
public RequestBodyUriSpec patch() {
return methodInternal(HttpMethod.PATCH);
}
@Override
public RequestHeadersUriSpec> delete() {
return methodInternal(HttpMethod.DELETE);
}
@Override
public RequestHeadersUriSpec> options() {
return methodInternal(HttpMethod.OPTIONS);
}
@Override
public RequestBodyUriSpec method(HttpMethod method) {
Assert.notNull(method, "HttpMethod must not be null");
return methodInternal(method);
}
private RequestBodyUriSpec methodInternal(HttpMethod httpMethod) {
DefaultRequestBodyUriSpec spec = new DefaultRequestBodyUriSpec(httpMethod);
if (this.defaultRequest != null) {
this.defaultRequest.accept(spec);
}
return spec;
}
@Override
public Builder mutate() {
return new DefaultRestClientBuilder(this.builder);
}
@Nullable
@SuppressWarnings({"rawtypes", "unchecked"})
private T readWithMessageConverters(
ClientHttpResponse clientResponse, Runnable callback, Type bodyType, Class bodyClass) {
MediaType contentType = getContentType(clientResponse);
try {
callback.run();
IntrospectingClientHttpResponse responseWrapper = new IntrospectingClientHttpResponse(clientResponse);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
for (HttpMessageConverter> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter genericMessageConverter) {
if (genericMessageConverter.canRead(bodyType, null, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [" + ResolvableType.forType(bodyType) + "]");
}
return (T) genericMessageConverter.read(bodyType, null, responseWrapper);
}
}
else if (messageConverter instanceof SmartHttpMessageConverter smartMessageConverter) {
ResolvableType resolvableType = ResolvableType.forType(bodyType);
if (smartMessageConverter.canRead(resolvableType, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [" + resolvableType + "]");
}
return (T) smartMessageConverter.read(resolvableType, responseWrapper, null);
}
}
else if (messageConverter.canRead(bodyClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading to [" + bodyClass.getName() + "] as \"" + contentType + "\"");
}
return (T) messageConverter.read((Class)bodyClass, responseWrapper);
}
}
throw new UnknownContentTypeException(bodyType, contentType,
responseWrapper.getStatusCode(), responseWrapper.getStatusText(),
responseWrapper.getHeaders(), RestClientUtils.getBody(responseWrapper));
}
catch (UncheckedIOException | IOException | HttpMessageNotReadableException exc) {
Throwable cause;
if (exc instanceof UncheckedIOException uncheckedIOException) {
cause = uncheckedIOException.getCause();
}
else {
cause = exc;
}
throw new RestClientException("Error while extracting response for type [" +
ResolvableType.forType(bodyType) + "] and content type [" + contentType + "]", cause);
}
}
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 Class bodyClass(Type type) {
if (type instanceof Class> clazz) {
return (Class) clazz;
}
if (type instanceof ParameterizedType parameterizedType &&
parameterizedType.getRawType() instanceof Class> rawType) {
return (Class) rawType;
}
return (Class) Object.class;
}
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
private final HttpMethod httpMethod;
@Nullable
private URI uri;
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap cookies;
@Nullable
private InternalBody body;
@Nullable
private Map attributes;
@Nullable
private Consumer httpRequestConsumer;
public DefaultRequestBodyUriSpec(HttpMethod httpMethod) {
this.httpMethod = httpMethod;
}
@Override
public RequestBodySpec uri(String uriTemplate, Object... uriVariables) {
attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate);
return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables));
}
@Override
public RequestBodySpec uri(String uriTemplate, Map uriVariables) {
attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate);
return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables));
}
@Override
public RequestBodySpec uri(String uriTemplate, Function uriFunction) {
attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate);
return uri(uriFunction.apply(DefaultRestClient.this.uriBuilderFactory.uriString(uriTemplate)));
}
@Override
public RequestBodySpec uri(Function uriFunction) {
return uri(uriFunction.apply(DefaultRestClient.this.uriBuilderFactory.builder()));
}
@Override
public RequestBodySpec uri(URI uri) {
if (uri.isAbsolute()) {
this.uri = uri;
}
else {
URI baseUri = DefaultRestClient.this.uriBuilderFactory.expand("");
this.uri = baseUri.resolve(uri);
}
return this;
}
private HttpHeaders getHeaders() {
if (this.headers == null) {
this.headers = new HttpHeaders();
}
return this.headers;
}
private MultiValueMap getCookies() {
if (this.cookies == null) {
this.cookies = new LinkedMultiValueMap<>(3);
}
return this.cookies;
}
@Override
public DefaultRequestBodyUriSpec header(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
getHeaders().add(headerName, headerValue);
}
return this;
}
@Override
public DefaultRequestBodyUriSpec headers(Consumer headersConsumer) {
headersConsumer.accept(getHeaders());
return this;
}
@Override
public DefaultRequestBodyUriSpec accept(MediaType... acceptableMediaTypes) {
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
return this;
}
@Override
public DefaultRequestBodyUriSpec acceptCharset(Charset... acceptableCharsets) {
getHeaders().setAcceptCharset(Arrays.asList(acceptableCharsets));
return this;
}
@Override
public DefaultRequestBodyUriSpec cookie(String name, String value) {
getCookies().add(name, value);
return this;
}
@Override
public DefaultRequestBodyUriSpec cookies(Consumer> cookiesConsumer) {
cookiesConsumer.accept(getCookies());
return this;
}
@Override
public DefaultRequestBodyUriSpec contentType(MediaType contentType) {
getHeaders().setContentType(contentType);
return this;
}
@Override
public DefaultRequestBodyUriSpec contentLength(long contentLength) {
getHeaders().setContentLength(contentLength);
return this;
}
@Override
public DefaultRequestBodyUriSpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
getHeaders().setIfModifiedSince(ifModifiedSince);
return this;
}
@Override
public DefaultRequestBodyUriSpec ifNoneMatch(String... ifNoneMatches) {
getHeaders().setIfNoneMatch(Arrays.asList(ifNoneMatches));
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
getAttributes().put(name, value);
return this;
}
@Override
public RequestBodySpec attributes(Consumer