From 22376c2efa68d800a2436463f43271353b341e61 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 10 Jul 2023 11:24:30 +0100 Subject: [PATCH] Polishing See gh-30117 --- .../ROOT/pages/integration/rest-clients.adoc | 8 +- .../client/support/RestTemplateAdapter.java | 65 +++++++-------- .../AbstractReactorHttpExchangeAdapter.java | 28 +++---- .../service/invoker/HttpClientAdapter.java | 42 +++++----- .../service/invoker/HttpExchangeAdapter.java | 14 ++-- .../service/invoker/HttpServiceMethod.java | 59 +++++++------ .../invoker/HttpServiceProxyFactory.java | 10 +-- .../invoker/ReactorHttpExchangeAdapter.java | 15 ++-- ...sts.java => RestTemplateAdapterTests.java} | 82 +++++++++---------- .../CookieValueArgumentResolverTests.java | 2 +- .../HttpMethodArgumentResolverTests.java | 2 +- .../NamedValueArgumentResolverTests.java | 2 +- .../PathVariableArgumentResolverTests.java | 2 +- ...RequestAttributeArgumentResolverTests.java | 2 +- .../RequestBodyArgumentResolverTests.java | 2 +- .../RequestHeaderArgumentResolverTests.java | 2 +- .../invoker/UrlArgumentResolverTests.java | 2 +- ...KotlinRestTemplateHttpServiceProxyTests.kt | 2 +- .../client/support/WebClientAdapter.java | 9 +- ...yTests.java => WebClientAdapterTests.java} | 24 +++--- .../WebClientHttpServiceProxyKotlinTests.kt | 6 +- 21 files changed, 185 insertions(+), 195 deletions(-) rename spring-web/src/test/java/org/springframework/web/client/support/{RestTemplateHttpServiceProxyTests.java => RestTemplateAdapterTests.java} (73%) rename spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/{WebClientHttpServiceProxyTests.java => WebClientAdapterTests.java} (90%) diff --git a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc index 0828d205fd7..f73d22e1a05 100644 --- a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc +++ b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc @@ -388,7 +388,8 @@ either using `WebClient`: [source,java,indent=0,subs="verbatim,quotes"] ---- WebClient client = WebClient.builder().baseUrl("https://api.github.com/").build(); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder().exchangeAdapter(WebClientAdapter.forClient(webClient)).build(); + WebClientAdapter adapter = WebClientAdapter.forClient(webClient) + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); RepositoryService service = factory.createClient(RepositoryService.class); ---- @@ -398,8 +399,9 @@ or using `RestTemplate`: [source,java,indent=0,subs="verbatim,quotes"] ---- RestTemplate restTemplate = new RestTemplate(); - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/")); - HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder().exchangeAdapter(RestTemplateAdapter.forTemplate(restTemplate)).build(); + restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory("https://api.github.com/")); + RestTemplateAdapter adapter = RestTemplateAdapter.forTemplate(restTemplate); + HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); RepositoryService service = factory.createClient(RepositoryService.class); ---- diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java b/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java index 496b9a133b2..214f3f339ab 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestTemplateAdapter.java @@ -17,8 +17,9 @@ package org.springframework.web.client.support; import java.net.URI; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; +import java.util.Map; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpCookie; @@ -27,19 +28,17 @@ import org.springframework.http.HttpMethod; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.service.invoker.HttpExchangeAdapter; import org.springframework.web.service.invoker.HttpRequestValues; import org.springframework.web.service.invoker.HttpServiceProxyFactory; /** - * An {@link HttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory} to use + * {@link HttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory} to use * {@link RestTemplate} for request execution. - *

- * Use static factory methods in this class to create an {@link HttpServiceProxyFactory} - * configured with a given {@link RestTemplate}. + * + *

