diff --git a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc index 37d85683e68..ad47f5824dd 100644 --- a/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc +++ b/framework-docs/modules/ROOT/pages/integration/rest-clients.adoc @@ -1007,7 +1007,8 @@ method parameters: is supported for non-String values. | `@RequestAttribute` -| Provide an `Object` to add as a request attribute. Only supported by `WebClient`. +| Provide an `Object` to add as a request attribute. Only supported by `RestClient` + and `WebClient`. | `@RequestBody` | Provide the body of the request either as an Object to be serialized, or a diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java index bac710f1bf9..10736fe4f02 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java @@ -18,6 +18,8 @@ package org.springframework.mock.http.client; import java.io.IOException; import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -46,6 +48,9 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie private boolean executed = false; + @Nullable + Map attributes; + /** * Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as @@ -115,6 +120,16 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie return this.executed; } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new ConcurrentHashMap<>(); + this.attributes = attributes; + } + return attributes; + } + /** * Set the {@link #isExecuted() executed} flag to {@code true} and return the * configured {@link #setResponse(ClientHttpResponse) response}. diff --git a/spring-web/src/main/java/org/springframework/http/HttpRequest.java b/spring-web/src/main/java/org/springframework/http/HttpRequest.java index 62ea73fad5d..d9a59251289 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/HttpRequest.java @@ -17,6 +17,7 @@ package org.springframework.http; import java.net.URI; +import java.util.Map; /** * Represents an HTTP request message, consisting of a @@ -41,4 +42,10 @@ public interface HttpRequest extends HttpMessage { */ URI getURI(); + /** + * Return a mutable map of request attributes for this request. + * @since 6.2 + */ + Map getAttributes(); + } diff --git a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java index f964e66e80d..5404493a343 100644 --- a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java @@ -18,6 +18,8 @@ package org.springframework.http.client; import java.io.IOException; import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; @@ -39,6 +41,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @Nullable private HttpHeaders readOnlyHeaders; + @Nullable + private Map attributes; + @Override public final HttpHeaders getHeaders() { @@ -60,6 +65,16 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { return getBodyInternal(this.headers); } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new LinkedHashMap<>(); + this.attributes = attributes; + } + return attributes; + } + @Override public final ClientHttpResponse execute() throws IOException { assertNotExecuted(); diff --git a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java index 5149618c7b1..07203df3710 100644 --- a/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java @@ -91,6 +91,7 @@ class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { HttpMethod method = request.getMethod(); ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method); request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value)); + request.getAttributes().forEach((key, value) -> delegate.getAttributes().put(key, value)); if (body.length > 0) { if (delegate instanceof StreamingHttpOutputMessage streamingOutputMessage) { streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() { diff --git a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java index dad91a6b1b0..57120d15c50 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java @@ -17,6 +17,7 @@ package org.springframework.http.client.support; import java.net.URI; +import java.util.Map; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -70,6 +71,14 @@ public class HttpRequestWrapper implements HttpRequest { return this.request.getURI(); } + /** + * Return the attributes of the wrapped request. + */ + @Override + public Map getAttributes() { + return this.request.getAttributes(); + } + /** * Return the headers of the wrapped request. */ diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index 85850d138ff..62023d26438 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -29,11 +29,16 @@ import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Principal; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; import java.util.Arrays; +import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import jakarta.servlet.http.HttpServletRequest; @@ -67,6 +72,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest { @Nullable private HttpHeaders headers; + @Nullable + private Map attributes; + + @Nullable private ServerHttpAsyncRequestControl asyncRequestControl; @@ -207,6 +216,16 @@ public class ServletServerHttpRequest implements ServerHttpRequest { return new InetSocketAddress(this.servletRequest.getRemoteHost(), this.servletRequest.getRemotePort()); } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new AttributesMap(); + this.attributes = attributes; + } + return attributes; + } + @Override public InputStream getBody() throws IOException { if (isFormPost(this.servletRequest) && this.servletRequest.getQueryString() == null) { @@ -276,4 +295,151 @@ public class ServletServerHttpRequest implements ServerHttpRequest { return new ByteArrayInputStream(bytes); } + + private final class AttributesMap extends AbstractMap { + + @Nullable + private transient Set keySet; + + @Nullable + private transient Collection values; + + @Nullable + private transient Set> entrySet; + + + @Override + public int size() { + int size = 0; + for (Enumeration names = servletRequest.getAttributeNames(); names.hasMoreElements(); names.nextElement()) { + size++; + } + return size; + } + + @Override + @Nullable + public Object get(Object key) { + if (key instanceof String name) { + return servletRequest.getAttribute(name); + } + else { + return null; + } + } + + @Override + @Nullable + public Object put(String key, Object value) { + Object old = get(key); + servletRequest.setAttribute(key, value); + return old; + } + + @Override + @Nullable + public Object remove(Object key) { + if (key instanceof String name) { + Object old = get(key); + servletRequest.removeAttribute(name); + return old; + } + else { + return null; + } + } + + @Override + public void clear() { + for (Enumeration names = servletRequest.getAttributeNames(); names.hasMoreElements(); ) { + String name = names.nextElement(); + servletRequest.removeAttribute(name); + } + } + + @Override + public Set keySet() { + Set keySet = this.keySet; + if (keySet == null) { + keySet = new AbstractSet<>() { + @Override + public Iterator iterator() { + return servletRequest.getAttributeNames().asIterator(); + } + + @Override + public int size() { + return AttributesMap.this.size(); + } + }; + this.keySet = keySet; + } + return keySet; + } + + @Override + public Collection values() { + Collection values = this.values; + if (values == null) { + values = new AbstractCollection<>() { + @Override + public Iterator iterator() { + Enumeration e = servletRequest.getAttributeNames(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return e.hasMoreElements(); + } + + @Override + public Object next() { + String name = e.nextElement(); + return servletRequest.getAttribute(name); + } + }; + } + + @Override + public int size() { + return AttributesMap.this.size(); + } + }; + this.values = values; + } + return values; + } + + @Override + public Set> entrySet() { + Set> entrySet = this.entrySet; + if (entrySet == null) { + entrySet = new AbstractSet<>() { + @Override + public Iterator> iterator() { + Enumeration e = servletRequest.getAttributeNames(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return e.hasMoreElements(); + } + + @Override + public Entry next() { + String name = e.nextElement(); + Object value = servletRequest.getAttribute(name); + return new SimpleImmutableEntry<>(name, value); + } + }; + } + + @Override + public int size() { + return AttributesMap.this.size(); + } + }; + this.entrySet = entrySet; + } + return entrySet; + } + } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java index 829a2202a81..c14399a6be2 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java @@ -19,6 +19,9 @@ package org.springframework.http.server.reactive; import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -68,6 +71,9 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { @Nullable private String logPrefix; + @Nullable + private Supplier> attributesSupplier; + /** * Constructor with the method, URI and headers for the request. @@ -122,6 +128,16 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { return this.uri; } + @Override + public Map getAttributes() { + if (this.attributesSupplier != null) { + return this.attributesSupplier.get(); + } + else { + return Collections.emptyMap(); + } + } + @Override public RequestPath getPath() { return this.path; @@ -230,4 +246,12 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { return getId(); } + /** + * Set the attribute supplier. + *

Note: This is exposed mainly for internal framework + * use. + */ + public void setAttributesSupplier(Supplier> attributesSupplier) { + this.attributesSupplier = attributesSupplier; + } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java index fc6143bfdaf..8b651de18b8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpRequestDecorator.java @@ -18,6 +18,7 @@ package org.springframework.http.server.reactive; import java.net.InetSocketAddress; import java.net.URI; +import java.util.Map; import reactor.core.publisher.Flux; @@ -70,6 +71,11 @@ public class ServerHttpRequestDecorator implements ServerHttpRequest { return getDelegate().getURI(); } + @Override + public Map getAttributes() { + return getDelegate().getAttributes(); + } + @Override public RequestPath getPath() { return getDelegate().getPath(); diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index 471f5ced20e..8ef97588cef 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -82,6 +83,8 @@ final class DefaultRestClient implements RestClient { private static final ClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention(); + private static final String URI_TEMPLATE_ATTRIBUTE = RestClient.class.getName() + ".uriTemplate"; + private final ClientHttpRequestFactory clientRequestFactory; @@ -297,7 +300,7 @@ final class DefaultRestClient implements RestClient { private InternalBody body; @Nullable - private String uriTemplate; + private Map attributes; @Nullable private Consumer httpRequestConsumer; @@ -308,19 +311,19 @@ final class DefaultRestClient implements RestClient { @Override public RequestBodySpec uri(String uriTemplate, Object... uriVariables) { - this.uriTemplate = uriTemplate; + attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables)); } @Override public RequestBodySpec uri(String uriTemplate, Map uriVariables) { - this.uriTemplate = uriTemplate; + attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); return uri(DefaultRestClient.this.uriBuilderFactory.expand(uriTemplate, uriVariables)); } @Override public RequestBodySpec uri(String uriTemplate, Function uriFunction) { - this.uriTemplate = uriTemplate; + attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate); return uri(uriFunction.apply(DefaultRestClient.this.uriBuilderFactory.uriString(uriTemplate))); } @@ -392,6 +395,27 @@ final class DefaultRestClient implements RestClient { return this; } + @Override + public RequestBodySpec attribute(String name, Object value) { + getAttributes().put(name, value); + return this; + } + + @Override + public RequestBodySpec attributes(Consumer> attributesConsumer) { + attributesConsumer.accept(getAttributes()); + return this; + } + + private Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new ConcurrentHashMap<>(4); + this.attributes = attributes; + } + return attributes; + } + @Override public RequestBodySpec httpRequest(Consumer requestConsumer) { this.httpRequestConsumer = (this.httpRequestConsumer != null ? @@ -483,8 +507,10 @@ final class DefaultRestClient implements RestClient { HttpHeaders headers = initHeaders(); ClientHttpRequest clientRequest = createRequest(uri); clientRequest.getHeaders().addAll(headers); + Map attributes = getAttributes(); + clientRequest.getAttributes().putAll(attributes); ClientRequestObservationContext observationContext = new ClientRequestObservationContext(clientRequest); - observationContext.setUriTemplate(this.uriTemplate); + observationContext.setUriTemplate((String) attributes.get(URI_TEMPLATE_ATTRIBUTE)); observation = ClientHttpObservationDocumentation.HTTP_CLIENT_EXCHANGES.observation(observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry).start(); if (this.body != null) { diff --git a/spring-web/src/main/java/org/springframework/web/client/RestClient.java b/spring-web/src/main/java/org/springframework/web/client/RestClient.java index e1a560062b0..ef995daff8e 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestClient.java @@ -498,6 +498,24 @@ public interface RestClient { */ S headers(Consumer headersConsumer); + /** + * Set the attribute with the given name to the given value. + * @param name the name of the attribute to add + * @param value the value of the attribute to add + * @return this builder + * @since 6.2 + */ + S attribute(String name, Object value); + + /** + * Provides access to every attribute declared so far with the + * possibility to add, replace, or remove values. + * @param attributesConsumer the consumer to provide access to + * @return this builder + * @since 6.2 + */ + S attributes(Consumer> attributesConsumer); + /** * Callback for access to the {@link ClientHttpRequest} that in turn * provides access to the native request of the underlying HTTP library. diff --git a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java index 403d7f71562..67c4ecca906 100644 --- a/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/client/support/RestClientAdapter.java @@ -56,7 +56,7 @@ public final class RestClientAdapter implements HttpExchangeAdapter { @Override public boolean supportsRequestAttributes() { - return false; + return true; } @Override @@ -121,6 +121,8 @@ public final class RestClientAdapter implements HttpExchangeAdapter { bodySpec.header(HttpHeaders.COOKIE, String.join("; ", cookies)); } + bodySpec.attributes(attributes -> attributes.putAll(values.getAttributes())); + if (values.getBodyValue() != null) { bodySpec.body(values.getBodyValue()); } diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java index 2c6f73bfbfc..c17beb484b8 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java @@ -42,6 +42,7 @@ import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.multipart.Part; +import org.springframework.http.server.reactive.AbstractServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; @@ -137,6 +138,10 @@ public class DefaultServerWebExchange implements ServerWebExchange { this.formDataMono = initFormData(request, codecConfigurer, getLogPrefix()); this.multipartDataMono = initMultipartData(codecConfigurer, getLogPrefix()); this.applicationContext = applicationContext; + + if (request instanceof AbstractServerHttpRequest abstractServerHttpRequest) { + abstractServerHttpRequest.setAttributesSupplier(() -> this.attributes); + } } private static Mono> initFormData(ServerHttpRequest request, diff --git a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java index cd08464d207..2735c00e315 100644 --- a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java @@ -115,6 +115,32 @@ class InterceptingClientHttpRequestFactoryTests { request.execute(); } + @Test + void changeAttribute() throws Exception { + final String attrName = "Foo"; + final String attrValue = "Bar"; + + ClientHttpRequestInterceptor interceptor = (request, body, execution) -> { + System.out.println("interceptor"); + request.getAttributes().put(attrName, attrValue); + return execution.execute(request, body); + }; + + requestMock = new MockClientHttpRequest() { + @Override + protected ClientHttpResponse executeInternal() { + System.out.println("execute"); + assertThat(getAttributes()).containsEntry(attrName, attrValue); + return responseMock; + } + }; + + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, Collections.singletonList(interceptor)); + + ClientHttpRequest request = requestFactory.createRequest(URI.create("https://example.com"), HttpMethod.GET); + request.execute(); + } + @Test void changeURI() throws Exception { final URI changedUri = URI.create("https://example.com/2"); diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index dcb98b14515..a021b4c9b9b 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -217,4 +217,10 @@ class ServletServerHttpRequestTests { assertThat(request.getHeaders().getContentLength()).isEqualTo(result.length); } + @Test + void attributes() { + request.getAttributes().put("foo", "bar"); + assertThat(mockRequest.getAttribute("foo")).isEqualTo("bar"); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java index 01199a582ec..f5921e7ff2b 100644 --- a/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/ForwardedHeaderUtilsTests.java @@ -17,6 +17,8 @@ package org.springframework.web.util; import java.net.URI; +import java.util.Collections; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -363,6 +365,11 @@ class ForwardedHeaderUtilsTests { return UriComponentsBuilder.fromUriString("/").build().toUri(); } + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } + @Override public HttpHeaders getHeaders() { return new HttpHeaders(); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java index 826b193712e..1cf6a68e596 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/MockClientHttpRequest.java @@ -18,6 +18,8 @@ package org.springframework.web.testfixture.http.client; import java.io.IOException; import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -46,6 +48,9 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie private boolean executed = false; + @Nullable + Map attributes; + /** * Create a {@code MockClientHttpRequest} with {@link HttpMethod#GET GET} as @@ -115,6 +120,16 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie return this.executed; } + @Override + public Map getAttributes() { + Map attributes = this.attributes; + if (attributes == null) { + attributes = new ConcurrentHashMap<>(); + this.attributes = attributes; + } + return attributes; + } + /** * Set the {@link #isExecuted() executed} flag to {@code true} and return the * configured {@link #setResponse(ClientHttpResponse) response}. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java index 24f41905d40..0a49c089714 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilder.java @@ -18,6 +18,8 @@ package org.springframework.web.reactive.function.client; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; @@ -65,6 +67,11 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder { public HttpHeaders getHeaders() { return HttpHeaders.EMPTY; } + + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } }; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java index cb77cac3fd2..c11c5d51178 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunctions.java @@ -17,6 +17,7 @@ package org.springframework.web.reactive.function.client; import java.net.URI; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -149,6 +150,11 @@ public abstract class ExchangeFunctions { return request.url(); } + @Override + public Map getAttributes() { + return request.attributes(); + } + @Override public HttpHeaders getHeaders() { return request.headers(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java index a97fbd90256..11c97952511 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilder.java @@ -193,7 +193,7 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder { @Override public ServerRequest build() { ServerHttpRequest serverHttpRequest = new BuiltServerHttpRequest(this.exchange.getRequest().getId(), - this.method, this.uri, this.contextPath, this.headers, this.cookies, this.body); + this.method, this.uri, this.contextPath, this.headers, this.cookies, this.body, this.attributes); ServerWebExchange exchange = new DelegatingServerWebExchange( serverHttpRequest, this.attributes, this.exchange, this.messageReaders); return new DefaultServerRequest(exchange, this.messageReaders); @@ -220,8 +220,10 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder { private final Flux body; + private final Map attributes; + public BuiltServerHttpRequest(String id, HttpMethod method, URI uri, @Nullable String contextPath, - HttpHeaders headers, MultiValueMap cookies, Flux body) { + HttpHeaders headers, MultiValueMap cookies, Flux body, Map attributes) { this.id = id; this.method = method; @@ -231,6 +233,7 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder { this.cookies = unmodifiableCopy(cookies); this.queryParams = parseQueryParams(uri); this.body = body; + this.attributes = attributes; } private static MultiValueMap unmodifiableCopy(MultiValueMap original) { @@ -273,6 +276,11 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder { return this.uri; } + @Override + public Map getAttributes() { + return this.attributes; + } + @Override public RequestPath getPath() { return this.path; diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java index 187fbcb97fa..a40bc25e6f4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.client; import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.OptionalLong; @@ -324,6 +325,11 @@ class DefaultClientResponseTests { public HttpHeaders getHeaders() { return HttpHeaders.EMPTY; } + + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } }; given(mockExchangeStrategies.messageReaders()).willReturn(