How can I detect a disconnected client with Spring 5 Reactive WebSocket

此生再无相见时 提交于 2020-05-13 11:48:52

问题


I managed to create a WebSocketHandler using the Spring 5 Reactive WebSocket support (Chapter 23.2.4). Receiving and sending all works fine. However, I can not figure out how to detect a client disconnect. When debugging a client disconnect it somewhere stops at the server side in the HttpServerWSOperations class (netty.http.server package), where it does detect a CloseWebSocketFrame.

Any suggestions how to deal with client disconnects?


回答1:


I implemented a close event handler in a reactive org.springframework.web.reactive.socket.WebSocketHandler as follows:

public Mono<Void> handle(final WebSocketSession session) {
    final String sessionId = session.getId();
    if(sessions.add(sessionId)) {  // add session id to set to keep a count of active sessions
        LOG.info("Starting WebSocket Session [{}]", sessionId);
        // Send the session id back to the client
        WebSocketMessage msg = session.textMessage(String.format("{\"session\":\"%s\"}", sessionId));
        // Register the outbound flux as the source of outbound messages
        final Flux<WebSocketMessage> outFlux = Flux.concat(Flux.just(msg), newMetricFlux.map(metric -> {
            LOG.info("Sending message to client [{}]: {}", sessionId, metric);
            return session.textMessage(metric);             
        }));
        // Subscribe to the inbound message flux
        session.receive().doFinally(sig -> {
            LOG.info("Terminating WebSocket Session (client side) sig: [{}], [{}]", sig.name(), sessionId);
            session.close();
            sessions.remove(sessionId);  // remove the stored session id
        }).subscribe(inMsg -> {
            LOG.info("Received inbound message from client [{}]: {}", sessionId, inMsg.getPayloadAsText());
        });
        return session.send(outFlux);
    }
    return Mono.empty();
}

The newMetricFlux field is the source of the outbound websocket messages. The trick to hooking the close event is the doFinally on the inbound message flux. When the websocket client closes, the inbound flux is terminated.

For some reason, though, there is a 1 minute delay between when the netty channel closes and the doFinally callback is executed. Not sure why yet.

Here's the log output for a browser client connecting and immediately closing. Note the 60 second delay between lines 3 and 4.

2017-08-03 11:15:41.177 DEBUG 28505 --- [ctor-http-nio-2] r.i.n.http.server.HttpServerOperations   : New http connection, requesting read
2017-08-03 11:15:41.294  INFO 28505 --- [ctor-http-nio-2] c.h.w.ws.NewMetricsWebSocketHandler      : Starting WebSocket Session [87fbe66]
2017-08-03 11:15:48.294 DEBUG 28505 --- [ctor-http-nio-2] r.i.n.http.server.HttpServerOperations   : CloseWebSocketFrame detected. Closing Websocket
2017-08-03 11:16:48.293  INFO 28505 --- [ctor-http-nio-2] c.h.w.ws.NewMetricsWebSocketHandler      : Terminating WebSocket Session (client side) sig: [ON_COMPLETE], [87fbe66]

Update: October 13, 2017:

As of Spring 5 GA, the delay mentioned above is not present and I observe my callback being invoked immediately after the client is closed. Not sure which version this was fixed in, but as I said, it's fixed in 5.0 GA.




回答2:


You can detect that thanks to the afterConnectionClosed method. You can even handle transport errors thanks to the method handleTransportError. Here is a code snippet:

@Component
public class YourHandler extends TextWebSocketHandler {

    private final static Logger logger = LoggerFactory.getLogger(YourHandler .class);
    private List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        logger.debug("Connected : " + session);
        sessions.add(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        try {
            //check the close status if you want...
            if (!status.equals(CloseStatus.NORMAL)) {
                session.close();
            }
        } catch (IOException e) {
            logger.error("Cannot close session on afterConnectionClosed ", e);
        }
        sessions.remove(session);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        logger.debug("error has occured with the following session {}", session);
        try {
            session.close();
        } catch (IOException e) {
            logger.error("Cannot close session on handleTransportError ", e);
        }
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        logger.debug("Receive : " + message.getPayload());      
    }
}


来源:https://stackoverflow.com/questions/45038008/how-can-i-detect-a-disconnected-client-with-spring-5-reactive-websocket

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!