Refactor HTTP client contracts
This commit refactors the `ClientHttpRequestFactory` into an `ClientHttpConnector` abstraction, in order to reflect that `ClientHttpRequest`s only "exist" once the client is connected to the origin server. This is why the HTTP client is now callback-based, containing all interactions with the request within a `Function<ClientHttpRequest,Mono<Void>>` that signals when it's done writing to the request. The `ClientHttpRequest` contract also adopts `setComplete()` and promotes that method to the `ReactiveHttpOutputMessage` contract. This commit also adapts all other APIs to that change and fixes a few issues, including: * use `HttpMessageConverter`s instead of `Encoders`/`Decoders` * better handle type information about request content publishers * support client cookies in HTTP requests * temporarily remove the RxNetty client support
This commit is contained in:
@@ -18,15 +18,16 @@ package org.springframework.http.server.reactive;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.springframework.web.client.reactive.HttpRequestBuilders.get;
|
||||
import static org.springframework.web.client.reactive.WebResponseExtractors.bodyStream;
|
||||
|
||||
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
|
||||
import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.FlushingDataBuffer;
|
||||
import org.springframework.http.client.reactive.ReactorHttpClientRequestFactory;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.client.reactive.WebClient;
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,7 @@ public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTest
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
this.webClient = new WebClient(new ReactorHttpClientRequestFactory());
|
||||
this.webClient = new WebClient(new ReactorClientHttpConnector());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,33 +16,41 @@
|
||||
|
||||
package org.springframework.web.client.reactive;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
public class DefaultHttpRequestBuilderTests {
|
||||
private DefaultHttpRequestBuilder builder;
|
||||
public class DefaultWebRequestBuilderTests {
|
||||
private DefaultClientWebRequestBuilder builder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
builder = new DefaultHttpRequestBuilder(HttpMethod.GET, "https://example.com/foo");
|
||||
builder = new DefaultClientWebRequestBuilder(HttpMethod.GET, "https://example.com/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply() {
|
||||
RequestPostProcessor postProcessor = mock(RequestPostProcessor.class);
|
||||
ClientWebRequestPostProcessor postProcessor = mock(ClientWebRequestPostProcessor.class);
|
||||
when(postProcessor.postProcess(any(ClientWebRequest.class))).thenAnswer(new Answer<ClientWebRequest>() {
|
||||
@Override
|
||||
public ClientWebRequest answer(InvocationOnMock invocation) throws Throwable {
|
||||
return (ClientWebRequest) invocation.getArguments()[0];
|
||||
}
|
||||
});
|
||||
|
||||
builder.apply(postProcessor);
|
||||
ClientWebRequest webRequest = builder.apply(postProcessor).build();
|
||||
|
||||
verify(postProcessor).postProcess(builder);
|
||||
verify(postProcessor).postProcess(webRequest);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@@ -17,8 +17,8 @@
|
||||
package org.springframework.web.client.reactive;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.client.reactive.HttpRequestBuilders.*;
|
||||
import static org.springframework.web.client.reactive.WebResponseExtractors.*;
|
||||
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.*;
|
||||
import static org.springframework.web.client.reactive.ResponseExtractors.*;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -38,9 +38,11 @@ import org.springframework.http.codec.Pojo;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ReactorHttpClientRequestFactory;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
|
||||
/**
|
||||
* {@link WebClient} integration tests with the {@code Flux} and {@code Mono} API.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class WebClientIntegrationTests {
|
||||
@@ -52,7 +54,7 @@ public class WebClientIntegrationTests {
|
||||
@Before
|
||||
public void setup() {
|
||||
this.server = new MockWebServer();
|
||||
this.webClient = new WebClient(new ReactorHttpClientRequestFactory());
|
||||
this.webClient = new WebClient(new ReactorClientHttpConnector());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -228,12 +230,14 @@ public class WebClientIntegrationTests {
|
||||
public void shouldPostPojoAsJson() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojo/capitalize");
|
||||
this.server.enqueue(new MockResponse().setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
|
||||
this.server.enqueue(new MockResponse()
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
|
||||
|
||||
Pojo spring = new Pojo("foofoo", "barbar");
|
||||
Mono<Pojo> result = this.webClient
|
||||
.perform(post(baseUrl.toString())
|
||||
.content(spring)
|
||||
.body(spring)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(Pojo.class));
|
||||
@@ -252,6 +256,28 @@ public class WebClientIntegrationTests {
|
||||
assertEquals("application/json", request.getHeader(HttpHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSendCookieHeader() throws Exception {
|
||||
HttpUrl baseUrl = server.url("/test");
|
||||
this.server.enqueue(new MockResponse()
|
||||
.setHeader("Content-Type", "text/plain").setBody("test"));
|
||||
|
||||
Mono<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.cookie("testkey", "testvalue"))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValues("test")
|
||||
.assertComplete();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
assertEquals(1, server.getRequestCount());
|
||||
assertEquals("/test", request.getPath());
|
||||
assertEquals("testkey=testvalue", request.getHeader(HttpHeaders.COOKIE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetErrorWhen404() throws Exception {
|
||||
|
||||
@@ -262,7 +288,6 @@ public class WebClientIntegrationTests {
|
||||
.perform(get(baseUrl.toString()))
|
||||
.extract(body(String.class));
|
||||
|
||||
// TODO: error message should be converted to a ClientException
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.await()
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.*;
|
||||
import static org.springframework.web.client.reactive.ResponseExtractors.*;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -29,14 +33,16 @@ import reactor.core.test.TestSubscriber;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.ByteBufferDecoder;
|
||||
import org.springframework.core.codec.ByteBufferEncoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.core.codec.StringEncoder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.http.codec.SseEventEncoder;
|
||||
import org.springframework.http.codec.json.JacksonJsonDecoder;
|
||||
import org.springframework.http.codec.json.JacksonJsonEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ReactorHttpClientRequestFactory;
|
||||
import org.springframework.http.codec.SseEventEncoder;
|
||||
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
|
||||
import org.springframework.http.converter.reactive.HttpMessageConverter;
|
||||
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
|
||||
@@ -49,9 +55,6 @@ import org.springframework.web.reactive.config.WebReactiveConfiguration;
|
||||
import org.springframework.web.reactive.sse.SseEvent;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.springframework.web.client.reactive.HttpRequestBuilders.get;
|
||||
import static org.springframework.web.client.reactive.WebResponseExtractors.bodyStream;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
@@ -64,11 +67,12 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
this.webClient = new WebClient(new ReactorHttpClientRequestFactory());
|
||||
this.webClient.setMessageDecoders(Arrays.asList(
|
||||
new ByteBufferDecoder(),
|
||||
new StringDecoder(false),
|
||||
new JacksonJsonDecoder()));
|
||||
this.webClient = new WebClient(new ReactorClientHttpConnector());
|
||||
List<HttpMessageConverter<?>> converters = new ArrayList<>();
|
||||
converters.add(new CodecHttpMessageConverter<>(new ByteBufferEncoder(), new ByteBufferDecoder()));
|
||||
converters.add(new CodecHttpMessageConverter<>(new StringEncoder(), new StringDecoder(false)));
|
||||
converters.add(new CodecHttpMessageConverter<>(new JacksonJsonEncoder(), new JacksonJsonDecoder()));
|
||||
this.webClient.setMessageConverters(converters);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user