Browse Source

Merge changes for SPR-15124

pull/1308/head
Rossen Stoyanchev 9 years ago
parent
commit
ecb2e97ab4
  1. 177
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/ClientRequest.java
  2. 56
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java
  3. 238
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientOperations.java
  4. 51
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientOperationsBuilder.java
  5. 295
      spring-web-reactive/src/main/java/org/springframework/web/reactive/function/client/WebClientOperations.java
  6. 29
      spring-web-reactive/src/test/java/org/springframework/web/reactive/FlushingIntegrationTests.java
  7. 113
      spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java
  8. 10
      spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java
  9. 158
      spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java
  10. 62
      spring-web-reactive/src/test/java/org/springframework/web/reactive/function/server/SseHandlerFunctionIntegrationTests.java
  11. 75
      spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java
  12. 14
      spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java
  13. 25
      spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
  14. 2
      spring-web/src/main/java/org/springframework/web/util/AbstractUriTemplateHandler.java
  15. 299
      spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java
  16. 2
      spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java
  17. 30
      spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java
  18. 12
      spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java
  19. 171
      spring-web/src/main/java/org/springframework/web/util/UriBuilder.java
  20. 43
      spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java
  21. 51
      spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
  22. 12
      spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java
  23. 7
      spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java
  24. 136
      spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java

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

