parent
495507e5d4
commit
496c1dcae1
|
|
@ -70,6 +70,8 @@ public final class HttpRequestValues {
|
||||||
|
|
||||||
private final MultiValueMap<String, String> cookies;
|
private final MultiValueMap<String, String> cookies;
|
||||||
|
|
||||||
|
private final Map<String, Object> attributes;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private final Object bodyValue;
|
private final Object bodyValue;
|
||||||
|
|
||||||
|
|
@ -82,7 +84,7 @@ public final class HttpRequestValues {
|
||||||
|
|
||||||
private HttpRequestValues(HttpMethod httpMethod,
|
private HttpRequestValues(HttpMethod httpMethod,
|
||||||
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
|
@Nullable URI uri, @Nullable String uriTemplate, Map<String, String> uriVariables,
|
||||||
HttpHeaders headers, MultiValueMap<String, String> cookies,
|
HttpHeaders headers, MultiValueMap<String, String> cookies, Map<String, Object> attributes,
|
||||||
@Nullable Object bodyValue,
|
@Nullable Object bodyValue,
|
||||||
@Nullable Publisher<?> body, @Nullable ParameterizedTypeReference<?> bodyElementType) {
|
@Nullable Publisher<?> body, @Nullable ParameterizedTypeReference<?> bodyElementType) {
|
||||||
|
|
||||||
|
|
@ -94,6 +96,7 @@ public final class HttpRequestValues {
|
||||||
this.uriVariables = uriVariables;
|
this.uriVariables = uriVariables;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.cookies = cookies;
|
this.cookies = cookies;
|
||||||
|
this.attributes = attributes;
|
||||||
this.bodyValue = bodyValue;
|
this.bodyValue = bodyValue;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
this.bodyElementType = bodyElementType;
|
this.bodyElementType = bodyElementType;
|
||||||
|
|
@ -142,12 +145,19 @@ public final class HttpRequestValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the cookies for the request, if any.
|
* Return the cookies for the request, or an empty map.
|
||||||
*/
|
*/
|
||||||
public MultiValueMap<String, String> getCookies() {
|
public MultiValueMap<String, String> getCookies() {
|
||||||
return this.cookies;
|
return this.cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the attributes associated with the request, or an empty map.
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getAttributes() {
|
||||||
|
return this.attributes;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the request body as a value to be serialized, if set.
|
* Return the request body as a value to be serialized, if set.
|
||||||
* <p>This is mutually exclusive with {@link #getBody()}.
|
* <p>This is mutually exclusive with {@link #getBody()}.
|
||||||
|
|
@ -209,6 +219,9 @@ public final class HttpRequestValues {
|
||||||
@Nullable
|
@Nullable
|
||||||
private MultiValueMap<String, String> requestParams;
|
private MultiValueMap<String, String> requestParams;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Map<String, Object> attributes;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Object bodyValue;
|
private Object bodyValue;
|
||||||
|
|
||||||
|
|
@ -325,6 +338,17 @@ public final class HttpRequestValues {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure an attribute to associate with the request.
|
||||||
|
* @param name the attribute name
|
||||||
|
* @param value the attribute value
|
||||||
|
*/
|
||||||
|
public Builder addAttribute(String name, Object value) {
|
||||||
|
this.attributes = (this.attributes != null ? this.attributes : new HashMap<>());
|
||||||
|
this.attributes.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the request body as a concrete value to be serialized.
|
* Set the request body as a concrete value to be serialized.
|
||||||
* <p>This is mutually exclusive with, and resets any previously set
|
* <p>This is mutually exclusive with, and resets any previously set
|
||||||
|
|
@ -388,9 +412,12 @@ public final class HttpRequestValues {
|
||||||
MultiValueMap<String, String> cookies = (this.cookies != null ?
|
MultiValueMap<String, String> cookies = (this.cookies != null ?
|
||||||
new LinkedMultiValueMap<>(this.cookies) : EMPTY_COOKIES_MAP);
|
new LinkedMultiValueMap<>(this.cookies) : EMPTY_COOKIES_MAP);
|
||||||
|
|
||||||
|
Map<String, Object> attributes = (this.attributes != null ?
|
||||||
|
new HashMap<>(this.attributes) : Collections.emptyMap());
|
||||||
|
|
||||||
return new HttpRequestValues(
|
return new HttpRequestValues(
|
||||||
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, bodyValue,
|
this.httpMethod, uri, uriTemplate, uriVars, headers, cookies, attributes,
|
||||||
this.body, this.bodyElementType);
|
bodyValue, this.body, this.bodyElementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String appendQueryParams(
|
private String appendQueryParams(
|
||||||
|
|
|
||||||
|
|
@ -185,14 +185,21 @@ public final class HttpServiceProxyFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionService conversionService) {
|
private List<HttpServiceArgumentResolver> initArgumentResolvers(ConversionService conversionService) {
|
||||||
|
|
||||||
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>(this.customResolvers);
|
List<HttpServiceArgumentResolver> resolvers = new ArrayList<>(this.customResolvers);
|
||||||
|
|
||||||
|
// Annotation-based
|
||||||
resolvers.add(new RequestHeaderArgumentResolver(conversionService));
|
resolvers.add(new RequestHeaderArgumentResolver(conversionService));
|
||||||
resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry));
|
resolvers.add(new RequestBodyArgumentResolver(this.reactiveAdapterRegistry));
|
||||||
resolvers.add(new PathVariableArgumentResolver(conversionService));
|
resolvers.add(new PathVariableArgumentResolver(conversionService));
|
||||||
resolvers.add(new RequestParamArgumentResolver(conversionService));
|
resolvers.add(new RequestParamArgumentResolver(conversionService));
|
||||||
resolvers.add(new CookieValueArgumentResolver(conversionService));
|
resolvers.add(new CookieValueArgumentResolver(conversionService));
|
||||||
|
resolvers.add(new RequestAttributeArgumentResolver());
|
||||||
|
|
||||||
|
// Specific type
|
||||||
resolvers.add(new UrlArgumentResolver());
|
resolvers.add(new UrlArgumentResolver());
|
||||||
resolvers.add(new HttpMethodArgumentResolver());
|
resolvers.add(new HttpMethodArgumentResolver());
|
||||||
|
|
||||||
return resolvers;
|
return resolvers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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
|
||||||
|
*
|
||||||
|
* https://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.service.invoker;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HttpServiceArgumentResolver} for {@link RequestAttribute @RequestAttribute}
|
||||||
|
* annotated arguments.
|
||||||
|
*
|
||||||
|
* <p>The argument may be a single variable value or a {@code Map} with multiple
|
||||||
|
* variables and values.
|
||||||
|
*
|
||||||
|
* <p>If the value is required but {@code null}, {@link IllegalArgumentException}
|
||||||
|
* is raised. The value is not required if:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link RequestAttribute#required()} is set to {@code false}
|
||||||
|
* <li>The argument is declared as {@link java.util.Optional}
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
public class RequestAttributeArgumentResolver extends AbstractNamedValueArgumentResolver {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
|
||||||
|
RequestAttribute annot = parameter.getParameterAnnotation(RequestAttribute.class);
|
||||||
|
return (annot == null ? null :
|
||||||
|
new NamedValueInfo(annot.name(), annot.required(), null, "request attribute", false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addRequestValue(String name, Object value, HttpRequestValues.Builder requestValues) {
|
||||||
|
requestValues.addAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -48,8 +48,7 @@ class PathVariableArgumentResolverTests {
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
private void assertPathVariable(String name, @Nullable String expectedValue) {
|
private void assertPathVariable(String name, @Nullable String expectedValue) {
|
||||||
assertThat(this.client.getRequestValues().getUriVariables().get(name))
|
assertThat(this.client.getRequestValues().getUriVariables().get(name)).isEqualTo(expectedValue);
|
||||||
.isEqualTo(expectedValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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
|
||||||
|
*
|
||||||
|
* https://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.service.invoker;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||||
|
import org.springframework.web.service.annotation.GetExchange;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link RequestAttributeArgumentResolver}.
|
||||||
|
* <p>For base class functionality, see {@link NamedValueArgumentResolverTests}.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
class RequestAttributeArgumentResolverTests {
|
||||||
|
|
||||||
|
private final TestHttpClientAdapter client = new TestHttpClientAdapter();
|
||||||
|
|
||||||
|
private final Service service = HttpServiceProxyFactory.builder(this.client).build().createClient(Service.class);
|
||||||
|
|
||||||
|
|
||||||
|
// Base class functionality should be tested in NamedValueArgumentResolverTests.
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void cookieValue() {
|
||||||
|
this.service.execute("test");
|
||||||
|
assertAttribute("attribute", "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private void assertAttribute(String name, @Nullable String expectedValue) {
|
||||||
|
assertThat(this.client.getRequestValues().getAttributes().get(name)).isEqualTo(expectedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private interface Service {
|
||||||
|
|
||||||
|
@GetExchange
|
||||||
|
void execute(@RequestAttribute String attribute);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -103,6 +103,7 @@ public class WebClientAdapter implements HttpClientAdapter {
|
||||||
|
|
||||||
bodySpec.headers(headers -> headers.putAll(requestValues.getHeaders()));
|
bodySpec.headers(headers -> headers.putAll(requestValues.getHeaders()));
|
||||||
bodySpec.cookies(cookies -> cookies.putAll(requestValues.getCookies()));
|
bodySpec.cookies(cookies -> cookies.putAll(requestValues.getCookies()));
|
||||||
|
bodySpec.attributes(attributes -> attributes.putAll(requestValues.getAttributes()));
|
||||||
|
|
||||||
if (requestValues.getBodyValue() != null) {
|
if (requestValues.getBodyValue() != null) {
|
||||||
bodySpec.bodyValue(requestValues.getBodyValue());
|
bodySpec.bodyValue(requestValues.getBodyValue());
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ package org.springframework.web.reactive.function.client.support;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import okhttp3.mockwebserver.MockResponse;
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
|
|
@ -29,11 +31,13 @@ import org.junit.jupiter.api.Test;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
import org.springframework.web.bind.annotation.RequestAttribute;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
import org.springframework.web.service.annotation.GetExchange;
|
import org.springframework.web.service.annotation.GetExchange;
|
||||||
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy}
|
* Integration tests for {@link HttpServiceProxyFactory HTTP Service proxy}
|
||||||
|
|
@ -45,22 +49,10 @@ public class WebClientHttpServiceProxyTests {
|
||||||
|
|
||||||
private MockWebServer server;
|
private MockWebServer server;
|
||||||
|
|
||||||
private TestHttpService httpService;
|
|
||||||
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
this.server = new MockWebServer();
|
this.server = new MockWebServer();
|
||||||
WebClient webClient = WebClient
|
|
||||||
.builder()
|
|
||||||
.clientConnector(new ReactorClientHttpConnector())
|
|
||||||
.baseUrl(this.server.url("/").toString())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
WebClientAdapter clientAdapter = new WebClientAdapter(webClient);
|
|
||||||
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(clientAdapter).build();
|
|
||||||
|
|
||||||
this.httpService = proxyFactory.createClient(TestHttpService.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
|
@ -78,12 +70,47 @@ public class WebClientHttpServiceProxyTests {
|
||||||
prepareResponse(response ->
|
prepareResponse(response ->
|
||||||
response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||||
|
|
||||||
StepVerifier.create(this.httpService.getGreeting())
|
StepVerifier.create(initHttpService().getGreeting())
|
||||||
.expectNext("Hello Spring!")
|
.expectNext("Hello Spring!")
|
||||||
.expectComplete()
|
.expectComplete()
|
||||||
.verify(Duration.ofSeconds(5));
|
.verify(Duration.ofSeconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void greetingWithRequestAttribute() {
|
||||||
|
|
||||||
|
Map<String, Object> attributes = new HashMap<>();
|
||||||
|
|
||||||
|
WebClient webClient = WebClient.builder()
|
||||||
|
.baseUrl(this.server.url("/").toString())
|
||||||
|
.filter((request, next) -> {
|
||||||
|
attributes.putAll(request.attributes());
|
||||||
|
return next.exchange(request);
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
prepareResponse(response ->
|
||||||
|
response.setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||||
|
|
||||||
|
StepVerifier.create(initHttpService(webClient).getGreetingWithAttribute("myAttributeValue"))
|
||||||
|
.expectNext("Hello Spring!")
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(5));
|
||||||
|
|
||||||
|
assertThat(attributes).containsEntry("myAttribute", "myAttributeValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestHttpService initHttpService() {
|
||||||
|
WebClient webClient = WebClient.builder().baseUrl(this.server.url("/").toString()).build();
|
||||||
|
return initHttpService(webClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestHttpService initHttpService(WebClient webClient) {
|
||||||
|
WebClientAdapter clientAdapter = new WebClientAdapter(webClient);
|
||||||
|
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder(clientAdapter).build();
|
||||||
|
return proxyFactory.createClient(TestHttpService.class);
|
||||||
|
}
|
||||||
|
|
||||||
private void prepareResponse(Consumer<MockResponse> consumer) {
|
private void prepareResponse(Consumer<MockResponse> consumer) {
|
||||||
MockResponse response = new MockResponse();
|
MockResponse response = new MockResponse();
|
||||||
consumer.accept(response);
|
consumer.accept(response);
|
||||||
|
|
@ -96,6 +123,9 @@ public class WebClientHttpServiceProxyTests {
|
||||||
@GetExchange("/greeting")
|
@GetExchange("/greeting")
|
||||||
Mono<String> getGreeting();
|
Mono<String> getGreeting();
|
||||||
|
|
||||||
|
@GetExchange("/greeting")
|
||||||
|
Mono<String> getGreetingWithAttribute(@RequestAttribute String myAttribute);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue