Skip to content

Commit a0af708

Browse files
Nicklas2751rstoyanchev
authored andcommitted
Add cookie support to RestClient
See gh-33697
1 parent d8c153a commit a0af708

File tree

6 files changed

+339
-0
lines changed

6 files changed

+339
-0
lines changed

framework-docs/modules/ROOT/pages/integration/rest-clients.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ RestClient customClient = RestClient.builder()
3838
.baseUrl("https://example.com")
3939
.defaultUriVariables(Map.of("variable", "foo"))
4040
.defaultHeader("My-Header", "Foo")
41+
.defaultCookie("My-Cookie", "Bar")
4142
.requestInterceptor(myCustomInterceptor)
4243
.requestInitializer(myCustomInitializer)
4344
.build();
@@ -55,6 +56,7 @@ val customClient = RestClient.builder()
5556
.baseUrl("https://example.com")
5657
.defaultUriVariables(mapOf("variable" to "foo"))
5758
.defaultHeader("My-Header", "Foo")
59+
.defaultCookie("My-Cookie", "Bar")
5860
.requestInterceptor(myCustomInterceptor)
5961
.requestInitializer(myCustomInitializer)
6062
.build()

spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java

+60
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
import org.springframework.core.ParameterizedTypeReference;
4242
import org.springframework.core.ResolvableType;
43+
import org.springframework.http.HttpCookie;
4344
import org.springframework.http.HttpHeaders;
4445
import org.springframework.http.HttpMethod;
4546
import org.springframework.http.HttpRequest;
@@ -64,6 +65,8 @@
6465
import org.springframework.lang.Nullable;
6566
import org.springframework.util.Assert;
6667
import org.springframework.util.CollectionUtils;
68+
import org.springframework.util.LinkedMultiValueMap;
69+
import org.springframework.util.MultiValueMap;
6770
import org.springframework.web.util.UriBuilder;
6871
import org.springframework.web.util.UriBuilderFactory;
6972