@ -17,27 +17,25 @@ @@ -17,27 +17,25 @@
package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
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.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.UriTemplateHandler;
/**
* Represents a typed, immutable, client-side HTTP request, as executed by the {@link WebClient}.
* Instances of this interface are created via static builder methods:
* {@link #method(HttpMethod, String, Object...)}, {@link #GET(String, Object...)}, etc.
* Represents a typed, immutable, client-side HTTP request, as executed by the
* {@link WebClient}. Instances of this interface can be created via static
* builder methods in this class.
*
* <p>Note that applications are more likely to perform requests through
* {@link WebClientOperations} rather than using this directly.
* :
* @param <T> the type of the body that this request contains
* @author Brian Clozel
* @author Arjen Poutsma
@ -45,8 +43,6 @@ import org.springframework.web.util.UriTemplateHandler; @@ -45,8 +43,6 @@ import org.springframework.web.util.UriTemplateHandler;
*/
public interface ClientRequest<T> {
// Instance methods
/**
* Return the HTTP method.
*/
@ -81,6 +77,7 @@ public interface ClientRequest<T> { @@ -81,6 +77,7 @@ public interface ClientRequest<T> {
*/
Mono<Void> writeTo(ClientHttpRequest request, WebClientStrategies strategies);
// Static builder methods
/**
@ -89,7 +86,7 @@ public interface ClientRequest<T> { @@ -89,7 +86,7 @@ public interface ClientRequest<T> {
* @param other the request to copy the method, URI, headers, and cookies from
* @return the created builder
*/
static BodyBuilder from(ClientRequest<?> other) {
static Builder from(ClientRequest<?> other) {
Assert.notNull(other, "'other' must not be null");
return new DefaultClientRequestBuilder(other.method(), other.url())
.headers(other.headers())
@ -102,100 +99,15 @@ public interface ClientRequest<T> { @@ -102,100 +99,15 @@ public interface ClientRequest<T> {
* @param url the URL
* @return the created builder
*/
static BodyBuilder method(HttpMethod method, URI url) {
return new DefaultClientRequestBuilder(method, url);
}
/**
* Create a builder with the given method and url template.
* @param method the HTTP method (GET, POST, etc)
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static BodyBuilder method(HttpMethod method, String urlTemplate, Object... uriVariables) {
UriTemplateHandler templateHandler = new DefaultUriTemplateHandler();
URI url = templateHandler.expand(urlTemplate, uriVariables);
static Builder method(HttpMethod method, URI url) {
return new DefaultClientRequestBuilder(method, url);
}
/**
* Create an HTTP GET builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static HeadersBuilder<?> GET(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.GET, urlTemplate, uriVariables);
}
/**
* Create an HTTP HEAD builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static HeadersBuilder<?> HEAD(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.HEAD, urlTemplate, uriVariables);
}
/**
* Create an HTTP POST builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static BodyBuilder POST(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.POST, urlTemplate, uriVariables);
}
/**
* Create an HTTP PUT builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static BodyBuilder PUT(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.PUT, urlTemplate, uriVariables);
}
/**
* Create an HTTP PATCH builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static BodyBuilder PATCH(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.PATCH, urlTemplate, uriVariables);
}
/**
* Create an HTTP DELETE builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static HeadersBuilder<?> DELETE(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.DELETE, urlTemplate, uriVariables);
}
/**
* Creates an HTTP OPTIONS builder with the given url template.
* @param urlTemplate the URL template
* @param uriVariables optional variables to expand the template
* @return the created builder
*/
static HeadersBuilder<?> OPTIONS(String urlTemplate, Object... uriVariables) {
return method(HttpMethod.OPTIONS, urlTemplate, uriVariables);
}
/**
* Defines a builder that adds headers to the request.
*
* @param <B> the builder subclass
* Defines a builder for a request.
*/
interface HeadersBuilder<B extends HeadersBuilder<B>> {
interface Builder {
/**
* Add the given, single header value under the given name.
@ -204,7 +116,7 @@ public interface ClientRequest<T> { @@ -204,7 +116,7 @@ public interface ClientRequest<T> {
* @return this builder
* @see HttpHeaders#add(String, String)
*/
B header(String headerName, String... headerValues);
Builder header(String headerName, String... headerValues);
/**
* Copy the given headers into the entity's headers map.
@ -212,39 +124,7 @@ public interface ClientRequest<T> { @@ -212,39 +124,7 @@ public interface ClientRequest<T> {
* @param headers the existing HttpHeaders to copy from
* @return this builder
*/
B headers(HttpHeaders headers);
/**
* Set the list of acceptable {@linkplain MediaType media types}, as
* specified by the {@code Accept} header.
* @param acceptableMediaTypes the acceptable media types
* @return this builder
*/
B accept(MediaType... acceptableMediaTypes);
/**
* Set the list of acceptable {@linkplain Charset charsets}, as specified
* by the {@code Accept-Charset} header.
* @param acceptableCharsets the acceptable charsets
* @return this builder
*/
B acceptCharset(Charset... acceptableCharsets);
/**
* Set the value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
* January 1, 1970 GMT.
* @param ifModifiedSince the new value of the header
* @return this builder
*/
B ifModifiedSince(ZonedDateTime ifModifiedSince);
/**
* Set the values of the {@code If-None-Match} header.
* @param ifNoneMatches the new value of the header
* @return this builder
*/
B ifNoneMatch(String... ifNoneMatches);
Builder headers(HttpHeaders headers);
/**
* Add a cookie with the given name and value.
@ -252,7 +132,7 @@ public interface ClientRequest<T> { @@ -252,7 +132,7 @@ public interface ClientRequest<T> {
* @param value the cookie value
* @return this builder
*/
B cookie(String name, String value);
Builder cookie(String name, String value);
/**
* Copy the given cookies into the entity's cookies map.
@ -260,39 +140,13 @@ public interface ClientRequest<T> { @@ -260,39 +140,13 @@ public interface ClientRequest<T> {
* @param cookies the existing cookies to copy from
* @return this builder
*/
B cookies(MultiValueMap<String, String> cookies);
Builder cookies(MultiValueMap<String, String> cookies);
/**
* Builds the request entity with no body.
* @return the request entity
*/
ClientRequest<Void> build();
}
/**
* Defines a builder that adds a body to the request entity.
*/
interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
/**
* Set the length of the body in bytes, as specified by the
* {@code Content-Length} header.
* @param contentLength the content length
* @return this builder
* @see HttpHeaders#setContentLength(long)
*/
BodyBuilder contentLength(long contentLength);
/**
* Set the {@linkplain MediaType media type} of the body, as specified
* by the {@code Content-Type} header.
* @param contentType the content type
* @return this builder
* @see HttpHeaders#setContentType(MediaType)
*/
BodyBuilder contentType(MediaType contentType);
/**
* Set the body of the request to the given {@code BodyInserter} and return it.
@ -314,5 +168,4 @@ public interface ClientRequest<T> { @@ -314,5 +168,4 @@ public interface ClientRequest<T> {
}
}

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

@ -17,11 +17,6 @@ @@ -17,11 +17,6 @@
package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.function.Supplier;
@ -33,7 +28,6 @@ import reactor.core.publisher.Mono; @@ -33,7 +28,6 @@ import reactor.core.publisher.Mono;
import org.springframework.http.HttpCookie;
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.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
@ -44,12 +38,12 @@ import org.springframework.web.reactive.function.BodyInserter; @@ -44,12 +38,12 @@ import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
/**
* Default implementation of {@link ClientRequest.BodyBuilder}.
* Default implementation of {@link ClientRequest.Builder}.
*
* @author Arjen Poutsma
* @since 5.0
*/
class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
class DefaultClientRequestBuilder implements ClientRequest.Builder {
private final HttpMethod method;
@ -66,7 +60,7 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder { @@ -66,7 +60,7 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
}
@Override
public ClientRequest.BodyBuilder header(String headerName, String... headerValues) {
public ClientRequest.Builder header(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
this.headers.add(headerName, headerValue);
}
@ -74,7 +68,7 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder { @@ -74,7 +68,7 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
}
@Override
public ClientRequest.BodyBuilder headers(HttpHeaders headers) {
public ClientRequest.Builder headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
@ -82,39 +76,13 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder { @@ -82,39 +76,13 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
}
@Override
public ClientRequest.BodyBuilder accept(MediaType... acceptableMediaTypes) {
this.headers.setAccept(Arrays.asList(acceptableMediaTypes));
return this;
}
@Override
public ClientRequest.BodyBuilder acceptCharset(Charset... acceptableCharsets) {
this.headers.setAcceptCharset(Arrays.asList(acceptableCharsets));
return this;
}
@Override
public ClientRequest.BodyBuilder 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);
return this;
}
@Override
public ClientRequest.BodyBuilder ifNoneMatch(String... ifNoneMatches) {
this.headers.setIfNoneMatch(Arrays.asList(ifNoneMatches));
return this;
}
@Override
public ClientRequest.BodyBuilder cookie(String name, String value) {
public ClientRequest.Builder cookie(String name, String value) {
this.cookies.add(name, value);
return this;
}
@Override
public ClientRequest.BodyBuilder cookies(MultiValueMap<String, String> cookies) {
public ClientRequest.Builder cookies(MultiValueMap<String, String> cookies) {
if (cookies != null) {
this.cookies.putAll(cookies);
}
@ -126,18 +94,6 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder { @@ -126,18 +94,6 @@ class DefaultClientRequestBuilder implements ClientRequest.BodyBuilder {
return body(BodyInserters.empty());
}
@Override
public ClientRequest.BodyBuilder contentLength(long contentLength) {
this.headers.setContentLength(contentLength);
return this;
}
@Override
public ClientRequest.BodyBuilder contentType(MediaType contentType) {
this.headers.setContentType(contentType);
return this;
}
@Override
public <T> ClientRequest<T> body(BodyInserter<T, ? super ClientHttpRequest> inserter) {
Assert.notNull(inserter, "'inserter' must not be null");

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

@ -0,0 +1,238 @@ @@ -0,0 +1,238 @@
/*
* 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.net.URI;
import java.nio.charset.Charset;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
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.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link WebClientOperations}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
class DefaultWebClientOperations implements WebClientOperations {
private final WebClient webClient;
private final UriBuilderFactory uriBuilderFactory;
DefaultWebClientOperations(WebClient webClient, UriBuilderFactory factory) {
this.webClient = webClient;
this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory());
}
private WebClient getWebClient() {
return this.webClient;
}
private UriBuilderFactory getUriBuilderFactory() {
return this.uriBuilderFactory;
}
@Override
public UriSpec get() {
return method(HttpMethod.GET);
}
@Override
public UriSpec head() {
return method(HttpMethod.HEAD);
}
@Override
public UriSpec post() {
return method(HttpMethod.POST);
}
@Override
public UriSpec put() {
return method(HttpMethod.PUT);
}
@Override
public UriSpec patch() {
return method(HttpMethod.PATCH);
}
@Override
public UriSpec delete() {
return method(HttpMethod.DELETE);
}
@Override
public UriSpec options() {
return method(HttpMethod.OPTIONS);
}
@NotNull
private UriSpec method(HttpMethod httpMethod) {
return new DefaultUriSpec(httpMethod);
}
@Override
public WebClientOperations filter(ExchangeFilterFunction filterFunction) {
WebClient filteredWebClient = this.webClient.filter(filterFunction);
return new DefaultWebClientOperations(filteredWebClient, this.uriBuilderFactory);
}
private class DefaultUriSpec implements UriSpec {
private final HttpMethod httpMethod;
DefaultUriSpec(HttpMethod httpMethod) {
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));
}
@Override
public HeaderSpec uri(Function<UriBuilderFactory, URI> uriFunction) {
return uri(uriFunction.apply(getUriBuilderFactory()));
}
}
private class DefaultHeaderSpec implements HeaderSpec {
private final ClientRequest.Builder requestBuilder;
private final HttpHeaders headers = new HttpHeaders();
DefaultHeaderSpec(ClientRequest.Builder requestBuilder) {
this.requestBuilder = requestBuilder;
}
@Override
public DefaultHeaderSpec header(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
this.headers.add(headerName, headerValue);
}
return this;
}
@Override
public DefaultHeaderSpec headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
return this;
}
@Override
public DefaultHeaderSpec accept(MediaType... acceptableMediaTypes) {
this.headers.setAccept(Arrays.asList(acceptableMediaTypes));
return this;
}
@Override
public DefaultHeaderSpec acceptCharset(Charset... acceptableCharsets) {
this.headers.setAcceptCharset(Arrays.asList(acceptableCharsets));
return this;
}
@Override
public DefaultHeaderSpec contentType(MediaType contentType) {
this.headers.setContentType(contentType);
return this;
}
@Override
public DefaultHeaderSpec contentLength(long contentLength) {
this.headers.setContentLength(contentLength);
return this;
}
@Override
public DefaultHeaderSpec cookie(String name, String value) {
this.requestBuilder.cookie(name, value);
return this;
}
@Override
public DefaultHeaderSpec cookies(MultiValueMap<String, String> cookies) {
this.requestBuilder.cookies(cookies);
return this;
}
@Override
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);
return this;
}
@Override
public DefaultHeaderSpec ifNoneMatch(String... ifNoneMatches) {
this.headers.setIfNoneMatch(Arrays.asList(ifNoneMatches));
return this;
}
@Override
public Mono<ClientResponse> exchange() {
ClientRequest<Void> request = this.requestBuilder.headers(this.headers).build();
return getWebClient().exchange(request);
}
@Override
public <T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter) {
ClientRequest<T> request = this.requestBuilder.headers(this.headers).body(inserter);
return getWebClient().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);
return getWebClient().exchange(request);
}
}
}

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

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*
* 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 org.springframework.util.Assert;
import org.springframework.web.util.UriBuilderFactory;
/**
* Default implementation of {@link WebClientOperations.Builder}.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
class DefaultWebClientOperationsBuilder implements WebClientOperations.Builder {
private final WebClient webClient;
private UriBuilderFactory uriBuilderFactory;
public DefaultWebClientOperationsBuilder(WebClient webClient) {
Assert.notNull(webClient, "WebClient is required");
this.webClient = webClient;
}
@Override
public WebClientOperations.Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
this.uriBuilderFactory = uriBuilderFactory;
return this;
}
@Override
public WebClientOperations build() {
return new DefaultWebClientOperations(this.webClient, this.uriBuilderFactory);
}
}

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

@ -0,0 +1,295 @@ @@ -0,0 +1,295 @@
/*
* 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.net.URI;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.util.UriBuilderFactory;
/**
* The main class for performing requests through a WebClient.
*
* <pre class="code">
*
* // Create WebClient (application-wide)
*
* ClientHttpConnector connector = new ReactorClientHttpConnector();
* WebClient webClient = WebClient.create(connector);
*
* // Create WebClientOperations (per base URI)
*
* String baseUri = "http://abc.com";
* UriBuilderFactory factory = new DefaultUriBuilderFactory(baseUri);
* WebClientOperations operations = WebClientOperations.create(webClient, factory);
*
* // Perform requests...
*
* Mono<String> result = operations.get()
* .uri("/foo")
* .exchange()
* .then(response -> response.bodyToMono(String.class));
* </pre>
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public interface WebClientOperations {
/**
* Prepare an HTTP GET request.
* @return a spec for specifying the target URL
*/
UriSpec get();
/**
* Prepare an HTTP HEAD request.
* @return a spec for specifying the target URL
*/
UriSpec head();
/**
* Prepare an HTTP POST request.
* @return a spec for specifying the target URL
*/
UriSpec post();
/**
* Prepare an HTTP PUT request.
* @return a spec for specifying the target URL
*/
UriSpec put();
/**
* Prepare an HTTP PATCH request.
* @return a spec for specifying the target URL
*/
UriSpec patch();
/**
* Prepare an HTTP DELETE request.
* @return a spec for specifying the target URL
*/
UriSpec delete();
/**
* Prepare an HTTP OPTIONS request.
* @return a spec for specifying the target URL
*/
UriSpec options();
/**
* Filter the client with the given {@code ExchangeFilterFunction}.
* @param filterFunction the filter to apply to this client
* @return the filtered client
* @see ExchangeFilterFunction#apply(ExchangeFunction)
*/
WebClientOperations filter(ExchangeFilterFunction filterFunction);
// Static, factory methods
/**
* Create {@link WebClientOperations} that wraps the given {@link WebClient}.
* @param webClient the underlying client to use
*/
static WebClientOperations create(WebClient webClient) {
return builder(webClient).build();
}
/**
* Create {@link WebClientOperations} with a builder for additional
* configuration options.
* @param webClient the underlying client to use
*/
static WebClientOperations.Builder builder(WebClient webClient) {
return new DefaultWebClientOperationsBuilder(webClient);
}
/**
* A mutable builder for a {@link WebClientOperations}.
*/
interface Builder {
/**
* Configure a {@code UriBuilderFactory} for use with this client for
* example to define a common "base" URI.
* @param uriBuilderFactory the URI builder factory
*/
Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory);
/**
* Builder the {@link WebClient} instance.
*/
WebClientOperations build();
}
/**
* Contract for specifying the URI for a request.
*/
interface UriSpec {
/**
* Specify the URI using an absolute, fully constructed {@link URI}.
*/
HeaderSpec uri(URI uri);
/**
* Specify the URI for the request using a URI template and URI variables.
* If a {@link UriBuilderFactory} was configured for the client (e.g.
* with a base URI) it will be used to expand the URI template.
* @see Builder#uriBuilderFactory(UriBuilderFactory)
*/
HeaderSpec uri(String uri, Object... uriVariables);
/**
* Build the URI for the request using the {@link UriBuilderFactory}
* configured for this client.
* @see Builder#uriBuilderFactory(UriBuilderFactory)
*/
HeaderSpec uri(Function<UriBuilderFactory, URI> uriFunction);
}
/**
* Contract for specifying request headers leading up to the exchange.
*/
interface HeaderSpec {
/**
* Set the list of acceptable {@linkplain MediaType media types}, as
* specified by the {@code Accept} header.
* @param acceptableMediaTypes the acceptable media types
* @return this builder
*/
HeaderSpec accept(MediaType... acceptableMediaTypes);
/**
* Set the list of acceptable {@linkplain Charset charsets}, as specified
* by the {@code Accept-Charset} header.
* @param acceptableCharsets the acceptable charsets
* @return this builder
*/
HeaderSpec acceptCharset(Charset... acceptableCharsets);
/**
* Set the length of the body in bytes, as specified by the
* {@code Content-Length} header.
* @param contentLength the content length
* @return this builder
* @see HttpHeaders#setContentLength(long)
*/
HeaderSpec contentLength(long contentLength);
/**
* Set the {@linkplain MediaType media type} of the body, as specified
* by the {@code Content-Type} header.
* @param contentType the content type
* @return this builder
* @see HttpHeaders#setContentType(MediaType)
*/
HeaderSpec contentType(MediaType contentType);
/**
* Add a cookie with the given name and value.
* @param name the cookie name
* @param value the cookie value
* @return this builder
*/
HeaderSpec cookie(String name, String value);
/**
* Copy the given cookies into the entity's cookies map.
*
* @param cookies the existing cookies to copy from
* @return this builder
*/
HeaderSpec cookies(MultiValueMap<String, String> cookies);
/**
* Set the value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
* January 1, 1970 GMT.
* @param ifModifiedSince the new value of the header
* @return this builder
*/
HeaderSpec ifModifiedSince(ZonedDateTime ifModifiedSince);
/**
* Set the values of the {@code If-None-Match} header.
* @param ifNoneMatches the new value of the header
* @return this builder
*/
HeaderSpec ifNoneMatch(String... ifNoneMatches);
/**
* Add the given, single header value under the given name.
* @param headerName the header name
* @param headerValues the header value(s)
* @return this builder
*/
HeaderSpec header(String headerName, String... headerValues);
/**
* Copy the given headers into the entity's headers map.
* @param headers the existing headers to copy from
* @return this builder
*/
HeaderSpec headers(HttpHeaders headers);
/**
* Perform the request without a request body.
* @return a {@code Mono} with the response
*/
Mono<ClientResponse> exchange();
/**
* Set the body of the request to the given {@code BodyInserter} and
* perform the request.
* @param inserter the {@code BodyInserter} that writes to the request
* @param <T> the type contained in the body
* @return a {@code Mono} with the response
*/
<T> Mono<ClientResponse> exchange(BodyInserter<T, ? super ClientHttpRequest> inserter);
/**
* Set the body of the request to the given {@code Publisher} and
* perform the request.
* @param publisher the {@code Publisher} to write to the request
* @param elementClass the class of elements contained in the publisher
* @param <T> the type of the elements contained in the publisher
* @param <S> the type of the {@code Publisher}
* @return a {@code Mono} with the response
*/
<T, S extends Publisher<T>> Mono<ClientResponse> exchange(S publisher, Class<T> elementClass);
}
}

29
spring-web-reactive/src/test/java/org/springframework/web/reactive/FlushingIntegrationTests.java

@ -37,15 +37,17 @@ import org.springframework.http.server.reactive.ServerHttpResponse; @@ -37,15 +37,17 @@ import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.bootstrap.RxNettyHttpServer;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientOperations;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
/**
* @author Sebastien Deleuze
*/
public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private WebClient webClient;
private WebClientOperations operations;
@Before
@ -55,15 +57,18 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest @@ -55,15 +57,18 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
Assume.assumeFalse(this.server instanceof RxNettyHttpServer);
super.setup();
this.webClient = WebClient.create(new ReactorClientHttpConnector());
WebClient client = WebClient.create(new ReactorClientHttpConnector());
UriBuilderFactory factory = new DefaultUriBuilderFactory("http://localhost:" + this.port);
this.operations = WebClientOperations.builder(client).uriBuilderFactory(factory).build();
}
@Test
public void writeAndFlushWith() throws Exception {
ClientRequest<Void> request = ClientRequest.GET("http://localhost:" + port + "/write-and-flush").build();
Mono<String> result = this.webClient
.exchange(request)
Mono<String> result = this.operations.get()
.uri("/write-and-flush")
.exchange()
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.takeUntil(s -> s.endsWith("data1"))
.reduce((s1, s2) -> s1 + s2);
@ -76,9 +81,9 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest @@ -76,9 +81,9 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
@Test // SPR-14991
public void writeAndAutoFlushOnComplete() {
ClientRequest<Void> request = ClientRequest.GET("http://localhost:" + port + "/write-and-complete").build();
Mono<String> result = this.webClient
.exchange(request)
Mono<String> result = this.operations.get()
.uri("/write-and-complete")
.exchange()
.flatMap(response -> response.bodyToFlux(String.class))
.reduce((s1, s2) -> s1 + s2);
@ -90,9 +95,9 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest @@ -90,9 +95,9 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
@Test // SPR-14992
public void writeAndAutoFlushBeforeComplete() {
ClientRequest<Void> request = ClientRequest.GET("http://localhost:" + port + "/write-and-never-complete").build();
Flux<String> result = this.webClient
.exchange(request)
Flux<String> result = this.operations.get()
.uri("/write-and-never-complete")
.exchange()
.flatMap(response -> response.bodyToFlux(String.class));
StepVerifier.create(result)

113
spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java

@ -18,11 +18,7 @@ package org.springframework.web.reactive.function.client; @@ -18,11 +18,7 @@ package org.springframework.web.reactive.function.client;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
@ -31,8 +27,6 @@ import reactor.core.publisher.Mono; @@ -31,8 +27,6 @@ import reactor.core.publisher.Mono;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
@ -45,6 +39,9 @@ import static org.junit.Assert.assertNotNull; @@ -45,6 +39,9 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
/**
* @author Arjen Poutsma
@ -53,12 +50,12 @@ public class DefaultClientRequestBuilderTests { @@ -53,12 +50,12 @@ public class DefaultClientRequestBuilderTests {
@Test
public void from() throws Exception {
ClientRequest<Void> other = ClientRequest.GET("http://example.com")
ClientRequest<Void> other = ClientRequest.method(GET, URI.create("http://example.com"))
.header("foo", "bar")
.cookie("baz", "qux").build();
ClientRequest<Void> result = ClientRequest.from(other).build();
assertEquals(new URI("http://example.com"), result.url());
assertEquals(HttpMethod.GET, result.method());
assertEquals(GET, result.method());
assertEquals("bar", result.headers().getFirst("foo"));
assertEquals("qux", result.cookies().getFirst("baz"));
}
@ -66,112 +63,26 @@ public class DefaultClientRequestBuilderTests { @@ -66,112 +63,26 @@ public class DefaultClientRequestBuilderTests {
@Test
public void method() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.method(HttpMethod.DELETE, url).build();
ClientRequest<Void> result = ClientRequest.method(DELETE, url).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.DELETE, result.method());
}
@Test
public void GET() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.GET(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.GET, result.method());
}
@Test
public void HEAD() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.HEAD(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.HEAD, result.method());
}
@Test
public void POST() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.POST(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.POST, result.method());
}
@Test
public void PUT() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.PUT(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.PUT, result.method());
}
@Test
public void PATCH() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.PATCH(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.PATCH, result.method());
}
@Test
public void DELETE() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.DELETE(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.DELETE, result.method());
}
@Test
public void OPTIONS() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.OPTIONS(url.toString()).build();
assertEquals(url, result.url());
assertEquals(HttpMethod.OPTIONS, result.method());
}
@Test
public void accept() throws Exception {
MediaType json = MediaType.APPLICATION_JSON;
ClientRequest<Void> result = ClientRequest.GET("http://example.com").accept(json).build();
assertEquals(Collections.singletonList(json), result.headers().getAccept());
}
@Test
public void acceptCharset() throws Exception {
Charset charset = Charset.defaultCharset();
ClientRequest<Void> result = ClientRequest.GET("http://example.com")
.acceptCharset(charset).build();
assertEquals(Collections.singletonList(charset), result.headers().getAcceptCharset());
}
@Test
public void ifModifiedSince() throws Exception {
ZonedDateTime now = ZonedDateTime.now();
ClientRequest<Void> result = ClientRequest.GET("http://example.com")
.ifModifiedSince(now).build();
assertEquals(now.toInstant().toEpochMilli()/1000, result.headers().getIfModifiedSince()/1000);
}
@Test
public void ifNoneMatch() throws Exception {
ClientRequest<Void> result = ClientRequest.GET("http://example.com")
.ifNoneMatch("\"v2.7\"", "\"v2.8\"").build();
assertEquals(Arrays.asList("\"v2.7\"", "\"v2.8\""), result.headers().getIfNoneMatch());
assertEquals(DELETE, result.method());
}
@Test
public void cookie() throws Exception {
ClientRequest<Void> result = ClientRequest.GET("http://example.com")
ClientRequest<Void> result = ClientRequest.method(GET, URI.create("http://example.com"))
.cookie("foo", "bar").build();
assertEquals("bar", result.cookies().getFirst("foo"));
}
@Test
public void build() throws Exception {
ClientRequest<Void> result = ClientRequest.GET("http://example.com")
ClientRequest<Void> result = ClientRequest.method(GET, URI.create("http://example.com"))
.header("MyKey", "MyValue")
.cookie("foo", "bar")
.build();
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/");
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
WebClientStrategies strategies = mock(WebClientStrategies.class);
result.writeTo(request, strategies).block();
@ -193,7 +104,7 @@ public class DefaultClientRequestBuilderTests { @@ -193,7 +104,7 @@ public class DefaultClientRequestBuilderTests {
return response.writeWith(Mono.just(buffer));
};
ClientRequest<String> result = ClientRequest.POST("http://example.com")
ClientRequest<String> result = ClientRequest.method(POST, URI.create("http://example.com"))
.body(inserter);
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
@ -202,7 +113,7 @@ public class DefaultClientRequestBuilderTests { @@ -202,7 +113,7 @@ public class DefaultClientRequestBuilderTests {
WebClientStrategies strategies = mock(WebClientStrategies.class);
when(strategies.messageWriters()).thenReturn(messageWriters::stream);
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/");
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
result.writeTo(request, strategies).block();
assertNotNull(request.getBody());
}

10
spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java

@ -16,15 +16,19 @@ @@ -16,15 +16,19 @@
package org.springframework.web.reactive.function.client;
import java.net.URI;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.http.HttpMethod.GET;
/**
* @author Arjen Poutsma
@ -33,7 +37,7 @@ public class ExchangeFilterFunctionsTests { @@ -33,7 +37,7 @@ public class ExchangeFilterFunctionsTests {
@Test
public void andThen() throws Exception {
ClientRequest<Void> request = ClientRequest.GET("http://example.com").build();
ClientRequest<Void> request = ClientRequest.method(GET, URI.create("http://example.com")).build();
ClientResponse response = mock(ClientResponse.class);
ExchangeFunction exchange = r -> Mono.just(response);
@ -63,7 +67,7 @@ public class ExchangeFilterFunctionsTests { @@ -63,7 +67,7 @@ public class ExchangeFilterFunctionsTests {
@Test
public void apply() throws Exception {
ClientRequest<Void> request = ClientRequest.GET("http://example.com").build();
ClientRequest<Void> request = ClientRequest.method(GET, URI.create("http://example.com")).build();
ClientResponse response = mock(ClientResponse.class);
ExchangeFunction exchange = r -> Mono.just(response);
@ -82,7 +86,7 @@ public class ExchangeFilterFunctionsTests { @@ -82,7 +86,7 @@ public class ExchangeFilterFunctionsTests {
@Test
public void basicAuthentication() throws Exception {
ClientRequest<Void> request = ClientRequest.GET("http://example.com").build();
ClientRequest<Void> request = ClientRequest.method(GET, URI.create("http://example.com")).build();
ClientResponse response = mock(ClientResponse.class);
ExchangeFunction exchange = r -> {

158
spring-web-reactive/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

@ -18,7 +18,6 @@ package org.springframework.web.reactive.function.client; @@ -18,7 +18,6 @@ package org.springframework.web.reactive.function.client;
import java.time.Duration;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
@ -36,39 +35,51 @@ import org.springframework.http.HttpStatus; @@ -36,39 +35,51 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.Pojo;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* {@link WebClient} integration tests with the {@code Flux} and {@code Mono} API.
* Integration tests using a {@link WebClient} through {@link WebClientOperations}.
*
* @author Brian Clozel
* @author Rossen Stoyanchev
*/
public class WebClientIntegrationTests {
private MockWebServer server;
private WebClient webClient;
private WebClientOperations operations;
@Before
public void setup() {
this.server = new MockWebServer();
this.webClient = WebClient.create(new ReactorClientHttpConnector());
WebClient webClient = WebClient.create(new ReactorClientHttpConnector());
UriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(this.server.url("/").toString());
this.operations = WebClientOperations.builder(webClient)
.uriBuilderFactory(uriBuilderFactory)
.build();
}
@After
public void tearDown() throws Exception {
this.server.shutdown();
}
@Test
public void headers() throws Exception {
HttpUrl baseUrl = server.url("/greeting?name=Spring");
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString()).build();
Mono<HttpHeaders> result = this.webClient
.exchange(request)
Mono<HttpHeaders> result = this.operations.get()
.uri("/greeting?name=Spring")
.exchange()
.map(response -> response.headers().asHttpHeaders());
StepVerifier.create(result)
@ -88,16 +99,13 @@ public class WebClientIntegrationTests { @@ -88,16 +99,13 @@ public class WebClientIntegrationTests {
@Test
public void plainText() throws Exception {
HttpUrl baseUrl = server.url("/greeting?name=Spring");
this.server.enqueue(new MockResponse().setBody("Hello Spring!"));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString())
Mono<String> result = this.operations.get()
.uri("/greeting?name=Spring")
.header("X-Test-Header", "testvalue")
.build();
Mono<String> result = this.webClient
.exchange(request)
.then(response -> response.body(toMono(String.class)));
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
@ -113,18 +121,15 @@ public class WebClientIntegrationTests { @@ -113,18 +121,15 @@ public class WebClientIntegrationTests {
@Test
public void jsonString() throws Exception {
HttpUrl baseUrl = server.url("/json");
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString())
Mono<String> result = this.operations.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.build();
Mono<String> result = this.webClient
.exchange(request)
.then(response -> response.body(toMono(String.class)));
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext(content)
@ -139,17 +144,14 @@ public class WebClientIntegrationTests { @@ -139,17 +144,14 @@ public class WebClientIntegrationTests {
@Test
public void jsonPojoMono() throws Exception {
HttpUrl baseUrl = server.url("/pojo");
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString())
Mono<Pojo> result = this.operations.get()
.uri("/pojo")
.accept(MediaType.APPLICATION_JSON)
.build();
Mono<Pojo> result = this.webClient
.exchange(request)
.then(response -> response.body(toMono(Pojo.class)));
.exchange()
.then(response -> response.bodyToMono(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertEquals("barbar", p.getBar()))
@ -164,17 +166,14 @@ public class WebClientIntegrationTests { @@ -164,17 +166,14 @@ public class WebClientIntegrationTests {
@Test
public void jsonPojoFlux() throws Exception {
HttpUrl baseUrl = server.url("/pojos");
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString())
Flux<Pojo> result = this.operations.get()
.uri("/pojos")
.accept(MediaType.APPLICATION_JSON)
.build();
Flux<Pojo> result = this.webClient
.exchange(request)
.flatMap(response -> response.body(toFlux(Pojo.class)));
.exchange()
.flatMap(response -> response.bodyToFlux(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertThat(p.getBar(), Matchers.is("bar1")))
@ -190,20 +189,16 @@ public class WebClientIntegrationTests { @@ -190,20 +189,16 @@ public class WebClientIntegrationTests {
@Test
public void postJsonPojo() throws Exception {
HttpUrl baseUrl = server.url("/pojo/capitalize");
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
Pojo spring = new Pojo("foofoo", "barbar");
ClientRequest<Pojo> request = ClientRequest.POST(baseUrl.toString())
Mono<Pojo> result = this.operations.post()
.uri("/pojo/capitalize")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(spring));
Mono<Pojo> result = this.webClient
.exchange(request)
.then(response -> response.body(BodyExtractors.toMono(Pojo.class)));
.exchange(fromObject(new Pojo("foofoo", "barbar")))
.then(response -> response.bodyToMono(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertEquals("BARBAR", p.getBar()))
@ -221,17 +216,14 @@ public class WebClientIntegrationTests { @@ -221,17 +216,14 @@ public class WebClientIntegrationTests {
@Test
public void cookies() throws Exception {
HttpUrl baseUrl = server.url("/test");
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "text/plain").setBody("test"));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString())
Mono<String> result = this.operations.get()
.uri("/test")
.cookie("testkey", "testvalue")
.build();
Mono<String> result = this.webClient
.exchange(request)
.then(response -> response.body(toMono(String.class)));
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("test")
@ -246,19 +238,13 @@ public class WebClientIntegrationTests { @@ -246,19 +238,13 @@ public class WebClientIntegrationTests {
@Test
public void notFound() throws Exception {
HttpUrl baseUrl = server.url("/greeting?name=Spring");
this.server.enqueue(new MockResponse().setResponseCode(404)
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString()).build();
Mono<ClientResponse> result = this.webClient
.exchange(request);
Mono<ClientResponse> result = this.operations.get().uri("/greeting?name=Spring").exchange();
StepVerifier.create(result)
.consumeNextWith(response -> {
assertEquals(HttpStatus.NOT_FOUND, response.statusCode());
})
.consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.statusCode()))
.expectComplete()
.verify(Duration.ofSeconds(3));
@ -270,21 +256,18 @@ public class WebClientIntegrationTests { @@ -270,21 +256,18 @@ public class WebClientIntegrationTests {
@Test
public void buildFilter() throws Exception {
HttpUrl baseUrl = server.url("/greeting?name=Spring");
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
ExchangeFilterFunction filter = (request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request)
.header("foo", "bar").build();
return next.exchange(filteredRequest);
};
WebClient filteredClient = WebClient.builder(new ReactorClientHttpConnector())
.filter(filter).build();
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString()).build();
WebClientOperations filteredClient = this.operations.filter(
(request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);
});
Mono<String> result = filteredClient.exchange(request)
.then(response -> response.body(toMono(String.class)));
Mono<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
@ -299,21 +282,18 @@ public class WebClientIntegrationTests { @@ -299,21 +282,18 @@ public class WebClientIntegrationTests {
@Test
public void filter() throws Exception {
HttpUrl baseUrl = server.url("/greeting?name=Spring");
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
ExchangeFilterFunction filter = (request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request)
.header("foo", "bar").build();
return next.exchange(filteredRequest);
};
WebClient client = WebClient.create(new ReactorClientHttpConnector());
WebClient filteredClient = client.filter(filter);
WebClientOperations filteredClient = this.operations.filter(
(request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);
});
ClientRequest<Void> request = ClientRequest.GET(baseUrl.toString()).build();
Mono<String> result = filteredClient.exchange(request)
.then(response -> response.body(toMono(String.class)));
Mono<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
@ -326,8 +306,4 @@ public class WebClientIntegrationTests { @@ -326,8 +306,4 @@ public class WebClientIntegrationTests {
}
@After
public void tearDown() throws Exception {
this.server.shutdown();
}
}

62
spring-web-reactive/src/test/java/org/springframework/web/reactive/function/server/SseHandlerFunctionIntegrationTests.java

@ -18,22 +18,24 @@ package org.springframework.web.reactive.function.server; @@ -18,22 +18,24 @@ package org.springframework.web.reactive.function.server;
import java.time.Duration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.junit.Before;
import org.junit.Test;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.ResolvableType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientOperations;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
import static org.springframework.web.reactive.function.BodyInserters.fromServerSentEvents;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@ -42,12 +44,15 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r @@ -42,12 +44,15 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
*/
public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIntegrationTests {
private WebClientOperations operations;
private WebClient webClient;
@Before
public void createWebClient() {
this.webClient = WebClient.create(new ReactorClientHttpConnector());
public void setup() throws Exception {
super.setup();
WebClient client = WebClient.create(new ReactorClientHttpConnector());
UriBuilderFactory factory = new DefaultUriBuilderFactory("http://localhost:" + this.port);
this.operations = WebClientOperations.builder(client).uriBuilderFactory(factory).build();
}
@Override
@ -60,13 +65,10 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn @@ -60,13 +65,10 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn
@Test
public void sseAsString() throws Exception {
ClientRequest<Void> request = ClientRequest
.GET("http://localhost:{port}/string", this.port)
.accept(TEXT_EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.exchange(request)
Flux<String> result = this.operations.get()
.uri("/string")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(String.class)));
StepVerifier.create(result)
@ -77,14 +79,10 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn @@ -77,14 +79,10 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn
}
@Test
public void sseAsPerson() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/person", this.port)
.accept(TEXT_EVENT_STREAM)
.build();
Flux<Person> result = this.webClient
.exchange(request)
Flux<Person> result = this.operations.get()
.uri("/person")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(Person.class)));
StepVerifier.create(result)
@ -96,16 +94,12 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn @@ -96,16 +94,12 @@ public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIn
@Test
public void sseAsEvent() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/event", this.port)
.accept(TEXT_EVENT_STREAM)
.build();
ResolvableType type = ResolvableType.forClassWithGenerics(ServerSentEvent.class, String.class);
Flux<ServerSentEvent<String>> result = this.webClient
.exchange(request)
.flatMap(response -> response.body(toFlux(type)));
Flux<ServerSentEvent<String>> result = this.operations.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(
forClassWithGenerics(ServerSentEvent.class, String.class))));
StepVerifier.create(result)
.consumeNextWith( event -> {

75
spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java

@ -18,14 +18,9 @@ package org.springframework.web.reactive.result.method.annotation; @@ -18,14 +18,9 @@ package org.springframework.web.reactive.result.method.annotation;
import java.time.Duration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.junit.Before;
import org.junit.Test;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -40,9 +35,17 @@ import org.springframework.web.bind.annotation.RequestMapping; @@ -40,9 +35,17 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.EnableWebReactive;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientOperations;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
/**
@ -52,14 +55,16 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { @@ -52,14 +55,16 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private AnnotationConfigApplicationContext wac;
private WebClient webClient;
private WebClientOperations operations;
@Override
@Before
public void setup() throws Exception {
super.setup();
this.webClient = WebClient.create(new ReactorClientHttpConnector());
WebClient client = WebClient.create(new ReactorClientHttpConnector());
UriBuilderFactory factory = new DefaultUriBuilderFactory("http://localhost:" + this.port + "/sse");
this.operations = WebClientOperations.builder(client).uriBuilderFactory(factory).build();
}
@ -74,14 +79,11 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { @@ -74,14 +79,11 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsString() throws Exception {
ClientRequest<Void> request = ClientRequest
.GET("http://localhost:{port}/sse/string", this.port)
.accept(TEXT_EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.exchange(request)
.flatMap(response -> response.body(toFlux(String.class)));
Flux<String> result = this.operations.get()
.uri("/string")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.bodyToFlux(String.class));
StepVerifier.create(result)
.expectNext("foo 0")
@ -91,15 +93,11 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { @@ -91,15 +93,11 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
}
@Test
public void sseAsPerson() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/person", this.port)
.accept(TEXT_EVENT_STREAM)
.build();
Flux<Person> result = this.webClient
.exchange(request)
.flatMap(response -> response.body(toFlux(Person.class)));
Flux<Person> result = this.operations.get()
.uri("/person")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.bodyToFlux(Person.class));
StepVerifier.create(result)
.expectNext(new Person("foo 0"))
@ -110,15 +108,11 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { @@ -110,15 +108,11 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsEvent() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/event", this.port)
.accept(TEXT_EVENT_STREAM)
.build();
ResolvableType type = ResolvableType.forClassWithGenerics(ServerSentEvent.class, String.class);
Flux<ServerSentEvent<String>> result = this.webClient
.exchange(request)
ResolvableType type = forClassWithGenerics(ServerSentEvent.class, String.class);
Flux<ServerSentEvent<String>> result = this.operations.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(type)));
StepVerifier.create(result)
@ -142,15 +136,12 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { @@ -142,15 +136,12 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsEventWithoutAcceptHeader() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/event", this.port)
Flux<ServerSentEvent<String>> result = this.operations.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.build();
Flux<ServerSentEvent<String>> result = this.webClient
.exchange(request)
.flatMap(response -> response.body(toFlux(ResolvableType.forClassWithGenerics(ServerSentEvent.class, String.class))));
.exchange()
.flatMap(response -> response.body(toFlux(
forClassWithGenerics(ServerSentEvent.class, String.class))));
StepVerifier.create(result)
.consumeNextWith( event -> {

14
spring-web/src/main/java/org/springframework/web/client/AsyncRestTemplate.java

@ -45,6 +45,7 @@ import org.springframework.util.Assert; @@ -45,6 +45,7 @@ import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureAdapter;
import org.springframework.web.util.AbstractUriTemplateHandler;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
/**
@ -163,9 +164,16 @@ public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements @@ -163,9 +164,16 @@ public class AsyncRestTemplate extends InterceptingAsyncHttpAccessor implements
*/
public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
UriTemplateHandler handler = this.syncTemplate.getUriTemplateHandler();
Assert.isInstanceOf(AbstractUriTemplateHandler.class, handler,
"Can only use this property in conjunction with a DefaultUriTemplateHandler");
((AbstractUriTemplateHandler) handler).setDefaultUriVariables(defaultUriVariables);
if (handler instanceof DefaultUriBuilderFactory) {
((DefaultUriBuilderFactory) handler).setDefaultUriVariables(defaultUriVariables);
}
else if (handler instanceof AbstractUriTemplateHandler) {
((AbstractUriTemplateHandler) handler).setDefaultUriVariables(defaultUriVariables);
}
else {
throw new IllegalArgumentException(
"This property is not supported with the configured UriTemplateHandler.");
}
}
/**

25
spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

@ -53,7 +53,7 @@ import org.springframework.http.converter.xml.SourceHttpMessageConverter; @@ -53,7 +53,7 @@ import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.util.AbstractUriTemplateHandler;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
/**
@ -149,7 +149,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -149,7 +149,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
private UriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
private UriTemplateHandler uriTemplateHandler = new DefaultUriBuilderFactory();
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
@ -254,24 +254,31 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -254,24 +254,31 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
/**
* Configure default URI variable values. This is a shortcut for:
* <pre class="code">
* DefaultUriTemplateHandler handler = new DefaultUriTemplateHandler();
* DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
* handler.setDefaultUriVariables(...);
*
* RestTemplate restTemplate = new RestTemplate();
* restTemplate.setUriTemplateHandler(handler);
* </pre>
* @param defaultUriVariables the default URI variable values
* @param uriVars the default URI variable values
* @since 4.3
*/
public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
Assert.isInstanceOf(AbstractUriTemplateHandler.class, this.uriTemplateHandler,
"Can only use this property in conjunction with an AbstractUriTemplateHandler");
((AbstractUriTemplateHandler) this.uriTemplateHandler).setDefaultUriVariables(defaultUriVariables);
public void setDefaultUriVariables(Map<String, ?> uriVars) {
if (this.uriTemplateHandler instanceof DefaultUriBuilderFactory) {
((DefaultUriBuilderFactory) this.uriTemplateHandler).setDefaultUriVariables(uriVars);
}
else if (this.uriTemplateHandler instanceof AbstractUriTemplateHandler) {
((AbstractUriTemplateHandler) this.uriTemplateHandler).setDefaultUriVariables(uriVars);
}
else {
throw new IllegalArgumentException(
"This property is not supported with the configured UriTemplateHandler.");
}
}
/**
* Configure the {@link UriTemplateHandler} to use to expand URI templates.
* By default the {@link DefaultUriTemplateHandler} is used which relies on
* By default the {@link DefaultUriBuilderFactory} is used which relies on
* Spring's URI template support and exposes several useful properties that
* customize its behavior for encoding and for prepending a common base URL.
* An alternative implementation may be used to plug an external URI

2
spring-web/src/main/java/org/springframework/web/util/AbstractUriTemplateHandler.java

@ -33,7 +33,9 @@ import org.springframework.util.Assert; @@ -33,7 +33,9 @@ import org.springframework.util.Assert;
*
* @author Rossen Stoyanchev
* @since 4.3
* @deprecated as of 5.0 in favor of {@link DefaultUriBuilderFactory}
*/
@Deprecated
public abstract class AbstractUriTemplateHandler implements UriTemplateHandler {
private String baseUrl;

299
spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java

@ -0,0 +1,299 @@ @@ -0,0 +1,299 @@
/*
* 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.util;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/**
* Default implementation of {@link UriBuilderFactory} using
* {@link UriComponentsBuilder} for building, encoding, and expanding URI
* templates.
*
* <p>Exposes configuration properties that customize the creation of all URIs
* built through this factory instance including a base URI, default URI
* variables, and an encoding mode.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class DefaultUriBuilderFactory implements UriBuilderFactory {
public enum EncodingMode {URI_COMPONENT, VALUES_ONLY, NONE };
private final UriComponentsBuilder baseUri;
private final Map<String, Object> defaultUriVariables = new HashMap<>();
private EncodingMode encodingMode = EncodingMode.URI_COMPONENT;
/**
* Default constructor without a base URI.
*/
public DefaultUriBuilderFactory() {
this(UriComponentsBuilder.fromPath(null));
}
/**
* Constructor with a String "base URI".
* <p>The String given here is used to create a single "base"
* {@code UriComponentsBuilder}. Each time a new URI is prepared via
* {@link #uriString(String)} a new {@code UriComponentsBuilder} is created and
* merged with a clone of the "base" {@code UriComponentsBuilder}.
* <p>Note that the base URI may contain any or all components of a URI and
* those will apply to every URI.
*/
public DefaultUriBuilderFactory(String baseUri) {
this(UriComponentsBuilder.fromUriString(baseUri));
}
/**
* Alternate constructor with a {@code UriComponentsBuilder} as the base URI.
*/
public DefaultUriBuilderFactory(UriComponentsBuilder baseUri) {
Assert.notNull(baseUri, "'baseUri' is required.");
this.baseUri = baseUri;
}
/**
* Configure default URI variable values to use when expanding a URI with a
* Map of values. The map supplied when expanding a given URI can override
* default values.
* @param defaultUriVariables the default URI variables
*/
public void setDefaultUriVariables(Map<String, ?> defaultUriVariables) {
this.defaultUriVariables.clear();
if (defaultUriVariables != null) {
this.defaultUriVariables.putAll(defaultUriVariables);
}
}
/**
* Return the configured default URI variable values.
*/
public Map<String, ?> getDefaultUriVariables() {
return Collections.unmodifiableMap(this.defaultUriVariables);
}
/**
* Specify the encoding mode to use when building URIs:
* <ul>
* <li>URI_COMPONENT -- expand the URI variables first and then encode all URI
* component (e.g. host, path, query, etc) according to the encoding rules
* for each individual component.
* <li>VALUES_ONLY -- encode URI variable values only, prior to expanding
* them, using a "strict" encoding mode, i.e. encoding all characters
* outside the unreserved set as defined in
* <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>.
* This ensures a URI variable value will not contain any characters with a
* reserved purpose.
* <li>NONE -- in this mode no encoding is performed.
* </ul>
* <p>By default this is set to {@code "URI_COMPONENT"}.
* @param encodingMode the encoding mode to use
*/
public void setEncodingMode(EncodingMode encodingMode) {
this.encodingMode = encodingMode;
}
/**
* Return the configured encoding mode.
*/
public EncodingMode getEncodingMode() {
return this.encodingMode;
}
// UriTemplateHandler
public URI expand(String uriTemplate, Map<String, ?> uriVars) {
return uriString(uriTemplate).build(uriVars);
}
public URI expand(String uriTemplate, Object... uriVars) {
return uriString(uriTemplate).build(uriVars);
}
// UriBuilderFactory
public UriBuilder uriString(String uriTemplate) {
return new DefaultUriBuilder(uriTemplate);
}
/**
* {@link DefaultUriBuilderFactory} specific implementation of UriBuilder.
*/
private class DefaultUriBuilder implements UriBuilder {
private final UriComponentsBuilder uriComponentsBuilder;
public DefaultUriBuilder(String uriTemplate) {
this.uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
}
private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
// Merge base URI with child URI template
UriComponentsBuilder result = baseUri.cloneBuilder();
UriComponents child = UriComponentsBuilder.fromUriString(uriTemplate).build();
result.uriComponents(child);
// Split path into path segments
List<String> pathList = result.build().getPathSegments();
String[] pathArray = pathList.toArray(new String[pathList.size()]);
result.replacePath(null);
result.pathSegment(pathArray);
return result;
}
@Override
public DefaultUriBuilder scheme(String scheme) {
this.uriComponentsBuilder.scheme(scheme);
return this;
}
@Override
public DefaultUriBuilder userInfo(String userInfo) {
this.uriComponentsBuilder.userInfo(userInfo);
return this;
}
@Override
public DefaultUriBuilder host(String host) {
this.uriComponentsBuilder.host(host);
return this;
}
@Override
public DefaultUriBuilder port(int port) {
this.uriComponentsBuilder.port(port);
return this;
}
@Override
public DefaultUriBuilder port(String port) {
this.uriComponentsBuilder.port(port);
return this;
}
@Override
public DefaultUriBuilder path(String path) {
this.uriComponentsBuilder.path(path);
return this;
}
@Override
public DefaultUriBuilder replacePath(String path) {
this.uriComponentsBuilder.replacePath(path);
return this;
}
@Override
public DefaultUriBuilder pathSegment(String... pathSegments) {
this.uriComponentsBuilder.pathSegment(pathSegments);
return this;
}
@Override
public DefaultUriBuilder query(String query) {
this.uriComponentsBuilder.query(query);
return this;
}
@Override
public DefaultUriBuilder replaceQuery(String query) {
this.uriComponentsBuilder.replaceQuery(query);
return this;
}
@Override
public DefaultUriBuilder queryParam(String name, Object... values) {
this.uriComponentsBuilder.queryParam(name, values);
return this;
}
@Override
public DefaultUriBuilder replaceQueryParam(String name, Object... values) {
this.uriComponentsBuilder.replaceQueryParam(name, values);
return this;
}
@Override
public DefaultUriBuilder queryParams(MultiValueMap<String, String> params) {
this.uriComponentsBuilder.queryParams(params);
return this;
}
@Override
public DefaultUriBuilder replaceQueryParams(MultiValueMap<String, String> params) {
this.uriComponentsBuilder.replaceQueryParams(params);
return this;
}
@Override
public DefaultUriBuilder fragment(String fragment) {
this.uriComponentsBuilder.fragment(fragment);
return this;
}
@Override
public URI build(Map<String, ?> uriVars) {
if (!defaultUriVariables.isEmpty()) {
Map<String, Object> map = new HashMap<>();
map.putAll(defaultUriVariables);
map.putAll(uriVars);
uriVars = map;
}
if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
uriVars = UriUtils.encodeUriVariables(uriVars);
}
UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uriComponents = uriComponents.encode();
}
return URI.create(uriComponents.toString());
}
@Override
public URI build(Object... uriVars) {
if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) {
return build(Collections.emptyMap());
}
if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
uriVars = UriUtils.encodeUriVariables(uriVars);
}
UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uriComponents = uriComponents.encode();
}
return URI.create(uriComponents.toString());
}
}
}

2
spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java

@ -33,7 +33,9 @@ import java.util.Map; @@ -33,7 +33,9 @@ import java.util.Map;
*
* @author Rossen Stoyanchev
* @since 4.2
* @deprecated as of 5.0 in favor of {@link DefaultUriBuilderFactory}
*/
@Deprecated
public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler {
private boolean parsePath;

30
spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java

@ -446,14 +446,28 @@ final class HierarchicalUriComponents extends UriComponents { @@ -446,14 +446,28 @@ final class HierarchicalUriComponents extends UriComponents {
@Override
protected void copyToUriComponentsBuilder(UriComponentsBuilder builder) {
builder.scheme(getScheme());
builder.userInfo(getUserInfo());
builder.host(getHost());
builder.port(getPort());
builder.replacePath("");
this.path.copyToUriComponentsBuilder(builder);
builder.replaceQueryParams(getQueryParams());
builder.fragment(getFragment());
if (getScheme() != null) {
builder.scheme(getScheme());
}
if (getUserInfo() != null) {
builder.userInfo(getUserInfo());
}
if (getHost() != null) {
builder.host(getHost());
}
// Avoid parsing the port, may have URI variable..
if (this.port != null) {
builder.port(this.port);
}
if (getPath() != null) {
this.path.copyToUriComponentsBuilder(builder);
}
if (!getQueryParams().isEmpty()) {
builder.queryParams(getQueryParams());
}
if (getFragment() != null) {
builder.fragment(getFragment());
}
}

12
spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java

@ -137,9 +137,15 @@ final class OpaqueUriComponents extends UriComponents { @@ -137,9 +137,15 @@ final class OpaqueUriComponents extends UriComponents {
@Override
protected void copyToUriComponentsBuilder(UriComponentsBuilder builder) {
builder.scheme(getScheme());
builder.schemeSpecificPart(getSchemeSpecificPart());
builder.fragment(getFragment());
if (getScheme() != null) {
builder.scheme(getScheme());
}
if (getSchemeSpecificPart() != null) {
builder.schemeSpecificPart(getSchemeSpecificPart());
}
if (getFragment() != null) {
builder.fragment(getFragment());
}
}

171
spring-web/src/main/java/org/springframework/web/util/UriBuilder.java

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
/*
* 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,
* WITHOUUriBuilder 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.util;
import java.net.URI;
import java.util.Map;
import org.springframework.util.MultiValueMap;
/**
* Builder-style methods to prepare and expand a URI template with variables.
*
* <p>Effectively a generalization of {@link UriComponentsBuilder} but with
* shortcuts to expand directly into {@link URI} rather than
* {@link UriComponents} and also leaving common concerns such as encoding
* preferences, a base URI, and others as implementation concerns.
*
* <p>Typically obtained via {@link UriBuilderFactory} which serves as a central
* component configured once and used to create many URLs.
*
* @author Rossen Stoyanchev
* @since 5.0
* @see UriBuilderFactory
* @see UriComponentsBuilder
*/
public interface UriBuilder {
/**
* Set the URI scheme which may contain URI template variables,
* and may also be {@code null} to clear the scheme of this builder.
* @param scheme the URI scheme
*/
UriBuilder scheme(String scheme);
/**
* Set the URI user info which may contain URI template variables, and
* may also be {@code null} to clear the user info of this builder.
* @param userInfo the URI user info
*/
UriBuilder userInfo(String userInfo);
/**
* Set the URI host which may contain URI template variables, and may also
* be {@code null} to clear the host of this builder.
* @param host the URI host
*/
UriBuilder host(String host);
/**
* Set the URI port. Passing {@code -1} will clear the port of this builder.
* @param port the URI port
*/
UriBuilder port(int port);
/**
* Set the URI port . Use this method only when the port needs to be
* parameterized with a URI variable. Otherwise use {@link #port(int)}.
* Passing {@code null} will clear the port of this builder.
* @param port the URI port
*/
UriBuilder port(String port);
/**
* Append the given path to the existing path of this builder.
* The given path may contain URI template variables.
* @param path the URI path
*/
UriBuilder path(String path);
/**
* Set the path of this builder overriding the existing path values.
* @param path the URI path or {@code null} for an empty path.
*/
UriBuilder replacePath(String path);
/**
* Append path segments to the existing path. Each path segment may contain
* URI template variables and should not contain any slashes.
* Use {@code path("/")} subsequently to ensure a trailing slash.
* @param pathSegments the URI path segments
*/
UriBuilder pathSegment(String... pathSegments) throws IllegalArgumentException;
/**
* Append the given query to the existing query of this builder.
* The given query may contain URI template variables.
* <p><strong>Note:</strong> The presence of reserved characters can prevent
* correct parsing of the URI string. For example if a query parameter
* contains {@code '='} or {@code '&'} characters, the query string cannot
* be parsed unambiguously. Such values should be substituted for URI
* variables to enable correct parsing:
* <pre class="code">
* builder.query(&quot;filter={value}&quot;).uriString(&quot;hot&amp;cold&quot;);
* </pre>
* @param query the query string
*/
UriBuilder query(String query);
/**
* Set the query of this builder overriding all existing query parameters.
* @param query the query string or {@code null} to remove all query params
*/
UriBuilder replaceQuery(String query);
/**
* Append the given query parameter to the existing query parameters. The
* given name or any of the values may contain URI template variables. If no
* values are given, the resulting URI will contain the query parameter name
* only (i.e. {@code ?foo} instead of {@code ?foo=bar}.
* @param name the query parameter name
* @param values the query parameter values
*/
UriBuilder queryParam(String name, Object... values);
/**
* Add the given query parameters.
* @param params the params
*/
UriBuilder queryParams(MultiValueMap<String, String> params);
/**
* Set the query parameter values overriding all existing query values for
* the same parameter. If no values are given, the query parameter is removed.
* @param name the query parameter name
* @param values the query parameter values
*/
UriBuilder replaceQueryParam(String name, Object... values);
/**
* Set the query parameter values overriding all existing query values.
* @param params the query parameter name
*/
UriBuilder replaceQueryParams(MultiValueMap<String, String> params);
/**
* Set the URI fragment. The given fragment may contain URI template variables,
* and may also be {@code null} to clear the fragment of this builder.
* @param fragment the URI fragment
*/
UriBuilder fragment(String fragment);
/**
* Build a {@link URI} instance and replaces URI template variables
* with the values from an array.
* @param uriVariables the map of URI variables
* @return the URI
*/
URI build(Object... uriVariables);
/**
* Build a {@link URI} instance and replaces URI template variables
* with the values from a map.
* @param uriVariables the map of URI variables
* @return the URI
*/
URI build(Map<String, ?> uriVariables);
}

43
spring-web/src/main/java/org/springframework/web/util/UriBuilderFactory.java

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
/*
* 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.util;
/**
* Factory for instances of {@link UriBuilder}.
*
* <p>A single {@link UriBuilderFactory} may be created once, configured with
* common properties such as a base URI, and then used to create many URIs.
*
* <p>Extends {@link UriTemplateHandler} which has a similar purpose but only
* provides shortcuts for expanding URI templates, not builder style methods.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public interface UriBuilderFactory extends UriTemplateHandler {
/**
* Return a builder that is initialized with the given URI string which may
* be a URI template and represent full URI or just a path.
* <p>Depending on the factory implementation and configuration, the builder
* may merge the given URI string with a base URI and apply other operations.
* Refer to the specific factory implementation for details.
* @param uriTemplate the URI template to create the builder with
* @return the UriBuilder
*/
UriBuilder uriString(String uriTemplate);
}

51
spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

@ -57,7 +57,7 @@ import org.springframework.web.util.HierarchicalUriComponents.PathComponent; @@ -57,7 +57,7 @@ import org.springframework.web.util.HierarchicalUriComponents.PathComponent;
* @see #fromPath(String)
* @see #fromUri(URI)
*/
public class UriComponentsBuilder implements Cloneable {
public class UriComponentsBuilder implements UriBuilder, Cloneable {
private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
@ -360,6 +360,30 @@ public class UriComponentsBuilder implements Cloneable { @@ -360,6 +360,30 @@ public class UriComponentsBuilder implements Cloneable {
return build(false).expand(uriVariableValues);
}
/**
* Build a {@link URI} instance and replaces URI template variables
* with the values from an array.
* @param uriVariables the map of URI variables
* @return the URI
*/
@Override
public URI build(Object... uriVariables) {
return buildAndExpand(uriVariables).encode().toUri();
}
/**
* Build a {@link URI} instance and replaces URI template variables
* with the values from a map.
* @param uriVariables the map of URI variables
* @return the URI
*/
@Override
public URI build(Map<String, ?> uriVariables) {
return buildAndExpand(uriVariables).encode().toUri();
}
/**
* Build a URI String. This is a shortcut method which combines calls
* to {@link #build()}, then {@link UriComponents#encode()} and finally
@ -372,10 +396,10 @@ public class UriComponentsBuilder implements Cloneable { @@ -372,10 +396,10 @@ public class UriComponentsBuilder implements Cloneable {
}
// URI components methods
// Instance methods
/**
* Initialize all components of this URI builder with the components of the given URI.
* Initialize components of this builder from components of the given URI.
* @param uri the URI
* @return this UriComponentsBuilder
*/
@ -412,24 +436,25 @@ public class UriComponentsBuilder implements Cloneable { @@ -412,24 +436,25 @@ public class UriComponentsBuilder implements Cloneable {
}
/**
* Set the URI scheme. The given scheme may contain URI template variables,
* and may also be {@code null} to clear the scheme of this builder.
* @param scheme the URI scheme
* Initialize components of this {@link UriComponentsBuilder} from the
* components of the given {@link UriComponents}.
* @param uriComponents the UriComponents instance
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder scheme(String scheme) {
this.scheme = scheme;
public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
Assert.notNull(uriComponents, "UriComponents must not be null");
uriComponents.copyToUriComponentsBuilder(this);
return this;
}
/**
* Set all components of this URI builder from the given {@link UriComponents}.
* @param uriComponents the UriComponents instance
* Set the URI scheme. The given scheme may contain URI template variables,
* and may also be {@code null} to clear the scheme of this builder.
* @param scheme the URI scheme
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
Assert.notNull(uriComponents, "UriComponents must not be null");
uriComponents.copyToUriComponentsBuilder(this);
public UriComponentsBuilder scheme(String scheme) {
this.scheme = scheme;
return this;
}

12
spring-web/src/main/java/org/springframework/web/util/UriTemplateHandler.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* 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.
@ -20,18 +20,14 @@ import java.net.URI; @@ -20,18 +20,14 @@ import java.net.URI;
import java.util.Map;
/**
* Strategy for expanding a URI template with full control over the URI template
* syntax and the encoding of variables. Also a convenient central point for
* pre-processing all URI templates for example to insert a common base path.
* Strategy for expanding a URI template.
*
* <p>Supported as a property on the {@code RestTemplate} as well as the
* {@code AsyncRestTemplate}. The {@link DefaultUriTemplateHandler} is built
* on Spring's URI template support via {@link UriComponentsBuilder}. An
* alternative implementation may be used to plug external URI template libraries.
* {@code AsyncRestTemplate}.
*
* @author Rossen Stoyanchev
* @since 4.2
* @see org.springframework.web.client.RestTemplate#setUriTemplateHandler
* @see DefaultUriBuilderFactory
*/
public interface UriTemplateHandler {

7
spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java

@ -42,7 +42,7 @@ import org.springframework.http.client.ClientHttpRequestFactory; @@ -42,7 +42,7 @@ import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.util.DefaultUriTemplateHandler;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@ -83,7 +83,7 @@ public class RestTemplateTests { @@ -83,7 +83,7 @@ public class RestTemplateTests {
response = mock(ClientHttpResponse.class);
errorHandler = mock(ResponseErrorHandler.class);
converter = mock(HttpMessageConverter.class);
template = new RestTemplate(Collections.<HttpMessageConverter<?>>singletonList(converter));
template = new RestTemplate(Collections.singletonList(converter));
template.setRequestFactory(requestFactory);
template.setErrorHandler(errorHandler);
}
@ -273,8 +273,7 @@ public class RestTemplateTests { @@ -273,8 +273,7 @@ public class RestTemplateTests {
@Test
public void getForObjectWithCustomUriTemplateHandler() throws Exception {
DefaultUriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler();
uriTemplateHandler.setParsePath(true);
DefaultUriBuilderFactory uriTemplateHandler = new DefaultUriBuilderFactory();
template.setUriTemplateHandler(uriTemplateHandler);
URI expectedUri = new URI("http://example.com/hotels/1/pic/pics%2Flogo.png/size/150x150");

136
spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java

@ -0,0 +1,136 @@ @@ -0,0 +1,136 @@
/*
* 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.util;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
import static java.util.Collections.singletonMap;
import static junit.framework.TestCase.assertEquals;
/**
* Unit tests for {@link DefaultUriBuilderFactory}.
* @author Rossen Stoyanchev
*/
public class DefaultUriBuilderFactoryTests {
@Test
public void defaultSettings() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
URI uri = factory.uriString("/foo").pathSegment("{id}").build("a/b");
assertEquals("/foo/a%2Fb", uri.toString());
}
@Test
public void baseUri() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/bar?id=123");
URI uri = factory.uriString("/baz").port(8080).build();
assertEquals("http://foo.com:8080/bar/baz?id=123", uri.toString());
}
@Test
public void baseUriWithPathOverride() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://foo.com/bar");
URI uri = factory.uriString("").replacePath("/baz").build();
assertEquals("http://foo.com/baz", uri.toString());
}
@Test
public void defaultUriVars() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar");
factory.setDefaultUriVariables(singletonMap("host", "foo.com"));
URI uri = factory.uriString("/{id}").build(singletonMap("id", "123"));
assertEquals("http://foo.com/bar/123", uri.toString());
}
@Test
public void defaultUriVarsWithOverride() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar");
factory.setDefaultUriVariables(singletonMap("host", "spring.io"));
URI uri = factory.uriString("/baz").build(singletonMap("host", "docs.spring.io"));
assertEquals("http://docs.spring.io/bar/baz", uri.toString());
}
@Test
public void defaultUriVarsWithEmptyVarArg() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://{host}/bar");
factory.setDefaultUriVariables(singletonMap("host", "foo.com"));
URI uri = factory.uriString("/baz").build();
assertEquals("Expected delegation to build(Map) method", "http://foo.com/bar/baz", uri.toString());
}
@Test
public void defaultUriVarsSpr14147() throws Exception {
Map<String, String> defaultUriVars = new HashMap<>(2);
defaultUriVars.put("host", "api.example.com");
defaultUriVars.put("port", "443");
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setDefaultUriVariables(defaultUriVars);
URI uri = factory.expand("https://{host}:{port}/v42/customers/{id}", singletonMap("id", 123L));
assertEquals("https://api.example.com:443/v42/customers/123", uri.toString());
}
@Test
public void encodingValuesOnly() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
UriBuilder uriBuilder = factory.uriString("/foo/a%2Fb/{id}");
String id = "c/d";
String expected = "/foo/a%2Fb/c%2Fd";
assertEquals(expected, uriBuilder.build(id).toString());
assertEquals(expected, uriBuilder.build(singletonMap("id", id)).toString());
}
@Test
public void encodingValuesOnlySpr14147() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
factory.setDefaultUriVariables(singletonMap("host", "www.example.com"));
UriBuilder uriBuilder = factory.uriString("http://{host}/user/{userId}/dashboard");
assertEquals("http://www.example.com/user/john%3Bdoe/dashboard",
uriBuilder.build(singletonMap("userId", "john;doe")).toString());
}
@Test
public void encodingNone() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
factory.setEncodingMode(EncodingMode.NONE);
UriBuilder uriBuilder = factory.uriString("/foo/a%2Fb/{id}");
String id = "c%2Fd";
String expected = "/foo/a%2Fb/c%2Fd";
assertEquals(expected, uriBuilder.build(id).toString());
assertEquals(expected, uriBuilder.build(singletonMap("id", id)).toString());
}
@Test
public void initialPathSplitIntoPathSegments() throws Exception {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}");
URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d");
assertEquals("/foo/a%2Fb/baz/c%2Fd", uri.toString());
}
}
Loading…
Cancel
Save