Search code examples
spring-webfluxreactor-nettyspring-webclient

Is Spring webclient non-blocking client?


I don't understand reactive webclient works. It says that spring webclient is non-blocking client, but this webclient seems waiting signal onComplete() from remote api, then it can process each item that emitted from the remote api. I'm expecting that webclient can process each item when onNext() is fired from the target api

I'm new in the spring webflux worlds. I read about it and it says it uses netty as default server. And this netty using eventloop. So to understand how it works I try to create 2 small apps, client and server. Server app only return simple flux with delay 1 second each item. Client app using webclient to call remote api.

Server:

@GetMapping(ITEM_END_POINT_V1)
public Flux<Item> getAllItems(){
        return Flux.just(new Item(null, "Samsung TV", 399.99),
                new Item(null, "LG TV", 329.99),
                new Item(null, "Apple Watch", 349.99),
                new Item("ABC", "Beats HeadPhones", 
      149.99)).delayElements(Duration.ofSeconds(1)).log("Item : ");
}

Client:

WebClient webClient = WebClient.create("http://localhost:8080");

@GetMapping("/client/retrieve")
public Flux<Item> getAllItemsUsingRetrieve() {
        return webClient.get().uri("/v1/items")
                .retrieve()
                .bodyToFlux(Item.class).log();
}

Log from server:

2019-05-01 22:44:20.121  INFO 19644 --- [ctor-http-nio-2] Item :                                   : onSubscribe(FluxConcatMap.ConcatMapImmediate)
2019-05-01 22:44:20.122  INFO 19644 --- [ctor-http-nio-2] Item :                                   : request(unbounded)
2019-05-01 22:44:21.126  INFO 19644 --- [     parallel-1] Item :                                   : onNext(Item(id=null, description=Samsung TV, price=399.99))
2019-05-01 22:44:22.129  INFO 19644 --- [     parallel-2] Item :                                   : onNext(Item(id=null, description=LG TV, price=329.99))
2019-05-01 22:44:23.130  INFO 19644 --- [     parallel-3] Item :                                   : onNext(Item(id=null, description=Apple Watch, price=349.99))
2019-05-01 22:44:24.131  INFO 19644 --- [     parallel-4] Item :                                   : onNext(Item(id=ABC, description=Beats HeadPhones, price=149.99))
2019-05-01 22:44:24.132  INFO 19644 --- [     parallel-4] Item :                                   : onComplete()

Log from client:

2019-05-01 22:44:19.934  INFO 24164 --- [ctor-http-nio-2] reactor.Flux.MonoFlatMapMany.1           : onSubscribe(MonoFlatMapMany.FlatMapManyMain)
2019-05-01 22:44:19.936  INFO 24164 --- [ctor-http-nio-2] reactor.Flux.MonoFlatMapMany.1           : request(unbounded)
2019-05-01 22:44:19.940 TRACE 24164 --- [ctor-http-nio-2] o.s.w.r.f.client.ExchangeFunctions       : [7e73de5c] HTTP GET http://localhost:8080/v1/items, headers={}
2019-05-01 22:44:24.159 TRACE 24164 --- [ctor-http-nio-6] o.s.w.r.f.client.ExchangeFunctions       : [7e73de5c] Response 200 OK, headers={masked}
2019-05-01 22:44:24.204  INFO 24164 --- [ctor-http-nio-6] reactor.Flux.MonoFlatMapMany.1           : onNext(Item(id=null, description=Samsung TV, price=399.99))
2019-05-01 22:44:24.204  INFO 24164 --- [ctor-http-nio-6] reactor.Flux.MonoFlatMapMany.1           : onNext(Item(id=null, description=LG TV, price=329.99))
2019-05-01 22:44:24.204  INFO 24164 --- [ctor-http-nio-6] reactor.Flux.MonoFlatMapMany.1           : onNext(Item(id=null, description=Apple Watch, price=349.99))
2019-05-01 22:44:24.204  INFO 24164 --- [ctor-http-nio-6] reactor.Flux.MonoFlatMapMany.1           : onNext(Item(id=ABC, description=Beats HeadPhones, price=149.99))
2019-05-01 22:44:24.205  INFO 24164 --- [ctor-http-nio-6] reactor.Flux.MonoFlatMapMany.1           : onComplete()

I'm expecting that client won't wait for 4 seconds then get the actual result. As you can see that server start emit onNext() on 22:44:21.126, and client get result on 22:44:24.159. So I don't understand why webclient is called non-blocking client if it has this behaviour.


Solution

  • The WebClient is non-blocking in a sense that the thread sending HTTP requests through the WebClient is not blocked by the IO operation. When the response is available, netty will notify one of the worker threads and it will process the response according to the reactive stream operations that you defined.

    In your example the server will wait until all the elements in a Flux are available (4 seconds), serialize them to the JSON array, and send it back in a single HTTP response.

    The client waits for this single response, but non of its threads are blocked during this period.

    If you want to achieve the streaming effect, you need to leverage different content-type or the underlying protocol like WebSockets. Check-out the following SO thread about the application/stream+json content-type: Spring WebFlux Flux behavior with non streaming application/json