Browse Source

Apply attributes to the underlying HTTP request

See gh-29958
pull/32425/head
PhilKes 3 years ago committed by rstoyanchev
parent
commit
052b6357c8
  1. 4
      spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpRequest.java
  2. 22
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java
  3. 12
      spring-test/src/main/java/org/springframework/test/web/reactive/server/HttpHandlerConnector.java
  4. 7
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
  5. 12
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java
  6. 12
      spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java
  7. 18
      spring-test/src/test/java/org/springframework/test/web/reactive/server/WiretapConnectorTests.java
  8. 34
      spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java
  9. 10
      spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpConnector.java
  10. 6
      spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java
  11. 6
      spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpRequestDecorator.java
  12. 17
      spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpConnector.java
  13. 16
      spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java
  14. 17
      spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpConnector.java
  15. 12
      spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java
  16. 14
      spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java
  17. 11
      spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java
  18. 13
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java
  19. 19
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
  20. 14
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpConnector.java
  21. 19
      spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java
  22. 4
      spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpRequest.java
  23. 6
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java
  24. 20
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java
  25. 7
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java
  26. 66
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

4
spring-test/src/main/java/org/springframework/mock/http/client/reactive/MockClientHttpRequest.java

@ -119,6 +119,10 @@ public class MockClientHttpRequest extends AbstractClientHttpRequest { @@ -119,6 +119,10 @@ public class MockClientHttpRequest extends AbstractClientHttpRequest {
.forEach(cookie -> getHeaders().add(HttpHeaders.COOKIE, cookie.toString()));
}
@Override
protected void applyAttributes() {
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return doCommit(() -> Mono.defer(() -> this.writeHandler.apply(Flux.from(body))));

22
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java

@ -94,6 +94,8 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @@ -94,6 +94,8 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
@Nullable
private MultiValueMap<String, String> defaultCookies;
private boolean applyAttributes;
@Nullable
private List<ExchangeFilterFunction> filters;
@ -155,6 +157,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @@ -155,6 +157,7 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
}
this.defaultCookies = (other.defaultCookies != null ?
new LinkedMultiValueMap<>(other.defaultCookies) : null);
this.applyAttributes = other.applyAttributes;
this.filters = (other.filters != null ? new ArrayList<>(other.filters) : null);
this.entityResultConsumer = other.entityResultConsumer;
this.strategies = other.strategies;
@ -213,6 +216,12 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @@ -213,6 +216,12 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
return this.defaultCookies;
}
@Override
public WebTestClient.Builder applyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
return this;
}
@Override
public WebTestClient.Builder filter(ExchangeFilterFunction filter) {
Assert.notNull(filter, "ExchangeFilterFunction is required");
@ -312,22 +321,25 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { @@ -312,22 +321,25 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
this.entityResultConsumer, this.responseTimeout, new DefaultWebTestClientBuilder(this));
}
private static ClientHttpConnector initConnector() {
private ClientHttpConnector initConnector() {
final ClientHttpConnector connector;
if (reactorNettyClientPresent) {
return new ReactorClientHttpConnector();
connector = new ReactorClientHttpConnector();
}
else if (reactorNetty2ClientPresent) {
return new ReactorNetty2ClientHttpConnector();
}
else if (jettyClientPresent) {
return new JettyClientHttpConnector();
connector = new JettyClientHttpConnector();
}
else if (httpComponentsClientPresent) {
return new HttpComponentsClientHttpConnector();
connector = new HttpComponentsClientHttpConnector();
}
else {
return new JdkClientHttpConnector();
connector = new JdkClientHttpConnector();
}
connector.setApplyAttributes(this.applyAttributes);
return connector;
}
private ExchangeStrategies initExchangeStrategies() {

12
spring-test/src/main/java/org/springframework/test/web/reactive/server/HttpHandlerConnector.java

@ -64,6 +64,8 @@ public class HttpHandlerConnector implements ClientHttpConnector { @@ -64,6 +64,8 @@ public class HttpHandlerConnector implements ClientHttpConnector {
private final HttpHandler handler;
private boolean applyAttributes = true;
/**
* Constructor with the {@link HttpHandler} to handle requests with.
@ -82,6 +84,16 @@ public class HttpHandlerConnector implements ClientHttpConnector { @@ -82,6 +84,16 @@ public class HttpHandlerConnector implements ClientHttpConnector {
.subscribeOn(Schedulers.parallel());
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
private Mono<ClientHttpResponse> doConnect(
HttpMethod httpMethod, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {

7
spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

@ -425,6 +425,13 @@ public interface WebTestClient { @@ -425,6 +425,13 @@ public interface WebTestClient {
*/
Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Global option to specify whether or not attributes should be applied to every request,
* if the used {@link ClientHttpConnector} allows it.
* @param applyAttributes whether or not to apply attributes
*/
Builder applyAttributes(boolean applyAttributes);
/**
* Add the given filter to the filter chain.
* @param filter the filter to be added to the chain

12
spring-test/src/main/java/org/springframework/test/web/reactive/server/WiretapConnector.java

@ -55,6 +55,8 @@ class WiretapConnector implements ClientHttpConnector { @@ -55,6 +55,8 @@ class WiretapConnector implements ClientHttpConnector {
private final Map<String, ClientExchangeInfo> exchanges = new ConcurrentHashMap<>();
private boolean applyAttributes = true;
WiretapConnector(ClientHttpConnector delegate) {
this.delegate = delegate;
@ -84,6 +86,16 @@ class WiretapConnector implements ClientHttpConnector { @@ -84,6 +86,16 @@ class WiretapConnector implements ClientHttpConnector {
});
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
/**
* Create the {@link ExchangeResult} for the given "request-id" header value.
*/

12
spring-test/src/main/java/org/springframework/test/web/servlet/client/MockMvcHttpConnector.java

@ -86,6 +86,8 @@ public class MockMvcHttpConnector implements ClientHttpConnector { @@ -86,6 +86,8 @@ public class MockMvcHttpConnector implements ClientHttpConnector {
private final List<RequestPostProcessor> requestPostProcessors;
private boolean applyAttributes = true;
public MockMvcHttpConnector(MockMvc mockMvc) {
this(mockMvc, Collections.emptyList());
@ -115,6 +117,16 @@ public class MockMvcHttpConnector implements ClientHttpConnector { @@ -115,6 +117,16 @@ public class MockMvcHttpConnector implements ClientHttpConnector {
}
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
private RequestBuilder adaptRequest(
HttpMethod httpMethod, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {

18
spring-test/src/test/java/org/springframework/test/web/reactive/server/WiretapConnectorTests.java

@ -18,6 +18,7 @@ package org.springframework.test.web.reactive.server; @@ -18,6 +18,7 @@ package org.springframework.test.web.reactive.server;
import java.net.URI;
import java.time.Duration;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@ -48,7 +49,22 @@ public class WiretapConnectorTests { @@ -48,7 +49,22 @@ public class WiretapConnectorTests {
public void captureAndClaim() {
ClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
ClientHttpResponse response = new MockClientHttpResponse(HttpStatus.OK);
ClientHttpConnector connector = (method, uri, fn) -> fn.apply(request).then(Mono.just(response));
ClientHttpConnector connector = new ClientHttpConnector() {
@Override
public Mono<ClientHttpResponse> connect(HttpMethod method, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
return requestCallback.apply(request).then(Mono.just(response));
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
}
@Override
public boolean getApplyAttributes() {
return false;
}
};
ClientRequest clientRequest = ClientRequest.create(HttpMethod.GET, URI.create("/test"))
.header(WebTestClient.WEBTESTCLIENT_REQUEST_ID, "1").build();

34
spring-web/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java

@ -17,7 +17,10 @@ @@ -17,7 +17,10 @@
package org.springframework.http.client.reactive;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@ -55,6 +58,10 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @@ -55,6 +58,10 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private final MultiValueMap<String, HttpCookie> cookies;
private final Map<String, Object> attributes;
private final boolean applyAttributes;
private final AtomicReference<State> state = new AtomicReference<>(State.NEW);
private final List<Supplier<? extends Publisher<Void>>> commitActions = new ArrayList<>(4);
@ -64,13 +71,19 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @@ -64,13 +71,19 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
public AbstractClientHttpRequest() {
this(new HttpHeaders());
this(new HttpHeaders(), false);
}
public AbstractClientHttpRequest(boolean applyAttributes) {
this(new HttpHeaders(), applyAttributes);
}
public AbstractClientHttpRequest(HttpHeaders headers) {
public AbstractClientHttpRequest(HttpHeaders headers, boolean applyAttributes) {
Assert.notNull(headers, "HttpHeaders must not be null");
this.headers = headers;
this.cookies = new LinkedMultiValueMap<>();
this.attributes = new LinkedHashMap<>();
this.applyAttributes = applyAttributes;
}
@ -106,6 +119,14 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @@ -106,6 +119,14 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
return this.cookies;
}
@Override
public Map<String, Object> getAttributes() {
if (State.COMMITTED.equals(this.state.get())) {
return Collections.unmodifiableMap(this.attributes);
}
return this.attributes;
}
@Override
public void beforeCommit(Supplier<? extends Mono<Void>> action) {
Assert.notNull(action, "Action must not be null");
@ -140,6 +161,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @@ -140,6 +161,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
Mono.fromRunnable(() -> {
applyHeaders();
applyCookies();
if (this.applyAttributes) {
applyAttributes();
}
this.state.set(State.COMMITTED);
}));
@ -168,4 +192,10 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { @@ -168,4 +192,10 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
*/
protected abstract void applyCookies();
/**
* Add additional attributes from {@link #getAttributes()} to the underlying request.
* This method is called once only.
*/
protected abstract void applyAttributes();
}

10
spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpConnector.java

@ -48,4 +48,14 @@ public interface ClientHttpConnector { @@ -48,4 +48,14 @@ public interface ClientHttpConnector {
Mono<ClientHttpResponse> connect(HttpMethod method, URI uri,
Function<? super ClientHttpRequest, Mono<Void>> requestCallback);
/**
* Set whether or not attributes should be applied to the underlying http-client library request.
*/
void setApplyAttributes(boolean applyAttributes);
/**
* Whether or not attributes should be applied to the underlying http-client library request.
*/
boolean getApplyAttributes();
}

6
spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.http.client.reactive;
import java.net.URI;
import java.util.Map;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
@ -54,4 +55,9 @@ public interface ClientHttpRequest extends ReactiveHttpOutputMessage { @@ -54,4 +55,9 @@ public interface ClientHttpRequest extends ReactiveHttpOutputMessage {
*/
<T> T getNativeRequest();
/**
* Return a mutable map of the request attributes.
*/
Map<String, Object> getAttributes();
}

6
spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpRequestDecorator.java

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.http.client.reactive;
import java.net.URI;
import java.util.Map;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
@ -85,6 +86,11 @@ public class ClientHttpRequestDecorator implements ClientHttpRequest { @@ -85,6 +86,11 @@ public class ClientHttpRequestDecorator implements ClientHttpRequest {
return this.delegate.getNativeRequest();
}
@Override
public Map<String, Object> getAttributes() {
return this.delegate.getAttributes();
}
@Override
public void beforeCommit(Supplier<? extends Mono<Void>> action) {
this.delegate.beforeCommit(action);

17
spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpConnector.java

@ -59,6 +59,7 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C @@ -59,6 +59,7 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
private DataBufferFactory dataBufferFactory = DefaultDataBufferFactory.sharedInstance;
private boolean applyAttributes = true;
/**
* Default constructor that creates and starts a new instance of {@link CloseableHttpAsyncClient}.
@ -67,6 +68,7 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C @@ -67,6 +68,7 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
this(HttpAsyncClients.createDefault());
}
/**
* Constructor with a pre-configured {@link CloseableHttpAsyncClient} instance.
* @param client the client to use
@ -111,11 +113,22 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C @@ -111,11 +113,22 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
context.setCookieStore(new BasicCookieStore());
}
HttpComponentsClientHttpRequest request =
new HttpComponentsClientHttpRequest(method, uri, context, this.dataBufferFactory);
HttpComponentsClientHttpRequest request = new HttpComponentsClientHttpRequest(
method, uri, context, this.dataBufferFactory, this.applyAttributes);
return requestCallback.apply(request).then(Mono.defer(() -> execute(request, context)));
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
private Mono<ClientHttpResponse> execute(HttpComponentsClientHttpRequest request, HttpClientContext context) {
AsyncRequestProducer requestProducer = request.toRequestProducer();

16
spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java

@ -66,8 +66,8 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { @@ -66,8 +66,8 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
public HttpComponentsClientHttpRequest(HttpMethod method, URI uri, HttpClientContext context,
DataBufferFactory dataBufferFactory) {
DataBufferFactory dataBufferFactory, boolean applyAttributes) {
super(applyAttributes);
this.context = context;
this.httpRequest = new BasicHttpRequest(method.name(), uri);
this.dataBufferFactory = dataBufferFactory;
@ -157,6 +157,18 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { @@ -157,6 +157,18 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
});
}
/**
* Applies the attributes to the {@link HttpClientContext}.
*/
@Override
protected void applyAttributes() {
getAttributes().forEach((key, value) -> {
if(this.context.getAttribute(key) == null) {
this.context.setAttribute(key, value);
}
});
}
@Override
protected HttpHeaders initReadOnlyHeaders() {
return HttpHeaders.readOnlyHttpHeaders(new HttpComponentsHeadersAdapter(this.httpRequest));

17
spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpConnector.java

@ -96,7 +96,7 @@ public class JdkClientHttpConnector implements ClientHttpConnector { @@ -96,7 +96,7 @@ public class JdkClientHttpConnector implements ClientHttpConnector {
public Mono<ClientHttpResponse> connect(
HttpMethod method, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
JdkClientHttpRequest jdkClientHttpRequest = new JdkClientHttpRequest(method, uri, this.bufferFactory);
JdkClientHttpRequest jdkClientHttpRequest = new JdkClientHttpRequest(method, uri, this.bufferFactory, getApplyAttributes());
return requestCallback.apply(jdkClientHttpRequest).then(Mono.defer(() -> {
HttpRequest httpRequest = jdkClientHttpRequest.getNativeRequest();
@ -109,4 +109,19 @@ public class JdkClientHttpConnector implements ClientHttpConnector { @@ -109,4 +109,19 @@ public class JdkClientHttpConnector implements ClientHttpConnector {
}));
}
/**
* Sets nothing, since {@link JdkClientHttpConnector} does not offer any possibility to add attributes.
*/
@Override
public void setApplyAttributes(boolean applyAttributes) {
}
/**
* Returns false, since {@link JdkClientHttpConnector} does not offer any possibility to add attributes.
*/
@Override
public boolean getApplyAttributes() {
return false;
}
}

12
spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java

@ -56,7 +56,8 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest { @@ -56,7 +56,8 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest {
private final HttpRequest.Builder builder;
public JdkClientHttpRequest(HttpMethod httpMethod, URI uri, DataBufferFactory bufferFactory) {
public JdkClientHttpRequest(HttpMethod httpMethod, URI uri, DataBufferFactory bufferFactory, boolean applyAttributes) {
super(applyAttributes);
Assert.notNull(httpMethod, "HttpMethod is required");
Assert.notNull(uri, "URI is required");
Assert.notNull(bufferFactory, "DataBufferFactory is required");
@ -112,6 +113,15 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest { @@ -112,6 +113,15 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest {
.flatMap(List::stream).map(HttpCookie::toString).collect(Collectors.joining(";")));
}
/**
* Not implemented, since {@link HttpRequest} does not offer any possibility to add request attributes.
*/
@Override
protected void applyAttributes() {
// TODO
throw new RuntimeException(String.format("Using attributes is not available for %s", HttpRequest.class.getName()));
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return doCommit(() -> {

14
spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpConnector.java

@ -52,6 +52,8 @@ public class JettyClientHttpConnector implements ClientHttpConnector { @@ -52,6 +52,8 @@ public class JettyClientHttpConnector implements ClientHttpConnector {
private DataBufferFactory bufferFactory = DefaultDataBufferFactory.sharedInstance;
private boolean applyAttributes = true;
/**
* Default constructor that creates a new instance of {@link HttpClient}.
@ -126,11 +128,21 @@ public class JettyClientHttpConnector implements ClientHttpConnector { @@ -126,11 +128,21 @@ public class JettyClientHttpConnector implements ClientHttpConnector {
}
Request jettyRequest = this.httpClient.newRequest(uri).method(method.toString());
JettyClientHttpRequest request = new JettyClientHttpRequest(jettyRequest, this.bufferFactory);
JettyClientHttpRequest request = new JettyClientHttpRequest(jettyRequest, this.bufferFactory, getApplyAttributes());
return requestCallback.apply(request).then(execute(request));
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
private Mono<ClientHttpResponse> execute(JettyClientHttpRequest request) {
return Mono.fromDirect(request.toReactiveRequest()
.response((reactiveResponse, chunkPublisher) -> {

11
spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java

@ -55,7 +55,8 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { @@ -55,7 +55,8 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
private final ReactiveRequest.Builder builder;
public JettyClientHttpRequest(Request jettyRequest, DataBufferFactory bufferFactory) {
public JettyClientHttpRequest(Request jettyRequest, DataBufferFactory bufferFactory, boolean applyAttributes) {
super(applyAttributes);
this.jettyRequest = jettyRequest;
this.bufferFactory = bufferFactory;
this.builder = ReactiveRequest.newBuilder(this.jettyRequest).abortOnCancel(true);
@ -138,6 +139,14 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { @@ -138,6 +139,14 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
.forEach(this.jettyRequest::cookie);
}
/**
* Applies the attributes to {@link Request#getAttributes()}.
*/
@Override
protected void applyAttributes() {
getAttributes().forEach(this.jettyRequest::attribute);
}
@Override
protected void applyHeaders() {
HttpHeaders headers = getHeaders();

13
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java

@ -66,6 +66,7 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif @@ -66,6 +66,7 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif
private final Object lifecycleMonitor = new Object();
private boolean applyAttributes = true;
/**
* Default constructor. Initializes {@link HttpClient} via:
@ -170,10 +171,20 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif @@ -170,10 +171,20 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif
return requestSender.uri(uri.toString());
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
private ReactorClientHttpRequest adaptRequest(HttpMethod method, URI uri, HttpClientRequest request,
NettyOutbound nettyOutbound) {
return new ReactorClientHttpRequest(method, uri, request, nettyOutbound);
return new ReactorClientHttpRequest(method, uri, request, nettyOutbound, this.applyAttributes);
}
@Override

19
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java

@ -18,13 +18,16 @@ package org.springframework.http.client.reactive; @@ -18,13 +18,16 @@ package org.springframework.http.client.reactive;
import java.net.URI;
import java.nio.file.Path;
import java.util.Map;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.util.AttributeKey;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.NettyOutbound;
import reactor.netty.channel.ChannelOperations;
import reactor.netty.http.client.HttpClientRequest;
import org.springframework.core.io.buffer.DataBuffer;
@ -45,6 +48,8 @@ import org.springframework.http.support.Netty4HeadersAdapter; @@ -45,6 +48,8 @@ import org.springframework.http.support.Netty4HeadersAdapter;
*/
class ReactorClientHttpRequest extends AbstractClientHttpRequest implements ZeroCopyHttpOutputMessage {
public static final String ATTRIBUTES_CHANNEL_KEY = "attributes";
private final HttpMethod httpMethod;
private final URI uri;
@ -56,7 +61,8 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero @@ -56,7 +61,8 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero
private final NettyDataBufferFactory bufferFactory;
public ReactorClientHttpRequest(HttpMethod method, URI uri, HttpClientRequest request, NettyOutbound outbound) {
public ReactorClientHttpRequest(HttpMethod method, URI uri, HttpClientRequest request, NettyOutbound outbound, boolean applyAttributes) {
super(applyAttributes);
this.httpMethod = method;
this.uri = uri;
this.request = request;
@ -135,6 +141,17 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero @@ -135,6 +141,17 @@ class ReactorClientHttpRequest extends AbstractClientHttpRequest implements Zero
}));
}
/**
* Applies the request attributes to the {@link reactor.netty.http.client.HttpClientRequest} by setting
* a single {@link Map} into the {@link reactor.netty.channel.ChannelOperations#channel()},
* with {@link io.netty5.util.AttributeKey#name()} equal to {@link #ATTRIBUTES_CHANNEL_KEY}.
*/
@Override
protected void applyAttributes() {
((ChannelOperations<?, ?>) this.request)
.channel().attr(AttributeKey.valueOf(ATTRIBUTES_CHANNEL_KEY)).set(getAttributes());
}
@Override
protected HttpHeaders initReadOnlyHeaders() {
return HttpHeaders.readOnlyHttpHeaders(new Netty4HeadersAdapter(this.request.requestHeaders()));

14
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpConnector.java

@ -46,6 +46,8 @@ public class ReactorNetty2ClientHttpConnector implements ClientHttpConnector { @@ -46,6 +46,8 @@ public class ReactorNetty2ClientHttpConnector implements ClientHttpConnector {
private final HttpClient httpClient;
private boolean applyAttributes = true;
/**
* Default constructor. Initializes {@link HttpClient} via:
@ -126,10 +128,20 @@ public class ReactorNetty2ClientHttpConnector implements ClientHttpConnector { @@ -126,10 +128,20 @@ public class ReactorNetty2ClientHttpConnector implements ClientHttpConnector {
});
}
@Override
public void setApplyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
}
@Override
public boolean getApplyAttributes() {
return this.applyAttributes;
}
private ReactorNetty2ClientHttpRequest adaptRequest(HttpMethod method, URI uri, HttpClientRequest request,
NettyOutbound nettyOutbound) {
return new ReactorNetty2ClientHttpRequest(method, uri, request, nettyOutbound);
return new ReactorNetty2ClientHttpRequest(method, uri, request, nettyOutbound, getApplyAttributes());
}
}

19
spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java

@ -18,13 +18,16 @@ package org.springframework.http.client.reactive; @@ -18,13 +18,16 @@ package org.springframework.http.client.reactive;
import java.net.URI;
import java.nio.file.Path;
import java.util.Map;
import io.netty5.buffer.Buffer;
import io.netty5.handler.codec.http.headers.DefaultHttpCookiePair;
import io.netty5.util.AttributeKey;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty5.NettyOutbound;
import reactor.netty5.channel.ChannelOperations;
import reactor.netty5.http.client.HttpClientRequest;
import org.springframework.core.io.buffer.DataBuffer;
@ -46,6 +49,8 @@ import org.springframework.http.support.Netty5HeadersAdapter; @@ -46,6 +49,8 @@ import org.springframework.http.support.Netty5HeadersAdapter;
*/
class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implements ZeroCopyHttpOutputMessage {
public static final String ATTRIBUTES_CHANNEL_KEY = "attributes";
private final HttpMethod httpMethod;
private final URI uri;
@ -57,7 +62,8 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement @@ -57,7 +62,8 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement
private final Netty5DataBufferFactory bufferFactory;
public ReactorNetty2ClientHttpRequest(HttpMethod method, URI uri, HttpClientRequest request, NettyOutbound outbound) {
public ReactorNetty2ClientHttpRequest(HttpMethod method, URI uri, HttpClientRequest request, NettyOutbound outbound, boolean applyAttributes) {
super(applyAttributes);
this.httpMethod = method;
this.uri = uri;
this.request = request;
@ -136,6 +142,17 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement @@ -136,6 +142,17 @@ class ReactorNetty2ClientHttpRequest extends AbstractClientHttpRequest implement
}));
}
/**
* Applies the request attributes to the {@link reactor.netty.http.client.HttpClientRequest} by setting
* a single {@link Map} into the {@link reactor.netty.channel.ChannelOperations#channel()},
* with {@link AttributeKey#name()} equal to {@link #ATTRIBUTES_CHANNEL_KEY}.
*/
@Override
protected void applyAttributes() {
((ChannelOperations<?, ?>) this.request)
.channel().attr(AttributeKey.valueOf(ATTRIBUTES_CHANNEL_KEY)).set(getAttributes());
}
@Override
protected HttpHeaders initReadOnlyHeaders() {
return HttpHeaders.readOnlyHttpHeaders(new Netty5HeadersAdapter(this.request.requestHeaders()));

4
spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/client/reactive/MockClientHttpRequest.java

@ -121,6 +121,10 @@ public class MockClientHttpRequest extends AbstractClientHttpRequest implements @@ -121,6 +121,10 @@ public class MockClientHttpRequest extends AbstractClientHttpRequest implements
.forEach(cookie -> getHeaders().add(HttpHeaders.COOKIE, cookie.toString()));
}
@Override
protected void applyAttributes() {
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return doCommit(() -> Mono.defer(() -> this.writeHandler.apply(Flux.from(body))));

6
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java

@ -265,6 +265,12 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder { @@ -265,6 +265,12 @@ final class DefaultClientRequestBuilder implements ClientRequest.Builder {
requestCookies.add(name, cookie);
}));
}
Map<String, Object> requestAttributes = request.getAttributes();
if (!this.attributes.isEmpty()) {
this.attributes.forEach((key, value) -> requestAttributes.put(key, value));
}
if (this.httpRequestConsumer != null) {
this.httpRequestConsumer.accept(request);
}

20
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java

@ -89,6 +89,8 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @@ -89,6 +89,8 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
@Nullable
private MultiValueMap<String, String> defaultCookies;
private boolean applyAttributes;
@Nullable
private Consumer<WebClient.RequestHeadersSpec<?>> defaultRequest;
@ -137,6 +139,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @@ -137,6 +139,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
this.defaultCookies = (other.defaultCookies != null ?
new LinkedMultiValueMap<>(other.defaultCookies) : null);
this.applyAttributes = other.applyAttributes;
this.defaultRequest = other.defaultRequest;
this.statusHandlers = (other.statusHandlers != null ? new LinkedHashMap<>(other.statusHandlers) : null);
this.filters = (other.filters != null ? new ArrayList<>(other.filters) : null);
@ -200,6 +203,12 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @@ -200,6 +203,12 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
return this;
}
@Override
public WebClient.Builder applyAttributes(boolean applyAttributes) {
this.applyAttributes = applyAttributes;
return this;
}
private MultiValueMap<String, String> initCookies() {
if (this.defaultCookies == null) {
this.defaultCookies = new LinkedMultiValueMap<>(3);
@ -335,21 +344,24 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @@ -335,21 +344,24 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
}
private ClientHttpConnector initConnector() {
final ClientHttpConnector connector;
if (reactorNettyClientPresent) {
return new ReactorClientHttpConnector();
connector = new ReactorClientHttpConnector();
}
else if (reactorNetty2ClientPresent) {
return new ReactorNetty2ClientHttpConnector();
}
else if (jettyClientPresent) {
return new JettyClientHttpConnector();
connector = new JettyClientHttpConnector();
}
else if (httpComponentsClientPresent) {
return new HttpComponentsClientHttpConnector();
connector = new HttpComponentsClientHttpConnector();
}
else {
return new JdkClientHttpConnector();
connector = new JdkClientHttpConnector();
}
connector.setApplyAttributes(this.applyAttributes);
return connector;
}
private ExchangeStrategies initExchangeStrategies() {

7
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java

@ -250,6 +250,13 @@ public interface WebClient { @@ -250,6 +250,13 @@ public interface WebClient {
*/
Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Global option to specify whether or not the request attributes should be applied
* to the underlying http-client request, if the used {@link ClientHttpConnector} allows it.
* @param applyAttributes whether or not to apply the attributes
*/
Builder applyAttributes(boolean applyAttributes);
/**
* Provide a consumer to customize every request being built.
* @param defaultRequest the consumer to use for modifying requests

66
spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

@ -34,14 +34,18 @@ import java.time.Duration; @@ -34,14 +34,18 @@ import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.eclipse.jetty.client.Request;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
@ -50,6 +54,7 @@ import org.junit.jupiter.params.provider.MethodSource; @@ -50,6 +54,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.netty.channel.ChannelOperations;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.test.StepVerifier;
@ -186,6 +191,67 @@ class WebClientIntegrationTests { @@ -186,6 +191,67 @@ class WebClientIntegrationTests {
});
}
@ParameterizedWebClientTest
void applyAttributesInNativeRequest(ClientHttpConnector connector) {
startServer(connector);
connector.setApplyAttributes(true);
checkAttributesInNativeRequest(true);
}
@ParameterizedWebClientTest
void dontApplyAttributesInNativeRequest(ClientHttpConnector connector) {
startServer(connector);
connector.setApplyAttributes(false);
checkAttributesInNativeRequest(false);
}
private void checkAttributesInNativeRequest(boolean expectAttributesApplied){
prepareResponse(response -> {});
final AtomicReference<Object> nativeRequest = new AtomicReference<>();
Mono<Void> result = this.webClient.get()
.uri("/pojo")
.attribute("foo","bar")
.httpRequest(clientHttpRequest -> nativeRequest.set(clientHttpRequest.getNativeRequest()))
.retrieve()
.bodyToMono(Void.class);
StepVerifier.create(result)
.expectComplete()
.verify();
if (nativeRequest.get() instanceof ChannelOperations<?,?> nativeReq) {
Attribute<Map<String, Object>> attributes = nativeReq.channel().attr(AttributeKey.valueOf("attributes"));
if (expectAttributesApplied) {
assertThat(attributes.get()).isNotNull();
assertThat(attributes.get()).containsEntry("foo", "bar");
}
else {
assertThat(attributes.get()).isNull();
}
}
else if (nativeRequest.get() instanceof reactor.netty5.channel.ChannelOperations<?,?> nativeReq) {
io.netty5.util.Attribute<Map<String, Object>> attributes = nativeReq.channel().attr(io.netty5.util.AttributeKey.valueOf("attributes"));
if (expectAttributesApplied) {
assertThat(attributes.get()).isNotNull();
assertThat(attributes.get()).containsEntry("foo", "bar");
}
else {
assertThat(attributes.get()).isNull();
}
}
else if (nativeRequest.get() instanceof Request nativeReq) {
if (expectAttributesApplied) {
assertThat(nativeReq.getAttributes()).containsEntry("foo", "bar");
}
else {
assertThat(nativeReq.getAttributes()).doesNotContainEntry("foo", "bar");
}
}
else if (nativeRequest.get() instanceof org.apache.hc.core5.http.HttpRequest nativeReq) {
// TODO get attributes from HttpClientContext
}
}
@ParameterizedWebClientTest
void retrieveJsonWithParameterizedTypeReference(ClientHttpConnector connector) {
startServer(connector);

Loading…
Cancel
Save