Browse Source

Support for default headers and cookies

Issue: SPR-15208
pull/1313/head
Rossen Stoyanchev 9 years ago
parent
commit
81d1217976
  1. 128
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
  2. 55
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java
  3. 22
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/WebClient.java
  4. 112
      spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java

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

@ -32,6 +32,8 @@ import org.springframework.http.HttpHeaders; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 URI uri;
private final HttpHeaders headers = new HttpHeaders();
private HttpHeaders headers;
private MultiValueMap<String, String> cookies;
DefaultHeaderSpec(HttpMethod httpMethod, URI uri) {
this.httpMethod = httpMethod;
this.uri = uri;
}
DefaultHeaderSpec(ClientRequest.Builder requestBuilder) {
this.requestBuilder = requestBuilder;
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 { @@ -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 { @@ -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;
}
}
}
}

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

@ -16,9 +16,14 @@ @@ -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 { @@ -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 { @@ -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);
}
}
}

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

@ -173,6 +173,28 @@ public interface WebClient { @@ -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.
*/

112
spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java

@ -0,0 +1,112 @@ @@ -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;
}
}
Loading…
Cancel
Save