2

I am using a third-party REST controller which accepts an array of JSON objects and returns a single object response. When I POST from a WebClient using a limited Flux the code works (I assume, because the Flux completes).

However, when the Flux is potentially unlimited, how do I;

  1. POST in chunks of arrays?
  2. Capture the response, per POSTed array?
  3. Stop the transmission of the Flux?

Here is my bean;

public class Car implements Serializable {

    Long id;

    public Car() {}
    public Car(Long id) { this.id = id; }
    public Long getId() {return id; }
    public void setId(Long id) { this.id = id; }
}

This is how I assume that the third-party client looks like;

@RestController
public class ThirdPartyServer {

    @PostMapping("/cars")
    public CarResponse doCars(@RequestBody List<Car> cars) {
        System.err.println("Got " + cars);
        return new CarResponse("OK");
    }
}

And here is my code. When I POST flux2 , on completion a JSON array is sent. However, when I POST flux1, nothing is sent after the first take(5). How do POST the next chunks of 5?

@Component
public class MyCarClient {

    public void sendCars() {

//      Flux<Car> flux1 = Flux.interval(Duration.ofMillis(250)).map(i -> new Car(i));
        Flux<Car> flux2 = Flux.range(1, 10).map(i -> new Car((long) i));

        WebClient client = WebClient.create("http://localhost:8080");
        client
            .post()
            .uri("/cars")
            .contentType(MediaType.APPLICATION_JSON)
            .body(flux2, Car.class) 
//          .body(flux1.take(5).collectList(), new ParameterizedTypeReference<List<Car>>() {})
            .exchange()
            .subscribe(r -> System.err.println(r.statusCode()));
    }
}
lafual
  • 683
  • 1
  • 7
  • 23

1 Answers1

3
  1. How do I POST in chunks of arrays?

Use one of the variants of Flux.window to split the main flux into windowed fluxes, and then send the requests using the windowed fluxes via .flatMap

        Flux<Car> flux1 = Flux.interval(Duration.ofMillis(250)).map(i -> new Car(i));

        WebClient client = WebClient.create("http://localhost:8080");
        Disposable disposable = flux1
                // 1
                .window(5)
                .flatMap(windowedFlux -> client
                        .post()
                        .uri("/cars")
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(windowedFlux, Car.class)
                        .exchange()
                        // 2
                        .doOnNext(response -> System.out.println(response.statusCode()))
                        .flatMap(response -> response.bodyToMono(...)))
                .subscribe();

        Thread.sleep(10000);

        // 3
        disposable.dispose();

  1. How do I capture the response, per POSTed array?

You can analyze the response via operators after .exchange().

In the example I provided, the response can be seen in the doOnNext operator, but you can use any operator that operates on onNext signals, such as map or handle.

Be sure to read the response body fully to ensure the connection is returned back to the pool (see note). Here, I have used .bodyToMono, but any .body or .toEntity method will work.

  1. Stop the transmission of the Flux?

When using the subscribe method as you have done, you can stop the flow using the returned disposable.dispose().

Alternatively, you can return the Flux from the sendCars() method and delegate the subscription and disposing to the caller.

Note that in the example I provided, I just used Thread.sleep() to simulate waiting. In a real application, you should use something more advanced, and avoid Thread.sleep()

Phil Clay
  • 4,028
  • 17
  • 22
  • 2nd time in two weeks that you have come to my rescue! I have a rather open ended question for you; How did you arrive at this answer? I have read the `Flux` and `WebClient` API documents many times as well as Googling for many examples, but I never got close to using the `WebClient` **within** the `flatMap`. I was assuming that all the `Flux` magic of breaking the stream into arrays would happen in the `.body()` method ! – lafual Apr 06 '19 at 18:41
  • Glad I could help! The `WebClient` `exchange()` method only represents a single request. And in order to identify the "end" of the JSON array to send in that request, the `body(...)` reads the `Flux` given to it fully (meaning it must complete before the request is completed). Therefore, I realized that the input `Flux` needed to be broken up into chucks (one chunk per request). And one way to break up a `Flux` into chunks is the `window(...)` methods. – Phil Clay Apr 07 '19 at 03:45
  • thanks, that should go to help me solve future problems. – lafual Apr 07 '19 at 09:50
  • Phil, you may want to amend the answer. When running the solution, the posting stops after a few iterations, because the response payload is not being read and the http connection is not being released, thus emptying the pool. You need to flatMap() after the exchange(). See the comment at the end of section 2.3 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web-reactive.html#webflux-client-exchange – lafual Apr 20 '19 at 18:55
  • Thanks. I've updated the answer to show one possible way (out of many) of reading the response. – Phil Clay Apr 20 '19 at 19:01