Spring Boot Webflux/Netty - Detect closed connection

后端 未结 2 1538
时光取名叫无心
时光取名叫无心 2020-12-31 13:57

I\'ve been working with spring-boot 2.0.0.RC1 using the webflux starter (spring-boot-starter-webflux). I created a simple controller that returns a

相关标签:
2条回答
  • 2020-12-31 14:39

    I'm not sure why this behaves like this, but I suspect it is because of the choice of generation operator. I think using the following would work:

        return Flux.interval(Duration.ofMillis(500))
        .map(input -> {
            return "DATA";
        });
    

    According to Reactor's reference documentation, you're probably hitting the key difference between generate and push (I believe a quite similar approach using generate would probably work as well).

    My comment was referring to the backpressure information (how many elements a Subscriber is willing to accept), but the success/error information is communicated over the network.

    Depending on your choice of web server (Reactor Netty, Tomcat, Jetty, etc), closing the client connection might result in:

    • a cancel signal being received on the server side (I think this is supported by Netty)
    • an error signal being received by the server when it's trying to write on a connection that's been closed (I believe the Servlet spec does not provide that that callback and we're missing the cancel information).

    In short: you don't need to do anything special, it should be supported already, but your Flux implementation might be the actual problem here.

    Update: this is a known issue in Reactor Netty

    0 讨论(0)
  • 2020-12-31 14:50

    Since this is a known issue (see Brian Clozel's answer), I ended up using one Flux to fetch my real data and having another one in order to implement some sort of ping/heartbeat mechanism. As a result, I merge both together with Flux.merge().

    Here you can see a simplified version of my solution:

    @RestController
    public class Demo {
    
        public interface Notification{}
    
        public static class MyData implements Notification{
            …
            public boolean isEmpty(){…}
        }
    
        @GetMapping(value = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
        public Flux<ServerSentEvent<? extends Notification>> getNotificationStream() {
            return Flux.merge(getEventMessageStream(), getHeartbeatStream());
        }
    
        private Flux<ServerSentEvent<Notification>> getHeartbeatStream() {
            return Flux.interval(Duration.ofSeconds(2))
                    .map(i -> ServerSentEvent.<Notification>builder().event("ping").build())
                    .doFinally(signalType ->System.out.println("END"));
        }
    
        private Flux<ServerSentEvent<MyData>> getEventMessageStream() {
            return Flux.interval(Duration.ofSeconds(30))
                    .map(i -> {
    
                        // TODO e.g. fetch data from somewhere,
                        // if there is no data return an empty object
    
                        return data;
                    })
                    .filter(data -> !data.isEmpty())
                    .map(data -> ServerSentEvent
                            .builder(data)
                            .event("message").build());
        }
    }
    

    I wrap everything up as ServerSentEvent<? extends Notification>. Notification is just a marker interface. I use the event field from the ServerSentEvent class in order to separate between data and ping events. Since the heartbeat Flux sends events constantly and in short intervals, the time it takes to detect that the client is gone is at most the length of that interval. Remember, I need that because it might take a while before I get some real data that can be sent and, as a result, it might also take a while before it detects that the client is gone. Like this, it will detect that the client is gone as soon as it can’t sent the ping (or possibly the message event).

    One last note on the marker interface, which I called Notification. This is not really necessary, but it gives some type safety. Without that, we could write Flux<ServerSentEvent<?>> instead of Flux<ServerSentEvent<? extends Notification>> as return type for the getNotificationStream() method. Or also possible, make getHeartbeatStream() return Flux<ServerSentEvent<MyData>>. However, like this it would allow that any object could be sent, which I don’t want. As a consequence, I added the interface.

    0 讨论(0)
提交回复
热议问题