Align Reactive WebClient with web.reactive.function

This commit refactors the web client to be more similar to
web.reactive.function. Changes include:

- Refactor ClientWebRequest to immutable ClientRequest with builder and
   support for BodyInserters.
- Introduce ClientResponse which exposes headers, status, and support
   for reading from the body with BodyExtractors.
- Removed ResponseErrorHandler, in favor of having a ClientResponse
   with "error" status code (i.e. 4xx or 5xx). Also removed
   WebClientException and subclasses.
- Refactored WebClientConfig to WebClientStrategies.
- Refactored ClientHttpRequestInterceptor to ExchangeFilterFunction.
- Removed ClientWebRequestPostProcessor in favor of
   ExchangeFilterFunction, which allows for asynchronous execution.

Issue: SPR-14827
This commit is contained in:
Arjen Poutsma
2016-10-20 12:13:07 +02:00
parent dc1926a861
commit 0cfb6b37f2
43 changed files with 2191 additions and 2447 deletions

View File

@@ -26,13 +26,13 @@ import reactor.core.publisher.Mono;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.BodyExtractors;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.tests.TestSubscriber;
import org.springframework.web.client.reactive.ClientRequest;
import org.springframework.web.client.reactive.WebClient;
import static org.springframework.http.codec.BodyInserters.fromServerSentEvents;
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream;
import static org.springframework.web.reactive.function.RouterFunctions.route;
/**
@@ -41,11 +41,13 @@ import static org.springframework.web.reactive.function.RouterFunctions.route;
public class SseHandlerFunctionIntegrationTests
extends AbstractRouterFunctionIntegrationTests {
private static final MediaType EVENT_STREAM = new MediaType("text", "event-stream");
private WebClient webClient;
@Before
public void createWebClient() {
this.webClient = new WebClient(new ReactorClientHttpConnector());
this.webClient = WebClient.create(new ReactorClientHttpConnector());
}
@Override
@@ -59,10 +61,15 @@ public class SseHandlerFunctionIntegrationTests
@Test
public void sseAsString() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/string", this.port)
.accept(EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.perform(get("http://localhost:" + port + "/string")
.accept(new MediaType("text", "event-stream")))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> (s.replace("\n", "")))
.take(2);
@@ -75,10 +82,15 @@ public class SseHandlerFunctionIntegrationTests
@Test
public void sseAsPerson() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/person", this.port)
.accept(EVENT_STREAM)
.build();
Mono<String> result = this.webClient
.perform(get("http://localhost:" + port + "/person")
.accept(new MediaType("text", "event-stream")))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> s.replace("\n", ""))
.takeUntil(s -> s.endsWith("foo 1\"}"))
@@ -92,10 +104,15 @@ public class SseHandlerFunctionIntegrationTests
@Test
public void sseAsEvent() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/event", this.port)
.accept(EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.perform(get("http://localhost:" + port + "/event")
.accept(new MediaType("text", "event-stream")))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> s.replace("\n", ""))
.take(2);
@@ -107,7 +124,9 @@ public class SseHandlerFunctionIntegrationTests
"id:0:bardata:foo",
"id:1:bardata:foo"
);
;
}
private static class SseHandler {
public Response<Publisher<String>> string(Request request) {
@@ -177,5 +196,4 @@ public class SseHandlerFunctionIntegrationTests
}
}
}

View File

@@ -28,34 +28,38 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.BodyExtractors;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.tests.TestSubscriber;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.reactive.ClientRequest;
import org.springframework.web.client.reactive.WebClient;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.EnableWebReactive;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream;
/**
* @author Sebastien Deleuze
*/
public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private static final MediaType EVENT_STREAM = new MediaType("text", "event-stream");
private AnnotationConfigApplicationContext wac;
private WebClient webClient;
@Override
@Before
public void setup() throws Exception {
super.setup();
this.webClient = new WebClient(new ReactorClientHttpConnector());
this.webClient = WebClient.create(new ReactorClientHttpConnector());
}
@@ -70,10 +74,15 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsString() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/string", this.port)
.accept(EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.perform(get("http://localhost:" + port + "/sse/string")
.accept(new MediaType("text", "event-stream")))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> (s.replace("\n", "")))
.take(2);
@@ -83,13 +92,17 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
.await(Duration.ofSeconds(5))
.assertValues("data:foo 0", "data:foo 1");
}
@Test
public void sseAsPerson() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/person", this.port)
.accept(EVENT_STREAM)
.build();
Mono<String> result = this.webClient
.perform(get("http://localhost:" + port + "/sse/person")
.accept(new MediaType("text", "event-stream")))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> s.replace("\n", ""))
.takeUntil(s -> s.endsWith("foo 1\"}"))
@@ -103,10 +116,14 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsEvent() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/event", this.port)
.accept(EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.perform(get("http://localhost:" + port + "/sse/event")
.accept(new MediaType("text", "event-stream")))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> s.replace("\n", ""))
.take(2);
@@ -122,9 +139,15 @@ public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Test
public void sseAsEventWithoutAcceptHeader() throws Exception {
ClientRequest<Void> request =
ClientRequest
.GET("http://localhost:{port}/sse/event", this.port)
.accept(EVENT_STREAM)
.build();
Flux<String> result = this.webClient
.perform(get("http://localhost:" + port + "/sse/event"))
.extract(bodyStream(String.class))
.exchange(request)
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.filter(s -> !s.equals("\n"))
.map(s -> s.replace("\n", ""))
.take(2);