Problem:
@ReadOperation
on a custom actuator endpoint returning Flux
cancels the request after first element, the same code wrapped as a @GetMapping
of a @RestController
returns full response as expected. The workaround is to return Mono but its a bit weird.
Spring Boot Version 2.6.4 created via initializr with Webflux and Actuator
Code: Actuator Endpoint
@Endpoint(id = "demo")
@Component
public class DemoEndpoint {
@ReadOperation
public Flux<String> test() {
return Flux.range(0, 10).map(String::valueOf).log();
}
}
Demonstration Controller:
@RestController
public class DemoRestController {
@GetMapping
public Flux<String> test(){
return Flux.range(0,10).map(String::valueOf).log();
}
}
See sample project attached.
curl localhost:8080 -> 0123456789 curl localhost:8080/actuator/demo -> 0
Expected behavior: 2nd call delivers 0123456789
Log:
2022-03-07 14:35:55.185 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onSubscribe([Fuseable] FluxMapFuseable.MapFuseableSubscriber)
2022-03-07 14:35:55.186 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | request(1)
2022-03-07 14:35:55.187 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(0)
2022-03-07 14:35:55.201 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | request(127)
2022-03-07 14:35:55.201 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(1)
2022-03-07 14:35:55.201 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(2)
2022-03-07 14:35:55.202 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(3)
2022-03-07 14:35:55.202 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(4)
2022-03-07 14:35:55.202 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(5)
2022-03-07 14:35:55.202 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(6)
2022-03-07 14:35:55.203 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(7)
2022-03-07 14:35:55.203 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(8)
2022-03-07 14:35:55.203 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onNext(9)
2022-03-07 14:35:55.203 INFO 42052 --- [ctor-http-nio-2] reactor.Flux.MapFuseable.1 : | onComplete()
2022-03-07 14:36:45.169 INFO 42052 --- [ctor-http-nio-3] reactor.Flux.MapFuseable.2 : | onSubscribe([Fuseable] FluxMapFuseable.MapFuseableSubscriber)
2022-03-07 14:36:45.169 INFO 42052 --- [ctor-http-nio-3] reactor.Flux.MapFuseable.2 : | request(unbounded)
2022-03-07 14:36:45.169 INFO 42052 --- [ctor-http-nio-3] reactor.Flux.MapFuseable.2 : | onNext(0)
2022-03-07 14:36:45.169 INFO 42052 --- [ctor-http-nio-3] reactor.Flux.MapFuseable.2 : | cancel()
Comment From: wilkinsona
I spent a bit of time looking at this following the discussion on Gitter. Only a single element is returned due to this code:
https://github.com/spring-projects/spring-boot/blob/5960d2dba153e2550359ccb0551e10c34c0ad393/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/reactive/AbstractWebFluxEndpointHandlerMapping.java#L365
Mono.from(Publisher)
is documented as follows:
Expose the specified
Publisher
with theMono
API, and ensure it will emit 0 or 1 item. The source emitter will be cancelled on the firstonNext
.
There also appears to be problems with returning Flux
when using Spring MVC and Jersey as well.
I can't remember if it was a conscious decision to only support Mono
or if it's an oversight. If it was intentional, we should improve the documentation as we don't seem to say anything about supported return types at the moment. If it was an oversight, we'll have to decide if we want to support returning a Flux and, if we do, if we consider adding support to be a bug fix or an enhancement.