/** Handle an outbound Spring Message to a WebSocket client. */ @Override public void handleMessage(Message<?> message) throws MessagingException { String sessionId = resolveSessionId(message); if (sessionId == null) { logger.error("Couldn't find sessionId in " + message); return; } WebSocketSessionHolder holder = this.sessions.get(sessionId); if (holder == null) { if (logger.isDebugEnabled()) { // The broker may not have removed the session yet logger.debug("No session for " + message); } return; } WebSocketSession session = holder.getSession(); try { findProtocolHandler(session).handleMessageToClient(session, message); } catch (SessionLimitExceededException ex) { try { if (logger.isDebugEnabled()) { logger.debug("Terminating '" + session + "'", ex); } this.stats.incrementLimitExceededCount(); clearSession(session, ex.getStatus()); // clear first, session may be unresponsive session.close(ex.getStatus()); } catch (Exception secondException) { logger.debug("Failure while closing session " + sessionId + ".", secondException); } } catch (Exception e) { // Could be part of normal workflow (e.g. browser tab closed) logger.debug("Failed to send message to client in " + session + ": " + message, e); } }
/** * When a session is connected through a higher-level protocol it has a chance * to use heartbeat management to shut down sessions that are too slow to send * or receive messages. However, after a WebSocketSession is established and * before the higher level protocol is fully connected there is a possibility * for sessions to hang. This method checks and closes any sessions that have * been connected for more than 60 seconds without having received a single * message. */ private void checkSessions() throws IOException { long currentTime = System.currentTimeMillis(); if (!isRunning() || (currentTime - this.lastSessionCheckTime < TIME_TO_FIRST_MESSAGE)) { return; } if (this.sessionCheckLock.tryLock()) { try { for (WebSocketSessionHolder holder : this.sessions.values()) { if (holder.hasHandledMessages()) { continue; } long timeSinceCreated = currentTime - holder.getCreateTime(); if (timeSinceCreated < TIME_TO_FIRST_MESSAGE) { continue; } WebSocketSession session = holder.getSession(); if (logger.isErrorEnabled()) { logger.error("No messages received after " + timeSinceCreated + " ms. " + "Closing " + holder.getSession() + "."); } try { this.stats.incrementNoMessagesReceivedCount(); session.close(CloseStatus.SESSION_NOT_RELIABLE); } catch (Throwable t) { logger.error("Failure while closing " + session, t); } } } finally { this.sessionCheckLock.unlock(); } } }
/** * Handle an inbound message from a WebSocket client. */ @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { WebSocketSessionHolder holder = this.sessions.get(session.getId()); if (holder != null) { session = holder.getSession(); } SubProtocolHandler protocolHandler = findProtocolHandler(session); protocolHandler.handleMessageFromClient(session, message, this.clientInboundChannel); if (holder != null) { holder.setHasHandledMessages(); } checkSessions(); }
@Override public final void stop() { synchronized (this.lifecycleMonitor) { this.running = false; this.clientOutboundChannel.unsubscribe(this); for (WebSocketSessionHolder holder : this.sessions.values()) { try { holder.getSession().close(CloseStatus.GOING_AWAY); } catch (Throwable t) { logger.error("Failed to close '" + holder.getSession() + "': " + t.getMessage()); } } } }