Add cookie support to RestClient

See gh-33697
This commit is contained in:
Nicklas Wiegandt
2024-10-13 19:07:00 +02:00
committed by rstoyanchev
parent d8c153a9d1
commit a0af708c03
6 changed files with 339 additions and 0 deletions

View File

@@ -40,6 +40,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
@@ -64,6 +65,8 @@ 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;
@@ -103,6 +106,9 @@ final class DefaultRestClient implements RestClient {
@Nullable
private final HttpHeaders defaultHeaders;
@Nullable
private final MultiValueMap<String, String> defaultCookies;
@Nullable
private final Consumer<RequestHeadersSpec<?>> defaultRequest;
@@ -123,6 +129,7 @@ final class DefaultRestClient implements RestClient {
@Nullable List<ClientHttpRequestInitializer> initializers,
UriBuilderFactory uriBuilderFactory,
@Nullable HttpHeaders defaultHeaders,
@Nullable MultiValueMap<String, String> defaultCookies,
@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest,
@Nullable List<StatusHandler> statusHandlers,
List<HttpMessageConverter<?>> messageConverters,
@@ -135,6 +142,7 @@ final class DefaultRestClient implements RestClient {
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;
@@ -293,6 +301,8 @@ final class DefaultRestClient implements RestClient {
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
private static final String COOKIE_DELIMITER = "; ";
private final HttpMethod httpMethod;
@Nullable
@@ -301,6 +311,9 @@ final class DefaultRestClient implements RestClient {
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private InternalBody body;
@@ -356,6 +369,13 @@ final class DefaultRestClient implements RestClient {
return this.headers;
}
private MultiValueMap<String, String> 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) {
@@ -382,6 +402,18 @@ final class DefaultRestClient implements RestClient {
return this;
}
@Override
public DefaultRequestBodyUriSpec cookie(String name, String value) {
getCookies().add(name, value);
return this;
}
@Override
public DefaultRequestBodyUriSpec cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
cookiesConsumer.accept(getCookies());
return this;
}
@Override
public DefaultRequestBodyUriSpec contentType(MediaType contentType) {
getHeaders().setContentType(contentType);
@@ -525,6 +557,12 @@ final class DefaultRestClient implements RestClient {
try {
uri = initUri();
HttpHeaders headers = initHeaders();
MultiValueMap<String, String> cookies = initCookies();
if (!CollectionUtils.isEmpty(cookies)) {
headers.put(HttpHeaders.COOKIE, List.of(cookiesToHeaderValue(cookies)));
}
ClientHttpRequest clientRequest = createRequest(uri);
clientRequest.getHeaders().addAll(headers);
Map<String, Object> attributes = getAttributes();
@@ -599,6 +637,28 @@ final class DefaultRestClient implements RestClient {
}
}
private MultiValueMap<String, String> initCookies() {
MultiValueMap<String, String> mergedCookies = new LinkedMultiValueMap<>();
if(!CollectionUtils.isEmpty(defaultCookies)) {
mergedCookies.putAll(defaultCookies);
}
if(!CollectionUtils.isEmpty(this.cookies)) {
mergedCookies.putAll(this.cookies);
}
return mergedCookies;
}
private String cookiesToHeaderValue(MultiValueMap<String, String> cookies) {
List<String> flatCookies = new ArrayList<>();
cookies.forEach((name, cookieValues) -> cookieValues.forEach(value ->
flatCookies.add(new HttpCookie(name, value).toString())
));
return String.join(COOKIE_DELIMITER, flatCookies);
}
private ClientHttpRequest createRequest(URI uri) throws IOException {
ClientHttpRequestFactory factory;
if (DefaultRestClient.this.interceptors != null) {

View File

@@ -56,6 +56,8 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
@@ -127,6 +129,9 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
@Nullable
private HttpHeaders defaultHeaders;
@Nullable
private MultiValueMap<String, String> defaultCookies;
@Nullable
private Consumer<RestClient.RequestHeadersSpec<?>> defaultRequest;
@@ -169,6 +174,8 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
else {
this.defaultHeaders = null;
}
this.defaultCookies = (other.defaultCookies != null ?
new LinkedMultiValueMap<>(other.defaultCookies) : null);
this.defaultRequest = other.defaultRequest;
this.statusHandlers = (other.statusHandlers != null ? new ArrayList<>(other.statusHandlers) : null);
@@ -289,6 +296,25 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
return this.defaultHeaders;
}
@Override
public RestClient.Builder defaultCookie(String cookie, String... values) {
initCookies().addAll(cookie, Arrays.asList(values));
return this;
}
@Override
public RestClient.Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
cookiesConsumer.accept(initCookies());
return this;
}
private MultiValueMap<String, String> initCookies() {
if (this.defaultCookies == null) {
this.defaultCookies = new LinkedMultiValueMap<>(3);
}
return this.defaultCookies;
}
@Override
public RestClient.Builder defaultRequest(Consumer<RestClient.RequestHeadersSpec<?>> defaultRequest) {
this.defaultRequest = this.defaultRequest != null ?
@@ -443,11 +469,13 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
ClientHttpRequestFactory requestFactory = initRequestFactory();
UriBuilderFactory uriBuilderFactory = initUriBuilderFactory();
HttpHeaders defaultHeaders = copyDefaultHeaders();
MultiValueMap<String, String> defaultCookies = copyDefaultCookies();
List<HttpMessageConverter<?>> messageConverters = (this.messageConverters != null ?
this.messageConverters : initMessageConverters());
return new DefaultRestClient(requestFactory,
this.interceptors, this.initializers, uriBuilderFactory,
defaultHeaders,
defaultCookies,
this.defaultRequest,
this.statusHandlers,
messageConverters,
@@ -501,4 +529,16 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
}
}
@Nullable
private MultiValueMap<String, String> copyDefaultCookies() {
if (this.defaultCookies != null) {
MultiValueMap<String, String> copy = new LinkedMultiValueMap<>(this.defaultCookies.size());
this.defaultCookies.forEach((key, values) -> copy.put(key, new ArrayList<>(values)));
return CollectionUtils.unmodifiableMultiValueMap(copy);
}
else {
return null;
}
}
}

View File

@@ -45,6 +45,7 @@ import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriBuilderFactory;
@@ -312,6 +313,23 @@ public interface RestClient {
*/
Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);
/**
* Global option to specify a cookie to be added to every request,
* if the request does not already contain such a cookie.
* @param cookie the cookie name
* @param values the cookie values
* @since 6.2
*/
Builder defaultCookie(String cookie, String... values);
/**
* Provides access to every {@link #defaultCookie(String, String...)}
* declared so far with the possibility to add, replace, or remove.
* @param cookiesConsumer a function that consumes the cookies map
* @since 6.2
*/
Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Provide a consumer to customize every request being built.
* @param defaultRequest the consumer to use for modifying requests
@@ -519,6 +537,24 @@ public interface RestClient {
*/
S acceptCharset(Charset... acceptableCharsets);
/**
* Add a cookie with the given name and value.
* @param name the cookie name
* @param value the cookie value
* @return this builder
* @since 6.2
*/
S cookie(String name, String value);
/**
* Provides access to every cookie declared so far with the possibility
* to add, replace, or remove values.
* @param cookiesConsumer the consumer to provide access to
* @return this builder
* @since 6.2
*/
S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Set the value of the {@code If-Modified-Since} header.
* @param ifModifiedSince the new value of the header