@@ -103,6 +106,9 @@ final class DefaultRestClient implements RestClient {
103106
@Nullable
104107
private final HttpHeaders defaultHeaders;
105108

109+
@Nullable
110+
private final MultiValueMap<String, String> defaultCookies;
111+
106112
@Nullable
107113
private final Consumer<RequestHeadersSpec<?>> defaultRequest;
108114

@@ -123,6 +129,7 @@ final class DefaultRestClient implements RestClient {
123129
@Nullable List<ClientHttpRequestInitializer> initializers,
124130
UriBuilderFactory uriBuilderFactory,
125131
@Nullable HttpHeaders defaultHeaders,
132+
@Nullable MultiValueMap<String, String> defaultCookies,
126133
@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest,
127134
@Nullable List<StatusHandler> statusHandlers,
128135
List<HttpMessageConverter<?>> messageConverters,
@@ -135,6 +142,7 @@ final class DefaultRestClient implements RestClient {
135142
this.interceptors = interceptors;
136143
this.uriBuilderFactory = uriBuilderFactory;
137144
this.defaultHeaders = defaultHeaders;
145+
this.defaultCookies = defaultCookies;
138146
this.defaultRequest = defaultRequest;
139147
this.defaultStatusHandlers = (statusHandlers != null ? new ArrayList<>(statusHandlers) : new ArrayList<>());
140148
this.messageConverters = messageConverters;
@@ -293,6 +301,8 @@ private static <T> Class<T> bodyClass(Type type) {
293301

294302
private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
295303

304+
private static final String COOKIE_DELIMITER = "; ";
305+
296306
private final HttpMethod httpMethod;
297307

298308
@Nullable
@@ -301,6 +311,9 @@ private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec {
301311
@Nullable
302312
private HttpHeaders headers;
303313

314+
@Nullable
315+
private MultiValueMap<String, String> cookies;
316+
304317
@Nullable
305318
private InternalBody body;
306319

@@ -356,6 +369,13 @@ private HttpHeaders getHeaders() {
356369
return this.headers;
357370
}
358371

372+
private MultiValueMap<String, String> getCookies() {
373+
if (this.cookies == null) {
374+
this.cookies = new LinkedMultiValueMap<>(3);
375+
}
376+
return this.cookies;
377+
}
378+
359379
@Override
360380
public DefaultRequestBodyUriSpec header(String headerName, String... headerValues) {
361381
for (String headerValue : headerValues) {
@@ -382,6 +402,18 @@ public DefaultRequestBodyUriSpec acceptCharset(Charset... acceptableCharsets) {
382402
return this;
383403
}
384404

405+
@Override
406+
public DefaultRequestBodyUriSpec cookie(String name, String value) {
407+
getCookies().add(name, value);
408+
return this;
409+
}
410+
411+
@Override
412+
public DefaultRequestBodyUriSpec cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
413+
cookiesConsumer.accept(getCookies());
414+
return this;
415+
}
416+
385417
@Override
386418
public DefaultRequestBodyUriSpec contentType(MediaType contentType) {
387419
getHeaders().setContentType(contentType);
@@ -525,6 +557,12 @@ private <T> T exchangeInternal(ExchangeFunction<T> exchangeFunction, boolean clo
525557
try {
526558
uri = initUri();
527559
HttpHeaders headers = initHeaders();
560+
561+
MultiValueMap<String, String> cookies = initCookies();
562+
if (!CollectionUtils.isEmpty(cookies)) {
563+
headers.put(HttpHeaders.COOKIE, List.of(cookiesToHeaderValue(cookies)));
564+
}
565+
528566
ClientHttpRequest clientRequest = createRequest(uri);
529567
clientRequest.getHeaders().addAll(headers);
530568
Map<String, Object> attributes = getAttributes();
@@ -599,6 +637,28 @@ else if (CollectionUtils.isEmpty(defaultHeaders)) {
599637
}
600638
}
601639

640+
private MultiValueMap<String, String> initCookies() {
641+
MultiValueMap<String, String> mergedCookies = new LinkedMultiValueMap<>();
642+
643+
if(!CollectionUtils.isEmpty(defaultCookies)) {
644+
mergedCookies.putAll(defaultCookies);
645+
}
646+
647+
if(!CollectionUtils.isEmpty(this.cookies)) {
648+
mergedCookies.putAll(this.cookies);
649+
}
650+
651+
return mergedCookies;
652+
}
653+
654+
private String cookiesToHeaderValue(MultiValueMap<String, String> cookies) {
655+
List<String> flatCookies = new ArrayList<>();
656+
cookies.forEach((name, cookieValues) -> cookieValues.forEach(value ->
657+
flatCookies.add(new HttpCookie(name, value).toString())
658+
));
659+
return String.join(COOKIE_DELIMITER, flatCookies);
660+
}
661+
602662
private ClientHttpRequest createRequest(URI uri) throws IOException {
603663
ClientHttpRequestFactory factory;
604664
if (DefaultRestClient.this.interceptors != null) {

spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java

+40
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import org.springframework.util.Assert;
5757
import org.springframework.util.ClassUtils;
5858
import org.springframework.util.CollectionUtils;
59+
import org.springframework.util.LinkedMultiValueMap;
60+
import org.springframework.util.MultiValueMap;
5961
import org.springframework.web.util.DefaultUriBuilderFactory;
6062
import org.springframework.web.util.UriBuilderFactory;
6163
import org.springframework.web.util.UriTemplateHandler;
@@ -127,6 +129,9 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
127129
@Nullable
128130
private HttpHeaders defaultHeaders;
129131

132+
@Nullable
133+
private MultiValueMap<String, String> defaultCookies;
134+
130135
@Nullable
131136
private Consumer<RestClient.RequestHeadersSpec<?>> defaultRequest;
132137

@@ -169,6 +174,8 @@ public DefaultRestClientBuilder(DefaultRestClientBuilder other) {
169174
else {
170175
this.defaultHeaders = null;
171176
}
177+
this.defaultCookies = (other.defaultCookies != null ?
178+
new LinkedMultiValueMap<>(other.defaultCookies) : null);
172179
this.defaultRequest = other.defaultRequest;
173180
this.statusHandlers = (other.statusHandlers != null ? new ArrayList<>(other.statusHandlers) : null);
174181

@@ -289,6 +296,25 @@ private HttpHeaders initHeaders() {
289296
return this.defaultHeaders;
290297
}
291298

299+
@Override
300+
public RestClient.Builder defaultCookie(String cookie, String... values) {
301+
initCookies().addAll(cookie, Arrays.asList(values));
302+
return this;
303+
}
304+
305+
@Override
306+
public RestClient.Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
307+
cookiesConsumer.accept(initCookies());
308+
return this;
309+
}
310+
311+
private MultiValueMap<String, String> initCookies() {
312+
if (this.defaultCookies == null) {
313+
this.defaultCookies = new LinkedMultiValueMap<>(3);
314+
}
315+
return this.defaultCookies;
316+
}
317+
292318
@Override
293319
public RestClient.Builder defaultRequest(Consumer<RestClient.RequestHeadersSpec<?>> defaultRequest) {
294320
this.defaultRequest = this.defaultRequest != null ?
@@ -443,11 +469,13 @@ public RestClient build() {
443469
ClientHttpRequestFactory requestFactory = initRequestFactory();
444470
UriBuilderFactory uriBuilderFactory = initUriBuilderFactory();
445471
HttpHeaders defaultHeaders = copyDefaultHeaders();
472+
MultiValueMap<String, String> defaultCookies = copyDefaultCookies();
446473
List<HttpMessageConverter<?>> messageConverters = (this.messageConverters != null ?
447474
this.messageConverters : initMessageConverters());
448475
return new DefaultRestClient(requestFactory,
449476
this.interceptors, this.initializers, uriBuilderFactory,
450477
defaultHeaders,
478+
defaultCookies,
451479
this.defaultRequest,
452480
this.statusHandlers,
453481
messageConverters,
@@ -501,4 +529,16 @@ private HttpHeaders copyDefaultHeaders() {
501529
}
502530
}
503531

532+
@Nullable
533+
private MultiValueMap<String, String> copyDefaultCookies() {
534+
if (this.defaultCookies != null) {
535+
MultiValueMap<String, String> copy = new LinkedMultiValueMap<>(this.defaultCookies.size());
536+
this.defaultCookies.forEach((key, values) -> copy.put(key, new ArrayList<>(values)));
537+
return CollectionUtils.unmodifiableMultiValueMap(copy);
538+
}
539+
else {
540+
return null;
541+
}
542+
}
543+
504544
}

spring-web/src/main/java/org/springframework/web/client/RestClient.java

+36
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.http.client.observation.ClientRequestObservationConvention;
4646
import org.springframework.http.converter.HttpMessageConverter;
4747
import org.springframework.lang.Nullable;
48+
import org.springframework.util.MultiValueMap;
4849
import org.springframework.web.util.DefaultUriBuilderFactory;
4950
import org.springframework.web.util.UriBuilder;
5051
import org.springframework.web.util.UriBuilderFactory;
@@ -312,6 +313,23 @@ interface Builder {
312313
*/
313314
Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);
314315

316+
/**
317+
* Global option to specify a cookie to be added to every request,
318+
* if the request does not already contain such a cookie.
319+
* @param cookie the cookie name
320+
* @param values the cookie values
321+
* @since 6.2
322+
*/
323+
Builder defaultCookie(String cookie, String... values);
324+
325+
/**
326+
* Provides access to every {@link #defaultCookie(String, String...)}
327+
* declared so far with the possibility to add, replace, or remove.
328+
* @param cookiesConsumer a function that consumes the cookies map
329+
* @since 6.2
330+
*/
331+
Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
332+
315333
/**
316334
* Provide a consumer to customize every request being built.
317335
* @param defaultRequest the consumer to use for modifying requests
@@ -519,6 +537,24 @@ interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
519537
*/
520538
S acceptCharset(Charset... acceptableCharsets);
521539

540+
/**
541+
* Add a cookie with the given name and value.
542+
* @param name the cookie name
543+
* @param value the cookie value
544+
* @return this builder
545+
* @since 6.2
546+
*/
547+
S cookie(String name, String value);
548+
549+
/**
550+
* Provides access to every cookie declared so far with the possibility
551+
* to add, replace, or remove values.
552+
* @param cookiesConsumer the consumer to provide access to
553+
* @return this builder
554+
* @since 6.2
555+
*/
556+
S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
557+
522558
/**
523559
* Set the value of the {@code If-Modified-Since} header.
524560
* @param ifModifiedSince the new value of the header

0 commit comments

Comments
 (0)