Use static factory methods in this class to create an + * {@link HttpServiceProxyFactory} configured with a given {@link RestTemplate}. * * @author Olga Maciaszek-Sharma * @since 6.1 @@ -48,11 +47,17 @@ public final class RestTemplateAdapter implements HttpExchangeAdapter { private final RestTemplate restTemplate; - // Private constructor; use static factory methods to instantiate + private RestTemplateAdapter(RestTemplate restTemplate) { this.restTemplate = restTemplate; } + + @Override + public boolean supportsRequestAttributes() { + return false; + } + @Override public void exchange(HttpRequestValues requestValues) { this.restTemplate.exchange(newRequest(requestValues), Void.class); @@ -74,14 +79,10 @@ public final class RestTemplateAdapter implements HttpExchangeAdapter { } @Override - public ResponseEntity exchangeForEntity(HttpRequestValues requestValues, - ParameterizedTypeReference bodyType) { - return this.restTemplate.exchange(newRequest(requestValues), bodyType); - } + public ResponseEntity exchangeForEntity( + HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - @Override - public boolean supportsRequestAttributes() { - return false; + return this.restTemplate.exchange(newRequest(requestValues), bodyType); } private RequestEntity newRequest(HttpRequestValues requestValues) { @@ -90,8 +91,9 @@ public final class RestTemplateAdapter implements HttpExchangeAdapter { uri = requestValues.getUri(); } else if (requestValues.getUriTemplate() != null) { - uri = this.restTemplate.getUriTemplateHandler().expand(requestValues.getUriTemplate(), - requestValues.getUriVariables()); + String uriTemplate = requestValues.getUriTemplate(); + Map variables = requestValues.getUriVariables(); + uri = this.restTemplate.getUriTemplateHandler().expand(uriTemplate, variables); } else { throw new IllegalStateException("Neither full URL nor URI template"); @@ -100,35 +102,30 @@ public final class RestTemplateAdapter implements HttpExchangeAdapter { HttpMethod httpMethod = requestValues.getHttpMethod(); Assert.notNull(httpMethod, "HttpMethod is required"); - RequestEntity.BodyBuilder builder = RequestEntity.method(httpMethod, uri) - .headers(requestValues.getHeaders()); + RequestEntity.BodyBuilder builder = RequestEntity.method(httpMethod, uri); + builder.headers(requestValues.getHeaders()); if (!requestValues.getCookies().isEmpty()) { - MultiValueMap cookies = new LinkedMultiValueMap<>(); - requestValues.getCookies() - .forEach((name, values) -> values.forEach(value -> - cookies.add(name, new HttpCookie(name, value)))); - - builder.header(HttpHeaders.COOKIE, - cookies.values() - .stream() - .flatMap(List::stream) - .map(HttpCookie::toString) - .collect(Collectors.joining("; "))); + List cookies = new ArrayList<>(); + requestValues.getCookies().forEach((name, values) -> values.forEach(value -> { + HttpCookie cookie = new HttpCookie(name, value); + cookies.add(cookie.toString()); + })); + builder.header(HttpHeaders.COOKIE, String.join("; ", cookies)); } if (requestValues.getBodyValue() != null) { return builder.body(requestValues.getBodyValue()); } + return builder.build(); } + /** - * Create a {@link RestTemplateAdapter} for the given {@link RestTemplate} instance. - * @param restTemplate the {@link RestTemplate} to use - * @return the created adapter instance + * Create a {@link RestTemplateAdapter} with the given {@link RestTemplate}. */ - public static RestTemplateAdapter forTemplate(RestTemplate restTemplate) { + public static RestTemplateAdapter create(RestTemplate restTemplate) { return new RestTemplateAdapter(restTemplate); } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java b/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java index af9cd782a5f..653a2b262f6 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java @@ -29,10 +29,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * A base reactive adapter that implements both {@link HttpClientAdapter} - * and {@link HttpExchangeAdapter}. Allows to ensure backwards compatibility - * with the deprecated {@link HttpClientAdapter} and handles blocking from reactive - * publishers to objects where necessary. + * Convenient base class for a {@link ReactorHttpExchangeAdapter} implementation + * adapting to the synchronous {@link HttpExchangeAdapter} contract. * * @author Rossen Stoyanchev * @since 6.1 @@ -55,30 +53,21 @@ public abstract class AbstractReactorHttpExchangeAdapter /** - * Configure the registry for adapting various reactive types. - *

By default this is an instance of {@link ReactiveAdapterRegistry} with - * default settings. + * Configure the {@link ReactiveAdapterRegistry} to use. + *

By default, this is {@link ReactiveAdapterRegistry#getSharedInstance()}. */ public void setReactiveAdapterRegistry(ReactiveAdapterRegistry reactiveAdapterRegistry) { this.reactiveAdapterRegistry = reactiveAdapterRegistry; } - /** - * Return the configured reactive type registry of adapters. - */ @Override public ReactiveAdapterRegistry getReactiveAdapterRegistry() { return this.reactiveAdapterRegistry; } /** - * Configure how long to block for the response of an HTTP service method with a - * synchronous (blocking) method signature. - *

- * By default, this is not set, in which case the behavior depends on connection and - * request timeout settings of the underlying HTTP client. We recommend configuring - * timeout values directly on the underlying HTTP client, which provides more - * control over such settings. + * Configure how long to block for the response of an HTTP service method + * as described in {@link #getBlockTimeout()}. */ public void setBlockTimeout(@Nullable Duration blockTimeout) { this.blockTimeout = blockTimeout; @@ -90,6 +79,7 @@ public abstract class AbstractReactorHttpExchangeAdapter return this.blockTimeout; } + @Override public void exchange(HttpRequestValues requestValues) { if (this.blockTimeout != null) { @@ -126,7 +116,9 @@ public abstract class AbstractReactorHttpExchangeAdapter } @Override - public ResponseEntity exchangeForEntity(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { + public ResponseEntity exchangeForEntity( + HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { + ResponseEntity entity = (this.blockTimeout != null ? exchangeForEntityMono(requestValues, bodyType).block(this.blockTimeout) : exchangeForEntityMono(requestValues, bodyType).block()); diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java index 1b5aaf587b2..75eaeb36fbb 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java @@ -91,59 +91,55 @@ public interface HttpClientAdapter { /** - * Adapt this {@link HttpClientAdapter} to {@link ReactorHttpExchangeAdapter}. - * @return a {@link ReactorHttpExchangeAdapter} instance created that delegating to - * the underlying {@link HttpClientAdapter} implementation + * Adapt this instance to {@link ReactorHttpExchangeAdapter}. * @since 6.1 */ - default ReactorHttpExchangeAdapter asHttpExchangeAdapter() { - - HttpClientAdapter delegate = this; + default ReactorHttpExchangeAdapter asReactorExchangeAdapter() { return new AbstractReactorHttpExchangeAdapter() { @Override - public Mono exchangeForMono(HttpRequestValues requestValues) { - return delegate.requestToVoid(requestValues); + public boolean supportsRequestAttributes() { + return true; + } + + @Override + public Mono exchangeForMono(HttpRequestValues values) { + return HttpClientAdapter.this.requestToVoid(values); } @Override - public Mono exchangeForHeadersMono(HttpRequestValues requestValues) { - return delegate.requestToHeaders(requestValues); + public Mono exchangeForHeadersMono(HttpRequestValues values) { + return HttpClientAdapter.this.requestToHeaders(values); } @Override - public Mono exchangeForBodyMono(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - return delegate.requestToBody(requestValues, bodyType); + public Mono exchangeForBodyMono(HttpRequestValues values, ParameterizedTypeReference bodyType) { + return HttpClientAdapter.this.requestToBody(values, bodyType); } @Override - public Flux exchangeForBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - return delegate.requestToBodyFlux(requestValues, bodyType); + public Flux exchangeForBodyFlux(HttpRequestValues values, ParameterizedTypeReference bodyType) { + return HttpClientAdapter.this.requestToBodyFlux(values, bodyType); } @Override - public Mono> exchangeForBodilessEntityMono(HttpRequestValues requestValues) { - return delegate.requestToBodilessEntity(requestValues); + public Mono> exchangeForBodilessEntityMono(HttpRequestValues values) { + return HttpClientAdapter.this.requestToBodilessEntity(values); } @Override public Mono> exchangeForEntityMono( HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - return delegate.requestToEntity(requestValues, bodyType); + return HttpClientAdapter.this.requestToEntity(requestValues, bodyType); } @Override public Mono>> exchangeForEntityFlux( HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - return delegate.requestToEntityFlux(requestValues, bodyType); - } - - @Override - public boolean supportsRequestAttributes() { - return true; + return HttpClientAdapter.this.requestToEntityFlux(requestValues, bodyType); } }; } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpExchangeAdapter.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpExchangeAdapter.java index 20d995f1024..3333e27fbc7 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpExchangeAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpExchangeAdapter.java @@ -30,6 +30,11 @@ import org.springframework.lang.Nullable; */ public interface HttpExchangeAdapter { + /** + * Whether the underlying client supports use of request attributes. + */ + boolean supportsRequestAttributes(); + /** * Perform the given request, and release the response content, if any. * @param requestValues the request to perform @@ -49,7 +54,7 @@ public interface HttpExchangeAdapter { * @param requestValues the request to perform * @param bodyType the target type to decode to * @param the type the response is decoded to - * @return the decoded response. + * @return the decoded response body. */ @Nullable T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference bodyType); @@ -57,18 +62,15 @@ public interface HttpExchangeAdapter { /** * Variant of {@link #exchange(HttpRequestValues)} with additional * access to the response status and headers. + * @return the response entity with status and headers. */ ResponseEntity exchangeForBodilessEntity(HttpRequestValues requestValues); /** * Variant of {@link #exchangeForBody(HttpRequestValues, ParameterizedTypeReference)} * with additional access to the response status and headers. + * @return the response entity with status, headers, and body. */ ResponseEntity exchangeForEntity(HttpRequestValues requestValues, ParameterizedTypeReference bodyType); - /** - * A flag that indicates whether request attributes are supported by a specific client - * adapter. - */ - boolean supportsRequestAttributes(); } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java index f60b9c5ee2c..18fb67fc9b6 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java @@ -80,7 +80,10 @@ final class HttpServiceMethod { this.method = method; this.parameters = initMethodParameters(method); this.argumentResolvers = argumentResolvers; - this.requestValuesInitializer = HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver); + + this.requestValuesInitializer = + HttpRequestValuesInitializer.create(method, containingClass, embeddedValueResolver); + this.responseFunction = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter reactorAdapter ? ReactorExchangeResponseFunction.create(reactorAdapter, method) : @@ -291,52 +294,54 @@ final class HttpServiceMethod { return this.responseFunction.apply(requestValues); } + + /** + * Create the {@code ResponseFunction} that matches the method return type. + */ public static ResponseFunction create(HttpExchangeAdapter client, Method method) { if (KotlinDetector.isSuspendingFunction(method)) { - throw new IllegalStateException("Kotlin Coroutines are only supported with reactive implementations"); + throw new IllegalStateException( + "Kotlin Coroutines are only supported with reactive implementations"); } - MethodParameter actualReturnParam = new MethodParameter(method, -1).nestedIfOptional(); - boolean returnOptional = actualReturnParam.getParameterType().equals(Optional.class); - Class actualReturnType = actualReturnParam.getNestedParameterType(); + + MethodParameter param = new MethodParameter(method, -1).nestedIfOptional(); + Class paramType = param.getNestedParameterType(); Function responseFunction; - if (actualReturnType.equals(void.class) || actualReturnType.equals(Void.class)) { + if (paramType.equals(void.class) || paramType.equals(Void.class)) { responseFunction = requestValues -> { client.exchange(requestValues); return null; }; } - else if (actualReturnType.equals(HttpHeaders.class)) { - responseFunction = request -> processResponse(client.exchangeForHeaders(request), - returnOptional); + else if (paramType.equals(HttpHeaders.class)) { + responseFunction = request -> asOptionalIfNecessary(client.exchangeForHeaders(request), param); } - else if (actualReturnType.equals(ResponseEntity.class)) { - MethodParameter bodyParam = actualReturnParam.nested(); - Class bodyType = bodyParam.getNestedParameterType(); - if (bodyType.equals(Void.class)) { - responseFunction = request -> processResponse(client - .exchangeForBodilessEntity(request), returnOptional); + else if (paramType.equals(ResponseEntity.class)) { + MethodParameter bodyParam = param.nested(); + if (bodyParam.getNestedParameterType().equals(Void.class)) { + responseFunction = request -> + asOptionalIfNecessary(client.exchangeForBodilessEntity(request), param); } else { - ParameterizedTypeReference bodyTypeReference = ParameterizedTypeReference - .forType(bodyParam.getNestedGenericParameterType()); - responseFunction = request -> processResponse(client.exchangeForEntity(request, - bodyTypeReference), returnOptional); + ParameterizedTypeReference bodyTypeRef = + ParameterizedTypeReference.forType(bodyParam.getNestedGenericParameterType()); + responseFunction = request -> + asOptionalIfNecessary(client.exchangeForEntity(request, bodyTypeRef), param); } } else { - ParameterizedTypeReference bodyTypeReference = ParameterizedTypeReference - .forType(actualReturnParam.getNestedGenericParameterType()); - responseFunction = request -> processResponse(client.exchangeForBody(request, - bodyTypeReference), returnOptional); + ParameterizedTypeReference bodyTypeRef = + ParameterizedTypeReference.forType(param.getNestedGenericParameterType()); + responseFunction = request -> + asOptionalIfNecessary(client.exchangeForBody(request, bodyTypeRef), param); } return new ExchangeResponseFunction(responseFunction); } - private static @Nullable Object processResponse(@Nullable Object response, - boolean returnOptional) { - return returnOptional ? Optional.ofNullable(response) : response; + private static @Nullable Object asOptionalIfNecessary(@Nullable Object response, MethodParameter param) { + return param.getParameterType().equals(Optional.class) ? Optional.ofNullable(response) : response; } } @@ -372,7 +377,7 @@ final class HttpServiceMethod { /** - * Create the {@code ResponseFunction} that matches the method's return type. + * Create the {@code ResponseFunction} that matches the method return type. */ public static ResponseFunction create(ReactorHttpExchangeAdapter client, Method method) { MethodParameter returnParam = new MethodParameter(method, -1); diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java index bbf179307a5..6f45ec13d61 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java @@ -119,7 +119,7 @@ public final class HttpServiceProxyFactory { @SuppressWarnings("removal") @Deprecated(since = "6.1", forRemoval = true) public static Builder builder(HttpClientAdapter clientAdapter) { - return new Builder().exchangeAdapter(clientAdapter.asHttpExchangeAdapter()); + return new Builder().exchangeAdapter(clientAdapter.asReactorExchangeAdapter()); } /** @@ -169,7 +169,7 @@ public final class HttpServiceProxyFactory { @SuppressWarnings("removal") @Deprecated(since = "6.1", forRemoval = true) public Builder clientAdapter(HttpClientAdapter clientAdapter) { - this.exchangeAdapter = clientAdapter.asHttpExchangeAdapter(); + this.exchangeAdapter = clientAdapter.asReactorExchangeAdapter(); return this; } @@ -262,12 +262,12 @@ public final class HttpServiceProxyFactory { resolvers.add(new RequestHeaderArgumentResolver(service)); resolvers.add(new RequestBodyArgumentResolver()); resolvers.add(new PathVariableArgumentResolver(service)); - if (this.exchangeAdapter.supportsRequestAttributes()) { - resolvers.add(new RequestAttributeArgumentResolver()); - } resolvers.add(new RequestParamArgumentResolver(service)); resolvers.add(new RequestPartArgumentResolver()); resolvers.add(new CookieValueArgumentResolver(service)); + if (this.exchangeAdapter.supportsRequestAttributes()) { + resolvers.add(new RequestAttributeArgumentResolver()); + } // Specific type resolvers.add(new UrlArgumentResolver()); diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/ReactorHttpExchangeAdapter.java b/spring-web/src/main/java/org/springframework/web/service/invoker/ReactorHttpExchangeAdapter.java index bad465b1f4e..47f08906ba1 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/ReactorHttpExchangeAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/ReactorHttpExchangeAdapter.java @@ -37,19 +37,18 @@ import org.springframework.lang.Nullable; public interface ReactorHttpExchangeAdapter extends HttpExchangeAdapter { /** - * Return the configured reactive type registry of adapters. + * Return the configured {@link ReactiveAdapterRegistry}. */ ReactiveAdapterRegistry getReactiveAdapterRegistry(); /** - * Return the configured time to block for the response of an HTTP service method with - * a synchronous (blocking) method signature. + * Return the configured time to block for the response from an HTTP service + * method with a synchronous (blocking) method signature. * - *

- * By default, this is not set, in which case the behavior depends on connection and - * request timeout settings of the underlying HTTP client. We recommend configuring - * timeout values directly on the underlying HTTP client, which provides more * - * control over such settings. + *

By default, not set in which case the behavior depends on connection + * and request timeout settings of the underlying HTTP client. We recommend + * configuring timeout values directly on the underlying HTTP client, which + * provides more control over such settings. */ @Nullable Duration getBlockTimeout(); diff --git a/spring-web/src/test/java/org/springframework/web/client/support/RestTemplateHttpServiceProxyTests.java b/spring-web/src/test/java/org/springframework/web/client/support/RestTemplateAdapterTests.java similarity index 73% rename from spring-web/src/test/java/org/springframework/web/client/support/RestTemplateHttpServiceProxyTests.java rename to spring-web/src/test/java/org/springframework/web/client/support/RestTemplateAdapterTests.java index b8c382382f6..5b36b9e513a 100644 --- a/spring-web/src/test/java/org/springframework/web/client/support/RestTemplateHttpServiceProxyTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/support/RestTemplateAdapterTests.java @@ -51,31 +51,27 @@ import org.springframework.web.util.DefaultUriBuilderFactory; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy} using - * {@link RestTemplate} and {@link MockWebServer}. + * Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy} + * with {@link RestTemplateAdapter} connecting to {@link MockWebServer}. * * @author Olga Maciaszek-Sharma */ -class RestTemplateHttpServiceProxyTests { +class RestTemplateAdapterTests { private MockWebServer server; - private TestService testService; + private Service service; + @BeforeEach void setUp() { this.server = new MockWebServer(); prepareResponse(); - this.testService = initTestService(); - } - private TestService initTestService() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(this.server.url("/").toString())); - return HttpServiceProxyFactory.builder() - .exchangeAdapter(RestTemplateAdapter.forTemplate(restTemplate)) - .build() - .createClient(TestService.class); + RestTemplateAdapter adapter = RestTemplateAdapter.create(restTemplate); + this.service = HttpServiceProxyFactory.builder().exchangeAdapter(adapter).build().createClient(Service.class); } @SuppressWarnings("ConstantConditions") @@ -86,32 +82,33 @@ class RestTemplateHttpServiceProxyTests { } } + @Test - void getRequest() throws InterruptedException { - String response = testService.getRequest(); + void greeting() throws InterruptedException { + String response = this.service.getGreeting(); RecordedRequest request = this.server.takeRequest(); assertThat(response).isEqualTo("Hello Spring!"); assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()).isEqualTo("/test"); + assertThat(request.getPath()).isEqualTo("/greeting"); } @Test - void getRequestWithPathVariable() throws InterruptedException { - ResponseEntity response = testService.getRequestWithPathVariable("456"); + void greetingById() throws InterruptedException { + ResponseEntity response = this.service.getGreetingById("456"); RecordedRequest request = this.server.takeRequest(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isEqualTo("Hello Spring!"); assertThat(request.getMethod()).isEqualTo("GET"); - assertThat(request.getPath()).isEqualTo("/test/456"); + assertThat(request.getPath()).isEqualTo("/greeting/456"); } @Test - void getRequestWithDynamicUri() throws InterruptedException { + void greetingWithDynamicUri() throws InterruptedException { URI dynamicUri = this.server.url("/greeting/123").uri(); - Optional response = testService.getRequestWithDynamicUri(dynamicUri, "456"); + Optional response = this.service.getGreetingWithDynamicUri(dynamicUri, "456"); RecordedRequest request = this.server.takeRequest(); assertThat(response.orElse("empty")).isEqualTo("Hello Spring!"); @@ -120,12 +117,12 @@ class RestTemplateHttpServiceProxyTests { } @Test - void postWithRequestHeader() throws InterruptedException { - testService.postRequestWithHeader("testHeader", "testBody"); + void postWithHeader() throws InterruptedException { + service.postWithHeader("testHeader", "testBody"); RecordedRequest request = this.server.takeRequest(); assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getPath()).isEqualTo("/test"); + assertThat(request.getPath()).isEqualTo("/greeting"); assertThat(request.getHeaders().get("testHeaderName")).isEqualTo("testHeader"); assertThat(request.getBody().readUtf8()).isEqualTo("testBody"); } @@ -136,7 +133,7 @@ class RestTemplateHttpServiceProxyTests { map.add("param1", "value 1"); map.add("param2", "value 2"); - testService.postForm(map); + service.postForm(map); RecordedRequest request = this.server.takeRequest(); assertThat(request.getHeaders().get("Content-Type")) @@ -151,7 +148,7 @@ class RestTemplateHttpServiceProxyTests { MultipartFile file = new MockMultipartFile(fileName, originalFileName, MediaType.APPLICATION_JSON_VALUE, "test".getBytes()); - testService.postMultipart(file, "test2"); + service.postMultipart(file, "test2"); RecordedRequest request = this.server.takeRequest(); assertThat(request.getHeaders().get("Content-Type")).startsWith("multipart/form-data;boundary="); @@ -163,8 +160,8 @@ class RestTemplateHttpServiceProxyTests { } @Test - void putRequestWithCookies() throws InterruptedException { - testService.putRequestWithCookies("test1", "test2"); + void putWithCookies() throws InterruptedException { + service.putWithCookies("test1", "test2"); RecordedRequest request = this.server.takeRequest(); assertThat(request.getMethod()).isEqualTo("PUT"); @@ -172,8 +169,8 @@ class RestTemplateHttpServiceProxyTests { } @Test - void putRequestWithSameNameCookies() throws InterruptedException { - testService.putRequestWithSameNameCookies("test1", "test2"); + void putWithSameNameCookies() throws InterruptedException { + service.putWithSameNameCookies("test1", "test2"); RecordedRequest request = this.server.takeRequest(); assertThat(request.getMethod()).isEqualTo("PUT"); @@ -186,20 +183,21 @@ class RestTemplateHttpServiceProxyTests { this.server.enqueue(response); } - private interface TestService { - @GetExchange("/test") - String getRequest(); + private interface Service { + + @GetExchange("/greeting") + String getGreeting(); - @GetExchange("/test/{id}") - ResponseEntity getRequestWithPathVariable(@PathVariable String id); + @GetExchange("/greeting/{id}") + ResponseEntity getGreetingById(@PathVariable String id); - @GetExchange("/test/{id}") - Optional getRequestWithDynamicUri(@Nullable URI uri, @PathVariable String id); + @GetExchange("/greeting/{id}") + Optional getGreetingWithDynamicUri(@Nullable URI uri, @PathVariable String id); - @PostExchange("/test") - void postRequestWithHeader(@RequestHeader("testHeaderName") String testHeader, - @RequestBody String requestBody); + @PostExchange("/greeting") + void postWithHeader( + @RequestHeader("testHeaderName") String testHeader, @RequestBody String requestBody); @PostExchange(contentType = "application/x-www-form-urlencoded") void postForm(@RequestParam MultiValueMap params); @@ -208,12 +206,12 @@ class RestTemplateHttpServiceProxyTests { void postMultipart(MultipartFile file, @RequestPart String anotherPart); @PutExchange - void putRequestWithCookies(@CookieValue String firstCookie, - @CookieValue String secondCookie); + void putWithCookies( + @CookieValue String firstCookie, @CookieValue String secondCookie); @PutExchange - void putRequestWithSameNameCookies(@CookieValue("testCookie") String firstCookie, - @CookieValue("testCookie") String secondCookie); + void putWithSameNameCookies( + @CookieValue("testCookie") String firstCookie, @CookieValue("testCookie") String secondCookie); } diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java index 27ed4bf9d71..d0e3bfaf2de 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/CookieValueArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java index d880cc5c85c..d060b550af7 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/HttpMethodArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java index 96f3a568d39..5b4b571b9ec 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/NamedValueArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java index 0cfaf6fbb23..4fdb1f08e5b 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/PathVariableArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java index 5107ed8b00f..51095802ef1 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestAttributeArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java index 55d56dc1adb..ca41cead540 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestBodyArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolverTests.java index 4bf60d8b447..d7d1dd2f0c8 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/RequestHeaderArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java index 210c411bdcf..2819ec459ee 100644 --- a/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/service/invoker/UrlArgumentResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. diff --git a/spring-web/src/test/kotlin/org/springframework/web/client/support/KotlinRestTemplateHttpServiceProxyTests.kt b/spring-web/src/test/kotlin/org/springframework/web/client/support/KotlinRestTemplateHttpServiceProxyTests.kt index 8d3e36f38af..82bba493ea6 100644 --- a/spring-web/src/test/kotlin/org/springframework/web/client/support/KotlinRestTemplateHttpServiceProxyTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/web/client/support/KotlinRestTemplateHttpServiceProxyTests.kt @@ -63,7 +63,7 @@ class KotlinRestTemplateHttpServiceProxyTests { val restTemplate = RestTemplate() restTemplate.uriTemplateHandler = DefaultUriBuilderFactory(server.url("/").toString()) return HttpServiceProxyFactory.builder() - .exchangeAdapter(RestTemplateAdapter.forTemplate(restTemplate)) + .exchangeAdapter(RestTemplateAdapter.create(restTemplate)) .build() .createClient(TestService::class.java) } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java index 2a9c99f4e66..5a058b06081 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/support/WebClientAdapter.java @@ -53,6 +53,11 @@ public final class WebClientAdapter extends AbstractReactorHttpExchangeAdapter { } + @Override + public boolean supportsRequestAttributes() { + return true; + } + @Override public Mono exchangeForMono(HttpRequestValues requestValues) { return newRequest(requestValues).retrieve().toBodilessEntity().then(); @@ -132,8 +137,4 @@ public final class WebClientAdapter extends AbstractReactorHttpExchangeAdapter { return new WebClientAdapter(webClient); } - @Override - public boolean supportsRequestAttributes() { - return true; - } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java similarity index 90% rename from spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java rename to spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java index bd11b8dbe6d..42bbc086591 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/support/WebClientAdapterTests.java @@ -53,12 +53,12 @@ import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy} - * using {@link WebClient} and {@link MockWebServer}. + * with {@link WebClientAdapter} connecting to {@link MockWebServer}. * * @author Rossen Stoyanchev * @author Olga Maciaszek-Sharma */ -public class WebClientHttpServiceProxyTests { +public class WebClientAdapterTests { private MockWebServer server; @@ -82,7 +82,7 @@ public class WebClientHttpServiceProxyTests { prepareResponse(response -> response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!")); - StepVerifier.create(initHttpService().getGreeting()) + StepVerifier.create(initService().getGreeting()) .expectNext("Hello Spring!") .expectComplete() .verify(Duration.ofSeconds(5)); @@ -103,7 +103,7 @@ public class WebClientHttpServiceProxyTests { prepareResponse(response -> response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!")); - StepVerifier.create(initHttpService(webClient).getGreetingWithAttribute("myAttributeValue")) + StepVerifier.create(initService(webClient).getGreetingWithAttribute("myAttributeValue")) .expectNext("Hello Spring!") .expectComplete() .verify(Duration.ofSeconds(5)); @@ -117,7 +117,7 @@ public class WebClientHttpServiceProxyTests { prepareResponse(response -> response.setResponseCode(200).setBody(expectedBody)); URI dynamicUri = this.server.url("/greeting/123").uri(); - String actualBody = initHttpService().getGreetingById(dynamicUri, "456"); + String actualBody = initService().getGreetingById(dynamicUri, "456"); assertThat(actualBody).isEqualTo(expectedBody); assertThat(this.server.takeRequest().getRequestUrl().uri()).isEqualTo(dynamicUri); @@ -131,7 +131,7 @@ public class WebClientHttpServiceProxyTests { map.add("param1", "value 1"); map.add("param2", "value 2"); - initHttpService().postForm(map); + initService().postForm(map); RecordedRequest request = this.server.takeRequest(); assertThat(request.getHeaders().get("Content-Type")).isEqualTo("application/x-www-form-urlencoded;charset=UTF-8"); @@ -146,7 +146,7 @@ public class WebClientHttpServiceProxyTests { MultipartFile file = new MockMultipartFile(fileName, originalFileName, MediaType.APPLICATION_JSON_VALUE, "test".getBytes()); - initHttpService().postMultipart(file, "test2"); + initService().postMultipart(file, "test2"); RecordedRequest request = this.server.takeRequest(); assertThat(request.getHeaders().get("Content-Type")).startsWith("multipart/form-data;boundary="); @@ -157,14 +157,14 @@ public class WebClientHttpServiceProxyTests { "Content-Type: text/plain;charset=UTF-8", "Content-Length: 5", "test2"); } - private TestHttpService initHttpService() { + private Service initService() { WebClient webClient = WebClient.builder().baseUrl(this.server.url("/").toString()).build(); - return initHttpService(webClient); + return initService(webClient); } - private TestHttpService initHttpService(WebClient webClient) { + private Service initService(WebClient webClient) { WebClientAdapter adapter = WebClientAdapter.forClient(webClient); - return HttpServiceProxyFactory.builderFor(adapter).build().createClient(TestHttpService.class); + return HttpServiceProxyFactory.builderFor(adapter).build().createClient(Service.class); } private void prepareResponse(Consumer consumer) { @@ -174,7 +174,7 @@ public class WebClientHttpServiceProxyTests { } - private interface TestHttpService { + private interface Service { @GetExchange("/greeting") Mono getGreeting(); diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyKotlinTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyKotlinTests.kt index 9292ade0298..7ca2816cc7d 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyKotlinTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/function/client/support/WebClientHttpServiceProxyKotlinTests.kt @@ -128,10 +128,8 @@ class KotlinWebClientHttpServiceProxyTests { } private fun initHttpService(webClient: WebClient): TestHttpService { - return HttpServiceProxyFactory.builder() - .clientAdapter(WebClientAdapter.forClient(webClient)) - .build() - .createClient() + val adapter = WebClientAdapter.forClient(webClient) + return HttpServiceProxyFactory.builderFor(adapter).build().createClient() } private fun prepareResponse(consumer: Consumer) {