Skip to content

Commit 81d1217

Browse files
committed
Support for default headers and cookies
Issue: SPR-15208
1 parent 3e8ac25 commit 81d1217

File tree

4 files changed

+290
-27
lines changed

4 files changed

+290
-27
lines changed

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.springframework.http.HttpMethod;
3333
import org.springframework.http.MediaType;
3434
import org.springframework.http.client.reactive.ClientHttpRequest;
35+
import org.springframework.util.CollectionUtils;
36+
import org.springframework.util.LinkedMultiValueMap;
3537
import org.springframework.util.MultiValueMap;
3638
import org.springframework.web.reactive.function.BodyInserter;
3739
import org.springframework.web.util.DefaultUriBuilderFactory;
@@ -50,10 +52,22 @@ class DefaultWebClient implements WebClient {
5052

5153
private final UriBuilderFactory uriBuilderFactory;
5254

55+
private final HttpHeaders defaultHeaders;
56+
57+
private final MultiValueMap<String, String> defaultCookies;
58+
59+
60+
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory factory,
61+
HttpHeaders defaultHeaders, MultiValueMap<String, String> defaultCookies) {
5362

54-
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory factory) {
5563
this.exchangeFunction = exchangeFunction;
5664
this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory());
65+
66+
this.defaultHeaders = defaultHeaders != null ?
67+
HttpHeaders.readOnlyHttpHeaders(defaultHeaders) : null;
68+
69+
this.defaultCookies = defaultCookies != null ?
70+
CollectionUtils.unmodifiableMultiValueMap(defaultCookies) : null;
5771
}
5872

5973

@@ -110,7 +124,8 @@ private UriSpec method(HttpMethod httpMethod) {
110124
@Override
111125
public WebClient filter(ExchangeFilterFunction filterFunction) {
112126
ExchangeFunction filteredExchangeFunction = this.exchangeFunction.filter(filterFunction);
113-
return new DefaultWebClient(filteredExchangeFunction, this.uriBuilderFactory);
127+
return new DefaultWebClient(filteredExchangeFunction,
128+
this.uriBuilderFactory, this.defaultHeaders, this.defaultCookies);
114129
}
115130

116131

@@ -123,11 +138,6 @@ private class DefaultUriSpec implements UriSpec {
123138
this.httpMethod = httpMethod;
124139
}
125140

126-
@Override
127-
public HeaderSpec uri(URI uri) {
128-
return new DefaultHeaderSpec(ClientRequest.method(this.httpMethod, uri));
129-
}
130-
131141
@Override
132142
public HeaderSpec uri(String uriTemplate, Object... uriVariables) {
133143
return uri(getUriBuilderFactory().expand(uriTemplate, uriVariables));
@@ -137,103 +147,173 @@ public HeaderSpec uri(String uriTemplate, Object... uriVariables) {
137147
public HeaderSpec uri(Function<UriBuilderFactory, URI> uriFunction) {
138148
return uri(uriFunction.apply(getUriBuilderFactory()));
139149
}
150+
151+
@Override
152+
public HeaderSpec uri(URI uri) {
153+
return new DefaultHeaderSpec(this.httpMethod, uri);
154+
}
140155
}
141156

142157
private class DefaultHeaderSpec implements HeaderSpec {
143158

144-
private final ClientRequest.Builder requestBuilder;
159+
private final HttpMethod httpMethod;
160+
161+
private final URI uri;
145162

146-
private final HttpHeaders headers = new HttpHeaders();
163+
private HttpHeaders headers;
164+
165+
private MultiValueMap<String, String> cookies;
166+
167+
168+
DefaultHeaderSpec(HttpMethod httpMethod, URI uri) {
169+
this.httpMethod = httpMethod;
170+
this.uri = uri;
171+
}
147172

148173

149-
DefaultHeaderSpec(ClientRequest.Builder requestBuilder) {
150-
this.requestBuilder = requestBuilder;
174+
private HttpHeaders getHeaders() {
175+
if (this.headers == null) {
176+
this.headers = new HttpHeaders();
177+
}
178+
return this.headers;
151179
}
152180

181+
private MultiValueMap<String, String> getCookies() {
182+
if (this.cookies == null) {
183+
this.cookies = new LinkedMultiValueMap<>(4);
184+
}
185+
return this.cookies;
186+
}
153187

154188
@Override
155189
public DefaultHeaderSpec header(String headerName, String... headerValues) {
156190
for (String headerValue : headerValues) {
157-
this.headers.add(headerName, headerValue);
191+
getHeaders().add(headerName, headerValue);
158192
}
159193
return this;
160194
}
161195

162196
@Override
163197
public DefaultHeaderSpec headers(HttpHeaders headers) {
164198
if (headers != null) {
165-
this.headers.putAll(headers);
199+
getHeaders().putAll(headers);
166200
}
167201
return this;
168202
}
169203

170204
@Override
171205
public DefaultHeaderSpec accept(MediaType... acceptableMediaTypes) {
172-
this.headers.setAccept(Arrays.asList(acceptableMediaTypes));
206+
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
173207
return this;
174208
}
175209

176210
@Override
177211
public DefaultHeaderSpec acceptCharset(Charset... acceptableCharsets) {
178-
this.headers.setAcceptCharset(Arrays.asList(acceptableCharsets));
212+
getHeaders().setAcceptCharset(Arrays.asList(acceptableCharsets));
179213
return this;
180214
}
181215

182216
@Override
183217
public DefaultHeaderSpec contentType(MediaType contentType) {
184-
this.headers.setContentType(contentType);
218+
getHeaders().setContentType(contentType);
185219
return this;
186220
}
187221

188222
@Override
189223
public DefaultHeaderSpec contentLength(long contentLength) {
190-
this.headers.setContentLength(contentLength);
224+
getHeaders().setContentLength(contentLength);
191225
return this;
192226
}
193227

194228
@Override
195229
public DefaultHeaderSpec cookie(String name, String value) {
196-
this.requestBuilder.cookie(name, value);
230+
getCookies().add(name, value);
197231
return this;
198232
}
199233

200234
@Override
201235
public DefaultHeaderSpec cookies(MultiValueMap<String, String> cookies) {
202-
this.requestBuilder.cookies(cookies);
236+
if (cookies != null) {
237+
getCookies().putAll(cookies);
238+
}
203239
return this;
204240
}
205241

206242
@Override
207243
public DefaultHeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
208244
ZonedDateTime gmt = ifModifiedSince.withZoneSameInstant(ZoneId.of("GMT"));
209245
String headerValue = DateTimeFormatter.RFC_1123_DATE_TIME.format(gmt);
210-
this.headers.set(HttpHeaders.IF_MODIFIED_SINCE, headerValue);
246+
getHeaders().set(HttpHeaders.IF_MODIFIED_SINCE, headerValue);
211247
return this;
212248
}
213249

214250
@Override
215251
public DefaultHeaderSpec ifNoneMatch(String... ifNoneMatches) {
216-
this.headers.setIfNoneMatch(Arrays.asList(ifNoneMatches));
252+
getHeaders().setIfNoneMatch(Arrays.asList(ifNoneMatches));
217253
return this;
218254
}
219255

220256
@Override
221257
public Mono<ClientResponse> exchange() {
222-
ClientRequest<Void> request = this.requestBuilder.headers(this.headers).build();
258+
ClientRequest<Void> request = initRequestBuilder().build();
223259
return getExchangeFunction().exchange(request);
224260
}
225261

226262
@Override
227263
public <T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
228-
ClientRequest<T> request = this.requestBuilder.headers(this.headers).body(inserter);
264+
ClientRequest<T> request = initRequestBuilder().body(inserter);
229265
return getExchangeFunction().exchange(request);
230266
}
231267

232268
@Override
233269
public <T, S extends Publisher<T>> Mono<ClientResponse> exchange(S publisher, Class<T> elementClass) {
234-
ClientRequest<S> request = this.requestBuilder.headers(this.headers).body(publisher, elementClass);
270+
ClientRequest<S> request = initRequestBuilder().headers(this.headers).body(publisher, elementClass);
235271
return getExchangeFunction().exchange(request);
236272
}
273+
274+
private ClientRequest.Builder initRequestBuilder() {
275+
return ClientRequest.method(this.httpMethod, this.uri).headers(initHeaders()).cookies(initCookies());
276+
}
277+
278+
private HttpHeaders initHeaders() {
279+
if (CollectionUtils.isEmpty(defaultHeaders) && CollectionUtils.isEmpty(this.headers)) {
280+
return null;
281+
}
282+
else if (CollectionUtils.isEmpty(defaultHeaders)) {
283+
return this.headers;
284+
}
285+
else if (CollectionUtils.isEmpty(this.headers)) {
286+
return defaultHeaders;
287+
}
288+
else {
289+
HttpHeaders result = new HttpHeaders();
290+
result.putAll(this.headers);
291+
defaultHeaders.forEach((name, values) -> {
292+
if (!this.headers.containsKey(name)) {
293+
values.forEach(value -> result.add(name, value));
294+
}
295+
});
296+
return result;
297+
}
298+
}
299+
300+
private MultiValueMap<String, String> initCookies() {
301+
if (CollectionUtils.isEmpty(defaultCookies) && CollectionUtils.isEmpty(this.cookies)) {
302+
return null;
303+
}
304+
else if (CollectionUtils.isEmpty(defaultCookies)) {
305+
return this.cookies;
306+
}
307+
else if (CollectionUtils.isEmpty(this.cookies)) {
308+
return defaultCookies;
309+
}
310+
else {
311+
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
312+
result.putAll(this.cookies);
313+
defaultCookies.forEach(result::putIfAbsent);
314+
return result;
315+
}
316+
}
237317
}
238318

239319
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616

1717
package org.springframework.web.reactive.function.client;
1818

19+
import java.util.Arrays;
20+
21+
import org.springframework.http.HttpHeaders;
1922
import org.springframework.http.client.reactive.ClientHttpConnector;
2023
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
2124
import org.springframework.util.Assert;
25+
import org.springframework.util.LinkedMultiValueMap;
26+
import org.springframework.util.MultiValueMap;
2227
import org.springframework.web.util.DefaultUriBuilderFactory;
2328
import org.springframework.web.util.UriBuilderFactory;
2429

@@ -36,6 +41,12 @@ class DefaultWebClientBuilder implements WebClient.Builder {
3641

3742
private ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults();
3843

44+
private ExchangeFunction exchangeFunction;
45+
46+
private HttpHeaders defaultHeaders;
47+
48+
private MultiValueMap<String, String> defaultCookies;
49+
3950

4051
public DefaultWebClientBuilder(String baseUrl) {
4152
this(new DefaultUriBuilderFactory(baseUrl));
@@ -60,11 +71,49 @@ public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
6071
return this;
6172
}
6273

74+
@Override
75+
public WebClient.Builder exchangeFunction(ExchangeFunction exchangeFunction) {
76+
this.exchangeFunction = exchangeFunction;
77+
return this;
78+
}
79+
80+
@Override
81+
public WebClient.Builder defaultHeader(String headerName, String... headerValues) {
82+
if (this.defaultHeaders == null) {
83+
this.defaultHeaders = new HttpHeaders();
84+
}
85+
for (String headerValue : headerValues) {
86+
this.defaultHeaders.add(headerName, headerValue);
87+
}
88+
return this;
89+
}
90+
91+
@Override
92+
public WebClient.Builder defaultCookie(String cookieName, String... cookieValues) {
93+
if (this.defaultCookies == null) {
94+
this.defaultCookies = new LinkedMultiValueMap<>(4);
95+
}
96+
this.defaultCookies.addAll(cookieName, Arrays.asList(cookieValues));
97+
return this;
98+
}
99+
63100
@Override
64101
public WebClient build() {
65-
ClientHttpConnector connector = this.connector != null ? this.connector : new ReactorClientHttpConnector();
66-
ExchangeFunction exchangeFunction = ExchangeFunctions.create(connector, this.exchangeStrategies);
67-
return new DefaultWebClient(exchangeFunction, this.uriBuilderFactory);
102+
return new DefaultWebClient(initExchangeFunction(),
103+
this.uriBuilderFactory, this.defaultHeaders, this.defaultCookies);
104+
}
105+
106+
private ExchangeFunction initExchangeFunction() {
107+
if (this.exchangeFunction != null) {
108+
return this.exchangeFunction;
109+
}
110+
else if (this.connector != null) {
111+
return ExchangeFunctions.create(this.connector, this.exchangeStrategies);
112+
}
113+
114+
else {
115+
return ExchangeFunctions.create(new ReactorClientHttpConnector(), this.exchangeStrategies);
116+
}
68117
}
69118

70119
}

spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/WebClient.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,28 @@ interface Builder {
173173
*/
174174
Builder exchangeStrategies(ExchangeStrategies strategies);
175175

176+
/**
177+
* Configure directly an {@link ExchangeFunction} instead of separately
178+
* providing a {@link ClientHttpConnector} and/or
179+
* {@link ExchangeStrategies}.
180+
* @param exchangeFunction the exchange function to use
181+
*/
182+
Builder exchangeFunction(ExchangeFunction exchangeFunction);
183+
184+
/**
185+
* Add the given header to all requests that haven't added it.
186+
* @param headerName the header name
187+
* @param headerValues the header values
188+
*/
189+
Builder defaultHeader(String headerName, String... headerValues);
190+
191+
/**
192+
* Add the given header to all requests that haven't added it.
193+
* @param cookieName the cookie name
194+
* @param cookieValues the cookie values
195+
*/
196+
Builder defaultCookie(String cookieName, String... cookieValues);
197+
176198
/**
177199
* Builder the {@link WebClient} instance.
178200
*/

0 commit comments

Comments
 (0)