Merge changes for SPR-15124
This commit is contained in:
@@ -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;
|
||||
*/
|
||||
public interface ClientRequest<T> {
|
||||
|
||||
// Instance methods
|
||||
|
||||
/**
|
||||
* Return the HTTP method.
|
||||
*/
|
||||
@@ -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> {
|
||||
* @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> {
|
||||
* @param url the URL
|
||||
* @return the created builder
|
||||
*/
|
||||
static BodyBuilder method(HttpMethod method, URI url) {
|
||||
static Builder 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);
|
||||
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
|
||||
* Defines a builder for a request.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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> {
|
||||
* @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> {
|
||||
* @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> {
|
||||
* @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> {
|
||||
* @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> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
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 {
|
||||
}
|
||||
|
||||
@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 {
|
||||
}
|
||||
|
||||
@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 {
|
||||
}
|
||||
|
||||
@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 {
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
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
|
||||
|
||||
@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
|
||||
|
||||
@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)
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
@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 {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 -> {
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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();
|
||||
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!")
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
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
|
||||
|
||||
@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
|
||||
}
|
||||
@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
|
||||
|
||||
@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 -> {
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
}
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 -> {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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("filter={value}").uriString("hot&cold");
|
||||
* </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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
*/
|
||||
@@ -411,6 +435,18 @@ public class UriComponentsBuilder implements Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize components of this {@link UriComponentsBuilder} from the
|
||||
* components of the given {@link UriComponents}.
|
||||
* @param uriComponents the UriComponents instance
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
|
||||
Assert.notNull(uriComponents, "UriComponents must not be null");
|
||||
uriComponents.copyToUriComponentsBuilder(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -422,17 +458,6 @@ public class UriComponentsBuilder implements Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all components of this URI builder from the given {@link UriComponents}.
|
||||
* @param uriComponents the UriComponents instance
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
|
||||
Assert.notNull(uriComponents, "UriComponents must not be null");
|
||||
uriComponents.copyToUriComponentsBuilder(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URI scheme-specific-part. When invoked, this method overwrites
|
||||
* {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
@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");
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user