Support for default headers and cookies
Issue: SPR-15208
This commit is contained in:
@@ -32,6 +32,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyInserter;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
@@ -50,10 +52,22 @@ class DefaultWebClient implements WebClient {
|
||||
|
||||
private final UriBuilderFactory uriBuilderFactory;
|
||||
|
||||
private final HttpHeaders defaultHeaders;
|
||||
|
||||
private final MultiValueMap<String, String> defaultCookies;
|
||||
|
||||
|
||||
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory factory,
|
||||
HttpHeaders defaultHeaders, MultiValueMap<String, String> defaultCookies) {
|
||||
|
||||
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory factory) {
|
||||
this.exchangeFunction = exchangeFunction;
|
||||
this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory());
|
||||
|
||||
this.defaultHeaders = defaultHeaders != null ?
|
||||
HttpHeaders.readOnlyHttpHeaders(defaultHeaders) : null;
|
||||
|
||||
this.defaultCookies = defaultCookies != null ?
|
||||
CollectionUtils.unmodifiableMultiValueMap(defaultCookies) : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +124,8 @@ class DefaultWebClient implements WebClient {
|
||||
@Override
|
||||
public WebClient filter(ExchangeFilterFunction filterFunction) {
|
||||
ExchangeFunction filteredExchangeFunction = this.exchangeFunction.filter(filterFunction);
|
||||
return new DefaultWebClient(filteredExchangeFunction, this.uriBuilderFactory);
|
||||
return new DefaultWebClient(filteredExchangeFunction,
|
||||
this.uriBuilderFactory, this.defaultHeaders, this.defaultCookies);
|
||||
}
|
||||
|
||||
|
||||
@@ -123,11 +138,6 @@ class DefaultWebClient implements WebClient {
|
||||
this.httpMethod = httpMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(URI uri) {
|
||||
return new DefaultHeaderSpec(ClientRequest.method(this.httpMethod, uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(String uriTemplate, Object... uriVariables) {
|
||||
return uri(getUriBuilderFactory().expand(uriTemplate, uriVariables));
|
||||
@@ -137,24 +147,48 @@ class DefaultWebClient implements WebClient {
|
||||
public HeaderSpec uri(Function<UriBuilderFactory, URI> uriFunction) {
|
||||
return uri(uriFunction.apply(getUriBuilderFactory()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderSpec uri(URI uri) {
|
||||
return new DefaultHeaderSpec(this.httpMethod, uri);
|
||||
}
|
||||
}
|
||||
|
||||
private class DefaultHeaderSpec implements HeaderSpec {
|
||||
|
||||
private final ClientRequest.Builder requestBuilder;
|
||||
private final HttpMethod httpMethod;
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
private final URI uri;
|
||||
|
||||
private HttpHeaders headers;
|
||||
|
||||
private MultiValueMap<String, String> cookies;
|
||||
|
||||
|
||||
DefaultHeaderSpec(ClientRequest.Builder requestBuilder) {
|
||||
this.requestBuilder = requestBuilder;
|
||||
DefaultHeaderSpec(HttpMethod httpMethod, URI uri) {
|
||||
this.httpMethod = httpMethod;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
|
||||
private HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders();
|
||||
}
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> getCookies() {
|
||||
if (this.cookies == null) {
|
||||
this.cookies = new LinkedMultiValueMap<>(4);
|
||||
}
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec header(String headerName, String... headerValues) {
|
||||
for (String headerValue : headerValues) {
|
||||
this.headers.add(headerName, headerValue);
|
||||
getHeaders().add(headerName, headerValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -162,44 +196,46 @@ class DefaultWebClient implements WebClient {
|
||||
@Override
|
||||
public DefaultHeaderSpec headers(HttpHeaders headers) {
|
||||
if (headers != null) {
|
||||
this.headers.putAll(headers);
|
||||
getHeaders().putAll(headers);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec accept(MediaType... acceptableMediaTypes) {
|
||||
this.headers.setAccept(Arrays.asList(acceptableMediaTypes));
|
||||
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec acceptCharset(Charset... acceptableCharsets) {
|
||||
this.headers.setAcceptCharset(Arrays.asList(acceptableCharsets));
|
||||
getHeaders().setAcceptCharset(Arrays.asList(acceptableCharsets));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec contentType(MediaType contentType) {
|
||||
this.headers.setContentType(contentType);
|
||||
getHeaders().setContentType(contentType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec contentLength(long contentLength) {
|
||||
this.headers.setContentLength(contentLength);
|
||||
getHeaders().setContentLength(contentLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec cookie(String name, String value) {
|
||||
this.requestBuilder.cookie(name, value);
|
||||
getCookies().add(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec cookies(MultiValueMap<String, String> cookies) {
|
||||
this.requestBuilder.cookies(cookies);
|
||||
if (cookies != null) {
|
||||
getCookies().putAll(cookies);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -207,33 +243,77 @@ class DefaultWebClient implements WebClient {
|
||||
public DefaultHeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
|
||||
ZonedDateTime gmt = ifModifiedSince.withZoneSameInstant(ZoneId.of("GMT"));
|
||||
String headerValue = DateTimeFormatter.RFC_1123_DATE_TIME.format(gmt);
|
||||
this.headers.set(HttpHeaders.IF_MODIFIED_SINCE, headerValue);
|
||||
getHeaders().set(HttpHeaders.IF_MODIFIED_SINCE, headerValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultHeaderSpec ifNoneMatch(String... ifNoneMatches) {
|
||||
this.headers.setIfNoneMatch(Arrays.asList(ifNoneMatches));
|
||||
getHeaders().setIfNoneMatch(Arrays.asList(ifNoneMatches));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ClientResponse> exchange() {
|
||||
ClientRequest<Void> request = this.requestBuilder.headers(this.headers).build();
|
||||
ClientRequest<Void> request = initRequestBuilder().build();
|
||||
return getExchangeFunction().exchange(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
|
||||
ClientRequest<T> request = this.requestBuilder.headers(this.headers).body(inserter);
|
||||
ClientRequest<T> request = initRequestBuilder().body(inserter);
|
||||
return getExchangeFunction().exchange(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, S extends Publisher<T>> Mono<ClientResponse> exchange(S publisher, Class<T> elementClass) {
|
||||
ClientRequest<S> request = this.requestBuilder.headers(this.headers).body(publisher, elementClass);
|
||||
ClientRequest<S> request = initRequestBuilder().headers(this.headers).body(publisher, elementClass);
|
||||
return getExchangeFunction().exchange(request);
|
||||
}
|
||||
|
||||
private ClientRequest.Builder initRequestBuilder() {
|
||||
return ClientRequest.method(this.httpMethod, this.uri).headers(initHeaders()).cookies(initCookies());
|
||||
}
|
||||
|
||||
private HttpHeaders initHeaders() {
|
||||
if (CollectionUtils.isEmpty(defaultHeaders) && CollectionUtils.isEmpty(this.headers)) {
|
||||
return null;
|
||||
}
|
||||
else if (CollectionUtils.isEmpty(defaultHeaders)) {
|
||||
return this.headers;
|
||||
}
|
||||
else if (CollectionUtils.isEmpty(this.headers)) {
|
||||
return defaultHeaders;
|
||||
}
|
||||
else {
|
||||
HttpHeaders result = new HttpHeaders();
|
||||
result.putAll(this.headers);
|
||||
defaultHeaders.forEach((name, values) -> {
|
||||
if (!this.headers.containsKey(name)) {
|
||||
values.forEach(value -> result.add(name, value));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private MultiValueMap<String, String> initCookies() {
|
||||
if (CollectionUtils.isEmpty(defaultCookies) && CollectionUtils.isEmpty(this.cookies)) {
|
||||
return null;
|
||||
}
|
||||
else if (CollectionUtils.isEmpty(defaultCookies)) {
|
||||
return this.cookies;
|
||||
}
|
||||
else if (CollectionUtils.isEmpty(this.cookies)) {
|
||||
return defaultCookies;
|
||||
}
|
||||
else {
|
||||
MultiValueMap<String, String> result = new LinkedMultiValueMap<>();
|
||||
result.putAll(this.cookies);
|
||||
defaultCookies.forEach(result::putIfAbsent);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
import org.springframework.web.util.UriBuilderFactory;
|
||||
|
||||
@@ -36,6 +41,12 @@ class DefaultWebClientBuilder implements WebClient.Builder {
|
||||
|
||||
private ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults();
|
||||
|
||||
private ExchangeFunction exchangeFunction;
|
||||
|
||||
private HttpHeaders defaultHeaders;
|
||||
|
||||
private MultiValueMap<String, String> defaultCookies;
|
||||
|
||||
|
||||
public DefaultWebClientBuilder(String baseUrl) {
|
||||
this(new DefaultUriBuilderFactory(baseUrl));
|
||||
@@ -60,11 +71,49 @@ class DefaultWebClientBuilder implements WebClient.Builder {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClient.Builder exchangeFunction(ExchangeFunction exchangeFunction) {
|
||||
this.exchangeFunction = exchangeFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClient.Builder defaultHeader(String headerName, String... headerValues) {
|
||||
if (this.defaultHeaders == null) {
|
||||
this.defaultHeaders = new HttpHeaders();
|
||||
}
|
||||
for (String headerValue : headerValues) {
|
||||
this.defaultHeaders.add(headerName, headerValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClient.Builder defaultCookie(String cookieName, String... cookieValues) {
|
||||
if (this.defaultCookies == null) {
|
||||
this.defaultCookies = new LinkedMultiValueMap<>(4);
|
||||
}
|
||||
this.defaultCookies.addAll(cookieName, Arrays.asList(cookieValues));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebClient build() {
|
||||
ClientHttpConnector connector = this.connector != null ? this.connector : new ReactorClientHttpConnector();
|
||||
ExchangeFunction exchangeFunction = ExchangeFunctions.create(connector, this.exchangeStrategies);
|
||||
return new DefaultWebClient(exchangeFunction, this.uriBuilderFactory);
|
||||
return new DefaultWebClient(initExchangeFunction(),
|
||||
this.uriBuilderFactory, this.defaultHeaders, this.defaultCookies);
|
||||
}
|
||||
|
||||
private ExchangeFunction initExchangeFunction() {
|
||||
if (this.exchangeFunction != null) {
|
||||
return this.exchangeFunction;
|
||||
}
|
||||
else if (this.connector != null) {
|
||||
return ExchangeFunctions.create(this.connector, this.exchangeStrategies);
|
||||
}
|
||||
|
||||
else {
|
||||
return ExchangeFunctions.create(new ReactorClientHttpConnector(), this.exchangeStrategies);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -173,6 +173,28 @@ public interface WebClient {
|
||||
*/
|
||||
Builder exchangeStrategies(ExchangeStrategies strategies);
|
||||
|
||||
/**
|
||||
* Configure directly an {@link ExchangeFunction} instead of separately
|
||||
* providing a {@link ClientHttpConnector} and/or
|
||||
* {@link ExchangeStrategies}.
|
||||
* @param exchangeFunction the exchange function to use
|
||||
*/
|
||||
Builder exchangeFunction(ExchangeFunction exchangeFunction);
|
||||
|
||||
/**
|
||||
* Add the given header to all requests that haven't added it.
|
||||
* @param headerName the header name
|
||||
* @param headerValues the header values
|
||||
*/
|
||||
Builder defaultHeader(String headerName, String... headerValues);
|
||||
|
||||
/**
|
||||
* Add the given header to all requests that haven't added it.
|
||||
* @param cookieName the cookie name
|
||||
* @param cookieValues the cookie values
|
||||
*/
|
||||
Builder defaultCookie(String cookieName, String... cookieValues);
|
||||
|
||||
/**
|
||||
* Builder the {@link WebClient} instance.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2002-2017 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
|
||||
*
|
||||
* http://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.reactive.function.client;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultWebClient}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class DefaultWebClientTests {
|
||||
|
||||
private ExchangeFunction exchangeFunction;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<ClientRequest<?>> captor;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
this.exchangeFunction = mock(ExchangeFunction.class);
|
||||
when(this.exchangeFunction.exchange(captor.capture())).thenReturn(Mono.empty());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void basic() throws Exception {
|
||||
WebClient client = builder().build();
|
||||
client.get().uri("/path").exchange();
|
||||
|
||||
ClientRequest<?> request = verifyExchange();
|
||||
assertEquals("/base/path", request.url().toString());
|
||||
assertEquals(new HttpHeaders(), request.headers());
|
||||
assertEquals(Collections.emptyMap(), request.cookies());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestHeaderAndCookie() throws Exception {
|
||||
WebClient client = builder().build();
|
||||
client.get().uri("/path").accept(MediaType.APPLICATION_JSON).cookie("id", "123").exchange();
|
||||
|
||||
ClientRequest<?> request = verifyExchange();
|
||||
assertEquals("application/json", request.headers().getFirst("Accept"));
|
||||
assertEquals("123", request.cookies().getFirst("id"));
|
||||
verifyNoMoreInteractions(this.exchangeFunction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultHeaderAndCookie() throws Exception {
|
||||
WebClient client = builder().defaultHeader("Accept", "application/json").defaultCookie("id", "123").build();
|
||||
client.get().uri("/path").exchange();
|
||||
|
||||
ClientRequest<?> request = verifyExchange();
|
||||
assertEquals("application/json", request.headers().getFirst("Accept"));
|
||||
assertEquals("123", request.cookies().getFirst("id"));
|
||||
verifyNoMoreInteractions(this.exchangeFunction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultHeaderAndCookieOverrides() throws Exception {
|
||||
WebClient client = builder().defaultHeader("Accept", "application/json").defaultCookie("id", "123").build();
|
||||
client.get().uri("/path").header("Accept", "application/xml").cookie("id", "456").exchange();
|
||||
|
||||
ClientRequest<?> request = verifyExchange();
|
||||
assertEquals("application/xml", request.headers().getFirst("Accept"));
|
||||
assertEquals("456", request.cookies().getFirst("id"));
|
||||
verifyNoMoreInteractions(this.exchangeFunction);
|
||||
}
|
||||
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder("/base").exchangeFunction(this.exchangeFunction);
|
||||
}
|
||||
|
||||
private ClientRequest<?> verifyExchange() {
|
||||
ClientRequest<?> request = this.captor.getValue();
|
||||
Mockito.verify(this.exchangeFunction).exchange(request);
|
||||
verifyNoMoreInteractions(this.exchangeFunction);
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user