All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.integration.websocket.IntegrationWebSocketContainer Maven / Gradle / Ivy

/*
 * Copyright 2014-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.integration.websocket;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.SubProtocolCapable;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;

/**
 * The high-level 'connection factory pattern' contract over low-level Web-Socket
 * configuration.
 * 

* Provides the composition for the internal {@link WebSocketHandler} * implementation, which is used with native Web-Socket containers. *

* Collects established {@link WebSocketSession}s, which can be accessed using * {@link #getSession(String)}. *

* Can accept the {@link WebSocketListener} to delegate {@link WebSocketSession} events * from the internal {@link IntegrationWebSocketContainer.IntegrationWebSocketHandler}. *

* Supported sub-protocols can be configured, but {@link WebSocketListener#getSubProtocols()} * have a precedent. * * @author Artem Bilan * @author Gary Russell * @author Julian Koch * * @since 4.1 * * @see org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter * @see org.springframework.integration.websocket.outbound.WebSocketOutboundMessageHandler */ public abstract class IntegrationWebSocketContainer implements DisposableBean { public static final int DEFAULT_SEND_TIME_LIMIT = 10 * 1000; public static final int DEFAULT_SEND_BUFFER_SIZE = 512 * 1024; protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR protected final Lock lock = new ReentrantLock(); private WebSocketHandler webSocketHandler = new IntegrationWebSocketHandler(); protected final Map sessions = new ConcurrentHashMap<>(); // NOSONAR private final List supportedProtocols = new ArrayList<>(); private WebSocketListener messageListener; private int sendTimeLimit = DEFAULT_SEND_TIME_LIMIT; private int sendBufferSizeLimit = DEFAULT_SEND_BUFFER_SIZE; @Nullable private ConcurrentWebSocketSessionDecorator.OverflowStrategy sendBufferOverflowStrategy; public void setSendTimeLimit(int sendTimeLimit) { this.sendTimeLimit = sendTimeLimit; } public void setSendBufferSizeLimit(int sendBufferSizeLimit) { this.sendBufferSizeLimit = sendBufferSizeLimit; } /** * Set the send buffer overflow strategy. *

Concurrently generated outbound messages are buffered if sending is slow. * This strategy determines the behavior when the buffer has reached the limit * configured with {@link #setSendBufferSizeLimit}. * @param overflowStrategy The {@link ConcurrentWebSocketSessionDecorator.OverflowStrategy} to use. * @since 5.5.19 * @see ConcurrentWebSocketSessionDecorator */ public void setSendBufferOverflowStrategy( @Nullable ConcurrentWebSocketSessionDecorator.OverflowStrategy overflowStrategy) { this.sendBufferOverflowStrategy = overflowStrategy; } public void setMessageListener(WebSocketListener messageListener) { Assert.state(this.messageListener == null || this.messageListener.equals(messageListener), "'messageListener' is already configured"); this.messageListener = messageListener; } public void setSupportedProtocols(String... protocols) { this.supportedProtocols.clear(); addSupportedProtocols(protocols); } public void addSupportedProtocols(String... protocols) { for (String protocol : protocols) { this.supportedProtocols.add(protocol.toLowerCase(Locale.ROOT)); } } /** * Replace the default {@link WebSocketHandler} with the one provided here, e.g. via decoration factories. * @param handler the actual {@link WebSocketHandler} to replace. * @since 5.5.18 */ protected void setWebSocketHandler(WebSocketHandler handler) { this.webSocketHandler = handler; } public WebSocketHandler getWebSocketHandler() { return this.webSocketHandler; } public List getSubProtocols() { List protocols = new ArrayList<>(); if (this.messageListener != null) { protocols.addAll(this.messageListener.getSubProtocols()); } protocols.addAll(this.supportedProtocols); return Collections.unmodifiableList(protocols); } public Map getSessions() { return Collections.unmodifiableMap(this.sessions); } public WebSocketSession getSession(String sessionId) { WebSocketSession session = this.sessions.get(sessionId); Assert.notNull(session, () -> "Session not found for id '" + sessionId + "'"); return session; } public void closeSession(WebSocketSession session, CloseStatus closeStatus) throws Exception { // NOSONAR // Session may be unresponsive so clear first session.close(closeStatus); this.webSocketHandler.afterConnectionClosed(session, closeStatus); } @Override public void destroy() { try { // Notify sessions to stop flushing messages for (WebSocketSession session : this.sessions.values()) { try { session.close(CloseStatus.GOING_AWAY); } catch (Exception ex) { this.logger.error("Failed to close session id '" + session.getId() + "': " + ex.getMessage()); } } } finally { this.sessions.clear(); } } private WebSocketSession decorateSession(WebSocketSession sessionToDecorate) { if (this.sendBufferOverflowStrategy == null) { return new ConcurrentWebSocketSessionDecorator(sessionToDecorate, this.sendTimeLimit, this.sendBufferSizeLimit); } else { return new ConcurrentWebSocketSessionDecorator(sessionToDecorate, this.sendTimeLimit, this.sendBufferSizeLimit, this.sendBufferOverflowStrategy); } } /** * An internal {@link WebSocketHandler} implementation to be used with native * Web-Socket containers. *

* Delegates all operations to the wrapping {@link IntegrationWebSocketContainer} * and its {@link WebSocketListener}. */ private class IntegrationWebSocketHandler implements WebSocketHandler, SubProtocolCapable { IntegrationWebSocketHandler() { } @Override public List getSubProtocols() { return IntegrationWebSocketContainer.this.getSubProtocols(); } @Override public void afterConnectionEstablished(WebSocketSession sessionToDecorate) throws Exception { // NOSONAR WebSocketSession session = decorateSession(sessionToDecorate); IntegrationWebSocketContainer.this.sessions.put(session.getId(), session); if (IntegrationWebSocketContainer.this.logger.isDebugEnabled()) { IntegrationWebSocketContainer.this.logger.debug("Started WebSocket session = " + session.getId() + ", number of sessions = " + IntegrationWebSocketContainer.this.sessions.size()); } if (IntegrationWebSocketContainer.this.messageListener != null) { IntegrationWebSocketContainer.this.messageListener.afterSessionStarted(session); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { // NOSONAR WebSocketSession removed = IntegrationWebSocketContainer.this.sessions.remove(session.getId()); if (removed != null && IntegrationWebSocketContainer.this.messageListener != null) { IntegrationWebSocketContainer.this.messageListener.afterSessionEnded(session, closeStatus); } } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { // NOSONAR IntegrationWebSocketContainer.this.sessions.remove(session.getId()); ReflectionUtils.rethrowException(exception); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { // NOSONAR if (IntegrationWebSocketContainer.this.messageListener != null) { IntegrationWebSocketContainer.this.messageListener.onMessage(session, message); } else if (IntegrationWebSocketContainer.this.logger.isInfoEnabled()) { IntegrationWebSocketContainer.this.logger.info("This 'WebSocketHandlerContainer' isn't " + "configured with 'WebSocketMessageListener'." + " Received messages are ignored. Current message is: " + message); } } @Override public boolean supportsPartialMessages() { return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy