3

I am using the Spring 5 WebClient to repeatedly fetch some state of a running process from a REST api.

With help from here I for now came to this solution:

webClient.get().uri(...).retrieve.bodyToMono(State.class)
          .repeat()
          .skipUntil(state -> stateFinished())
          .limitRequest(1)
          .subscribe(state -> {...});

While this works, the get request is fired at a very high rate. What would be the right way to limit the request rate to let's say 1 request per second?

I tried using delayElements(Duration.ofSeconds(1)) but that only delays the results, not the request itself.

Rosso
  • 428
  • 7
  • 17

5 Answers5

6

You could use repeatWhen operator with your custom implementation of the companion Publisher

Mono.just("test")
        .repeatWhen(longFlux -> Flux.interval(Duration.ofSeconds(1)))
        .take(5)
        .log()
        .blockLast();

or with Repeate function from the reactor-addons

Mono.just("test")
        .repeatWhen(Repeat.times(Long.MAX_VALUE)
                .fixedBackoff(Duration.ofSeconds(1)))
        .take(5)
        .log()
        .blockLast();
Alexander Pankin
  • 3,787
  • 1
  • 13
  • 23
0

There is another little workaround in your case where you zip each of your call with a Flux used as a limiter.

.zipWith(Flux.interval(Duration.of(1, ChronoUnit.SECONDS)))

Although I would have thought that the delayElements() works maybe you didn't put it on the correct stage of your Webclient stack.

CharlieNoodles
  • 316
  • 2
  • 9
0

That you are using delayElements tells me you are putting it after the repeat. What you want to delay is the subscription to the WebClient.

webClient
      .get()
      .uri(...)
      .retrieve
      .bodyToMono(State.class)
      .delaySubscription(Duration.ofSeconds(1)) //Just add this before the repeat
      .repeat()
      .skipUntil(state -> stateFinished())
      .limitRequest(1)
      .subscribe(state -> {...});

Doing this ensures that there is a second between the response of the nth request and the triggering of the n+1th request. If you need fixed frequency of calls irrespective of the time taken by each request to response, wrap your code with a Flux.interval as suggested by Roman.

Rajesh J Advani
  • 5,585
  • 2
  • 23
  • 35
0

I found this answer the best suited for a similar problem I was facing.

getJobStatus()
.repeatWhen(completed -> completed.delayElements(Duration.ofMillis(pollDelay))) //(1)
.takeUntil(JobStatus::isDone) //(2)
.last() //(3)
Amritansh
  • 172
  • 2
  • 6
-1

Alternative solution to your problem

Flux.interval(Duration.ZERO, Duration.ofSeconds(1))
        .onBackpressureDrop()
        .concatMap(i -> webClientCall(...), 1)
        //or flatMap() if you want send request each second
        .filter(state -> stateFinished(state))
        .next()
        .timeout(Duration.ofSeconds(...))
        //
        .subscribe(state -> {...});

But remember, if you subscribe by your self (not by Spring) then reactor Subscriber Context would not be propagated to your request (no security context, sleuth, etc...)

Roman M.
  • 45
  • 1
  • 7