diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index 51a629b22a5..6b62e7222b8 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java @@ -24,13 +24,10 @@ import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Map; import java.util.function.Consumer; -import java.util.function.Function; import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; -import org.springframework.web.util.DefaultUriBuilderFactory; -import org.springframework.web.util.UriTemplateHandler; /** * Extension of {@link HttpEntity} that also exposes the HTTP method and the @@ -69,17 +66,14 @@ import org.springframework.web.util.UriTemplateHandler; */ public class RequestEntity extends HttpEntity { - private final static UriTemplateHandler DEFAULT_TEMPLATE_HANDLER = new DefaultUriBuilderFactory(); - @Nullable private final HttpMethod method; - private final Function uriFunction; + private final URI url; @Nullable private final Type type; - /** * Constructor with method and URL but without body nor headers. * @param method the method @@ -146,19 +140,9 @@ public class RequestEntity extends HttpEntity { public RequestEntity(@Nullable T body, @Nullable MultiValueMap headers, @Nullable HttpMethod method, URI url, @Nullable Type type) { - this(body, headers, method, handler -> url, type); - } - - /** - * Private constructor with URI function. - * @since 5.3 - */ - private RequestEntity(@Nullable T body, @Nullable MultiValueMap headers, - @Nullable HttpMethod method, Function uriFunction, @Nullable Type type) { - super(body, headers); this.method = method; - this.uriFunction = uriFunction; + this.url = url; this.type = type; } @@ -174,24 +158,9 @@ public class RequestEntity extends HttpEntity { /** * Return the URL of the request. - *

If the URL was provided as a URI template, the returned URI is expanded - * and encoded with {@link DefaultUriBuilderFactory}. - * @return the URL as a {@code URI} */ public URI getUrl() { - return this.uriFunction.apply(DEFAULT_TEMPLATE_HANDLER); - } - - /** - * Return the URL of the request. - *

If the URL was provided as a URI template, the returned URI is expanded - * with the given {@link DefaultUriBuilderFactory}. - * @param templateHandler the handler to use to expand the URI template with - * @return the URL as a {@code URI} - * @since 5.3 - */ - public URI getUrl(UriTemplateHandler templateHandler) { - return this.uriFunction.apply(templateHandler); + return this.url; } @@ -235,13 +204,15 @@ public class RequestEntity extends HttpEntity { @Override public String toString() { + return format(getMethod(), getUrl().toString(), getBody(), getHeaders()); + } + + static String format(@Nullable HttpMethod httpMethod, String url, @Nullable T body, HttpHeaders headers) { StringBuilder builder = new StringBuilder("<"); - builder.append(getMethod()); + builder.append(httpMethod); builder.append(' '); - builder.append(getUrl()); + builder.append(url); builder.append(','); - T body = getBody(); - HttpHeaders headers = getHeaders(); if (body != null) { builder.append(body); builder.append(','); @@ -563,24 +534,42 @@ public class RequestEntity extends HttpEntity { private final HttpMethod method; - private final Function uriFunction; - private final HttpHeaders headers = new HttpHeaders(); + @Nullable + private final URI uri; - public DefaultBodyBuilder(HttpMethod method, URI url) { + @Nullable + String uriTemplate; + + @Nullable + private Object[] uriVarsArray; + + @Nullable + Map uriVarsMap; + + DefaultBodyBuilder(HttpMethod method, URI url) { this.method = method; - this.uriFunction = handler -> url; + this.uri = url; + this.uriTemplate = null; + this.uriVarsArray = null; + this.uriVarsMap = null; } - public DefaultBodyBuilder(HttpMethod method, String uriTemplate, Object... uriVars) { + DefaultBodyBuilder(HttpMethod method, String uriTemplate, Object... uriVars) { this.method = method; - this.uriFunction = handler -> handler.expand(uriTemplate, uriVars); + this.uri = null; + this.uriTemplate = uriTemplate; + this.uriVarsArray = uriVars; + this.uriVarsMap = null; } - public DefaultBodyBuilder(HttpMethod method, String uriTemplate, Map uriVars) { + DefaultBodyBuilder(HttpMethod method, String uriTemplate, Map uriVars) { this.method = method; - this.uriFunction = handler -> handler.expand(uriTemplate, uriVars); + this.uri = null; + this.uriTemplate = uriTemplate; + this.uriVarsArray = null; + this.uriVarsMap = uriVars; } @Override @@ -655,17 +644,85 @@ public class RequestEntity extends HttpEntity { @Override public RequestEntity build() { - return new RequestEntity<>(null, this.headers, this.method, this.uriFunction, null); + return buildInternal(null, null); } @Override public RequestEntity body(T body) { - return new RequestEntity<>(body, this.headers, this.method, this.uriFunction, null); + return buildInternal(body, null); } @Override public RequestEntity body(T body, Type type) { - return new RequestEntity<>(body, this.headers, this.method, this.uriFunction, type); + return buildInternal(body, type); + } + + private RequestEntity buildInternal(@Nullable T body, @Nullable Type type) { + if (this.uri != null) { + return new RequestEntity<>(body, this.headers, this.method, this.uri, type); + } + else if (this.uriTemplate != null){ + return new UriTemplateRequestEntity<>(body, this.headers, this.method, type, + this.uriTemplate, this.uriVarsArray, this.uriVarsMap); + } + else { + throw new IllegalStateException("Neither URI nor URI template"); + } } } + + + /** + * RequestEntity initialized with a URI template and variables instead of a {@link URI}. + * @since 5.3 + * @param the body type + */ + public static class UriTemplateRequestEntity extends RequestEntity { + + String uriTemplate; + + @Nullable + private Object[] uriVarsArray; + + @Nullable + Map uriVarsMap; + + + UriTemplateRequestEntity( + @Nullable T body, @Nullable MultiValueMap headers, + @Nullable HttpMethod method, @Nullable Type type, String uriTemplate, + @Nullable Object[] uriVarsArray, @Nullable Map uriVarsMap) { + + super(body, headers, method, null, type); + this.uriTemplate = uriTemplate; + this.uriVarsArray = uriVarsArray; + this.uriVarsMap = uriVarsMap; + } + + + public String getUriTemplate() { + return this.uriTemplate; + } + + @Nullable + public Object[] getVars() { + return this.uriVarsArray; + } + + @Nullable + public Map getVarsMap() { + return this.uriVarsMap; + } + + @Override + public URI getUrl() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return format(getMethod(), getUriTemplate(), getBody(), getHeaders()); + } + } + } diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index ba3a912ce3e..8e058bfca03 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -633,24 +633,34 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat } @Override - public ResponseEntity exchange(RequestEntity requestEntity, Class responseType) + public ResponseEntity exchange(RequestEntity entity, Class responseType) throws RestClientException { - RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType); + RequestCallback requestCallback = httpEntityCallback(entity, responseType); ResponseExtractor> responseExtractor = responseEntityExtractor(responseType); - URI url = requestEntity.getUrl(this.uriTemplateHandler); - return nonNull(doExecute(url, requestEntity.getMethod(), requestCallback, responseExtractor)); + return nonNull(doExecute(resolveUrl(entity), entity.getMethod(), requestCallback, responseExtractor)); } @Override - public ResponseEntity exchange(RequestEntity requestEntity, ParameterizedTypeReference responseType) + public ResponseEntity exchange(RequestEntity entity, ParameterizedTypeReference responseType) throws RestClientException { Type type = responseType.getType(); - RequestCallback requestCallback = httpEntityCallback(requestEntity, type); + RequestCallback requestCallback = httpEntityCallback(entity, type); ResponseExtractor> responseExtractor = responseEntityExtractor(type); - URI url = requestEntity.getUrl(this.uriTemplateHandler); - return nonNull(doExecute(url, requestEntity.getMethod(), requestCallback, responseExtractor)); + return nonNull(doExecute(resolveUrl(entity), entity.getMethod(), requestCallback, responseExtractor)); + } + + private URI resolveUrl(RequestEntity entity) { + if (entity instanceof RequestEntity.UriTemplateRequestEntity) { + RequestEntity.UriTemplateRequestEntity ext = (RequestEntity.UriTemplateRequestEntity) entity; + return (ext.getVars() != null ? + this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVars()) : + this.uriTemplateHandler.expand(ext.getUriTemplate(), ext.getVarsMap())); + } + else { + return entity.getUrl(); + } } diff --git a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java index 69f1285674e..9790b1a11e1 100644 --- a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java @@ -82,16 +82,17 @@ public class RequestEntityTests { } @Test - public void uriExpansion() throws URISyntaxException{ + public void uriExpansion() { RequestEntity entity = RequestEntity.get("https://www.{host}.com/{path}", "example", "foo/bar").build(); - DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); - assertThat(entity.getUrl(factory)).isEqualTo(new URI("https://www.example.com/foo%2Fbar")); + assertThat(entity).isInstanceOf(RequestEntity.UriTemplateRequestEntity.class); + RequestEntity.UriTemplateRequestEntity ext = (RequestEntity.UriTemplateRequestEntity) entity; - factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); - assertThat(entity.getUrl(factory)).isEqualTo(new URI("https://www.example.com/foo/bar")); + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); + assertThat(ext.getUriTemplate()).isEqualTo("https://www.{host}.com/{path}"); + assertThat(ext.getVars()).containsExactly("example", "foo/bar"); }