Browse Source
Add SubProtocolHandler to encapsulate the logic for using a sub-protocol. A SubProtocolWebSocketHandler is also provided to delegate to the appropriate SubProtocolHandler based on the negotiated sub-protocol value at handshake. StompSubProtocolHandler provides handling for STOMP messages. Issue: SPR-10786pull/319/merge
10 changed files with 532 additions and 341 deletions
@ -0,0 +1,94 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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 |
||||||
|
* |
||||||
|
* http://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.messaging.handler.websocket; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.springframework.messaging.Message; |
||||||
|
import org.springframework.messaging.MessageChannel; |
||||||
|
import org.springframework.web.socket.CloseStatus; |
||||||
|
import org.springframework.web.socket.WebSocketMessage; |
||||||
|
import org.springframework.web.socket.WebSocketSession; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A contract for handling WebSocket messages as part of a higher level protocol, referred |
||||||
|
* to as "sub-protocol" in the WebSocket RFC specification. Handles both |
||||||
|
* {@link WebSocketMessage}s from a client as well as {@link Message}s to a client. |
||||||
|
* <p> |
||||||
|
* Implementations of this interface can be configured on a |
||||||
|
* {@link SubProtocolWebSocketHandler} which selects a sub-protocol handler to delegate |
||||||
|
* messages to based on the sub-protocol requested by the client through the |
||||||
|
* {@code Sec-WebSocket-Protocol} request header. |
||||||
|
* |
||||||
|
* @author Andy Wilkinson |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public interface SubProtocolHandler { |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the list of sub-protocols supported by this handler, never {@code null}. |
||||||
|
*/ |
||||||
|
List<String> getSupportedProtocols(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Handle the given {@link WebSocketMessage} received from a client. |
||||||
|
* |
||||||
|
* @param session the client session |
||||||
|
* @param message the client message |
||||||
|
* @param outputChannel an output channel to send messages to |
||||||
|
*/ |
||||||
|
void handleMessageFromClient(WebSocketSession session, WebSocketMessage message, |
||||||
|
MessageChannel outputChannel) throws Exception; |
||||||
|
|
||||||
|
/** |
||||||
|
* Handle the given {@link Message} to the client associated with the given WebSocket |
||||||
|
* session. |
||||||
|
* |
||||||
|
* @param session the client session |
||||||
|
* @param message the client message |
||||||
|
*/ |
||||||
|
void handleMessageToClient(WebSocketSession session, Message<?> message) throws Exception; |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolve the session id from the given message or return {@code null}. |
||||||
|
* |
||||||
|
* @param the message to resolve the session id from |
||||||
|
*/ |
||||||
|
String resolveSessionId(Message<?> message); |
||||||
|
|
||||||
|
/** |
||||||
|
* Invoked after a {@link WebSocketSession} has started. |
||||||
|
* |
||||||
|
* @param session the client session |
||||||
|
* @param outputChannel a channel |
||||||
|
*/ |
||||||
|
void afterSessionStarted(WebSocketSession session, MessageChannel outputChannel) throws Exception; |
||||||
|
|
||||||
|
/** |
||||||
|
* Invoked after a {@link WebSocketSession} has ended. |
||||||
|
* |
||||||
|
* @param session the client session |
||||||
|
* @param closeStatus the reason why the session was closed |
||||||
|
* @param outputChannel a channel |
||||||
|
*/ |
||||||
|
void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus, |
||||||
|
MessageChannel outputChannel) throws Exception; |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,208 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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 |
||||||
|
* |
||||||
|
* http://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.messaging.handler.websocket; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.TreeMap; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.messaging.Message; |
||||||
|
import org.springframework.messaging.MessageChannel; |
||||||
|
import org.springframework.messaging.MessageHandler; |
||||||
|
import org.springframework.messaging.MessagingException; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.CollectionUtils; |
||||||
|
import org.springframework.web.socket.CloseStatus; |
||||||
|
import org.springframework.web.socket.WebSocketHandler; |
||||||
|
import org.springframework.web.socket.WebSocketMessage; |
||||||
|
import org.springframework.web.socket.WebSocketSession; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A {@link WebSocketHandler} that delegates messages to a {@link SubProtocolHandler} |
||||||
|
* based on the sub-protocol value requested by the client through the |
||||||
|
* {@code Sec-WebSocket-Protocol} request header A default handler can also be configured |
||||||
|
* to use if the client does not request a specific sub-protocol. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @author Andy Wilkinson |
||||||
|
* |
||||||
|
* @since 4.0 |
||||||
|
*/ |
||||||
|
public class SubProtocolWebSocketHandler implements WebSocketHandler, MessageHandler { |
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(SubProtocolWebSocketHandler.class); |
||||||
|
|
||||||
|
private final MessageChannel outputChannel; |
||||||
|
|
||||||
|
private final Map<String, SubProtocolHandler> protocolHandlers = |
||||||
|
new TreeMap<String, SubProtocolHandler>(String.CASE_INSENSITIVE_ORDER); |
||||||
|
|
||||||
|
private SubProtocolHandler defaultProtocolHandler; |
||||||
|
|
||||||
|
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<String, WebSocketSession>(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* @param outputChannel |
||||||
|
*/ |
||||||
|
public SubProtocolWebSocketHandler(MessageChannel outputChannel) { |
||||||
|
Assert.notNull(outputChannel, "outputChannel is required"); |
||||||
|
this.outputChannel = outputChannel; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Configure one or more handlers to use depending on the sub-protocol requested by |
||||||
|
* the client in the WebSocket handshake request. |
||||||
|
* |
||||||
|
* @param protocolHandlers the sub-protocol handlers to use |
||||||
|
*/ |
||||||
|
public void setProtocolHandlers(List<SubProtocolHandler> protocolHandlers) { |
||||||
|
this.protocolHandlers.clear(); |
||||||
|
for (SubProtocolHandler handler: protocolHandlers) { |
||||||
|
List<String> protocols = handler.getSupportedProtocols(); |
||||||
|
if (CollectionUtils.isEmpty(protocols)) { |
||||||
|
logger.warn("No sub-protocols, ignoring handler " + handler); |
||||||
|
continue; |
||||||
|
} |
||||||
|
for (String protocol: protocols) { |
||||||
|
SubProtocolHandler replaced = this.protocolHandlers.put(protocol, handler); |
||||||
|
if (replaced != null) { |
||||||
|
throw new IllegalStateException("Failed to map handler " + handler |
||||||
|
+ " to protocol '" + protocol + "', it is already mapped to handler " + replaced); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if ((this.protocolHandlers.size() == 1) &&(this.defaultProtocolHandler == null)) { |
||||||
|
this.defaultProtocolHandler = this.protocolHandlers.values().iterator().next(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the configured sub-protocol handlers |
||||||
|
*/ |
||||||
|
public Map<String, SubProtocolHandler> getProtocolHandlers() { |
||||||
|
return this.protocolHandlers; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the {@link SubProtocolHandler} to use when the client did not request a |
||||||
|
* sub-protocol. |
||||||
|
* |
||||||
|
* @param defaultProtocolHandler the default handler |
||||||
|
*/ |
||||||
|
public void setDefaultProtocolHandler(SubProtocolHandler defaultProtocolHandler) { |
||||||
|
this.defaultProtocolHandler = defaultProtocolHandler; |
||||||
|
if (this.protocolHandlers.isEmpty()) { |
||||||
|
setProtocolHandlers(Arrays.asList(defaultProtocolHandler)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the default sub-protocol handler to use |
||||||
|
*/ |
||||||
|
public SubProtocolHandler getDefaultProtocolHandler() { |
||||||
|
return this.defaultProtocolHandler; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
||||||
|
this.sessions.put(session.getId(), session); |
||||||
|
getProtocolHandler(session).afterSessionStarted(session, this.outputChannel); |
||||||
|
} |
||||||
|
|
||||||
|
protected final SubProtocolHandler getProtocolHandler(WebSocketSession session) { |
||||||
|
SubProtocolHandler handler; |
||||||
|
String protocol = session.getAcceptedProtocol(); |
||||||
|
if (protocol != null) { |
||||||
|
handler = this.protocolHandlers.get(protocol); |
||||||
|
Assert.state(handler != null, |
||||||
|
"No handler for sub-protocol '" + protocol + "', handlers=" + this.protocolHandlers); |
||||||
|
} |
||||||
|
else { |
||||||
|
handler = this.defaultProtocolHandler; |
||||||
|
Assert.state(handler != null, |
||||||
|
"No sub-protocol was requested and a default sub-protocol handler was not configured"); |
||||||
|
} |
||||||
|
return handler; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { |
||||||
|
getProtocolHandler(session).handleMessageFromClient(session, message, this.outputChannel); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void handleMessage(Message<?> message) throws MessagingException { |
||||||
|
|
||||||
|
String sessionId = resolveSessionId(message); |
||||||
|
if (sessionId == null) { |
||||||
|
logger.error("sessionId not found in message " + message); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
WebSocketSession session = this.sessions.get(sessionId); |
||||||
|
if (session == null) { |
||||||
|
logger.error("Session not found for session with id " + sessionId); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
getProtocolHandler(session).handleMessageToClient(session, message); |
||||||
|
} |
||||||
|
catch (Exception e) { |
||||||
|
logger.error("Failed to send message to client " + message, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String resolveSessionId(Message<?> message) { |
||||||
|
for (SubProtocolHandler handler : this.protocolHandlers.values()) { |
||||||
|
String sessionId = handler.resolveSessionId(message); |
||||||
|
if (sessionId != null) { |
||||||
|
return sessionId; |
||||||
|
} |
||||||
|
} |
||||||
|
if (this.defaultProtocolHandler != null) { |
||||||
|
String sessionId = this.defaultProtocolHandler.resolveSessionId(message); |
||||||
|
if (sessionId != null) { |
||||||
|
return sessionId; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { |
||||||
|
this.sessions.remove(session.getId()); |
||||||
|
getProtocolHandler(session).afterSessionEnded(session, closeStatus, this.outputChannel); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean supportsPartialMessages() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,4 +1,4 @@ |
|||||||
/** |
/** |
||||||
* Generic support for simple messaging protocols (like STOMP). |
* Generic support for SImple Messaging Protocols such as STOMP. |
||||||
*/ |
*/ |
||||||
package org.springframework.messaging.simp; |
package org.springframework.messaging.simp; |
||||||
|
|||||||
@ -0,0 +1,4 @@ |
|||||||
|
/** |
||||||
|
* Generic support for simple messaging protocols (like STOMP). |
||||||
|
*/ |
||||||
|
package org.springframework.messaging.simp.stomp; |
||||||
@ -0,0 +1,127 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2013 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 |
||||||
|
* |
||||||
|
* http://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 CONDITIOsNS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.messaging.handler.websocket; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.MockitoAnnotations; |
||||||
|
import org.springframework.messaging.MessageChannel; |
||||||
|
import org.springframework.web.socket.support.TestWebSocketSession; |
||||||
|
|
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture for {@link SubProtocolWebSocketHandler}. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @author Andy Wilkinson |
||||||
|
*/ |
||||||
|
public class SubProtocolWebSocketHandlerTests { |
||||||
|
|
||||||
|
private SubProtocolWebSocketHandler webSocketHandler; |
||||||
|
|
||||||
|
private TestWebSocketSession session; |
||||||
|
|
||||||
|
@Mock |
||||||
|
SubProtocolHandler stompHandler; |
||||||
|
|
||||||
|
@Mock |
||||||
|
SubProtocolHandler mqttHandler; |
||||||
|
|
||||||
|
@Mock |
||||||
|
SubProtocolHandler defaultHandler; |
||||||
|
|
||||||
|
@Mock |
||||||
|
MessageChannel channel; |
||||||
|
|
||||||
|
|
||||||
|
@Before |
||||||
|
public void setup() { |
||||||
|
MockitoAnnotations.initMocks(this); |
||||||
|
|
||||||
|
this.webSocketHandler = new SubProtocolWebSocketHandler(this.channel); |
||||||
|
when(stompHandler.getSupportedProtocols()).thenReturn(Arrays.asList("STOMP")); |
||||||
|
when(mqttHandler.getSupportedProtocols()).thenReturn(Arrays.asList("MQTT")); |
||||||
|
|
||||||
|
this.session = new TestWebSocketSession(); |
||||||
|
this.session.setId("1"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void subProtocolMatch() throws Exception { |
||||||
|
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler)); |
||||||
|
this.session.setAcceptedProtocol("sToMp"); |
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
|
||||||
|
verify(this.stompHandler).afterSessionStarted(session, this.channel); |
||||||
|
verify(this.mqttHandler, times(0)).afterSessionStarted(session, this.channel); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void subProtocolDefaultHandlerOnly() throws Exception { |
||||||
|
this.webSocketHandler.setDefaultProtocolHandler(stompHandler); |
||||||
|
this.session.setAcceptedProtocol("sToMp"); |
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
|
||||||
|
verify(this.stompHandler).afterSessionStarted(session, this.channel); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected=IllegalStateException.class) |
||||||
|
public void subProtocolNoMatch() throws Exception { |
||||||
|
this.webSocketHandler.setDefaultProtocolHandler(defaultHandler); |
||||||
|
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler)); |
||||||
|
this.session.setAcceptedProtocol("wamp"); |
||||||
|
|
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void noSubProtocol() throws Exception { |
||||||
|
this.webSocketHandler.setDefaultProtocolHandler(defaultHandler); |
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
|
||||||
|
verify(this.defaultHandler).afterSessionStarted(session, this.channel); |
||||||
|
verify(this.stompHandler, times(0)).afterSessionStarted(session, this.channel); |
||||||
|
verify(this.mqttHandler, times(0)).afterSessionStarted(session, this.channel); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void noSubProtocolOneHandler() throws Exception { |
||||||
|
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler)); |
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
|
||||||
|
verify(this.stompHandler).afterSessionStarted(session, this.channel); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected=IllegalStateException.class) |
||||||
|
public void noSubProtocolTwoHandlers() throws Exception { |
||||||
|
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler)); |
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected=IllegalStateException.class) |
||||||
|
public void noSubProtocolNoDefaultHandler() throws Exception { |
||||||
|
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler)); |
||||||
|
this.webSocketHandler.afterConnectionEstablished(session); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,125 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2013 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 |
|
||||||
* |
|
||||||
* http://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.web.socket.support; |
|
||||||
|
|
||||||
import java.util.Collections; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
import org.springframework.util.Assert; |
|
||||||
import org.springframework.web.socket.CloseStatus; |
|
||||||
import org.springframework.web.socket.WebSocketHandler; |
|
||||||
import org.springframework.web.socket.WebSocketMessage; |
|
||||||
import org.springframework.web.socket.WebSocketSession; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* A {@link WebSocketHandler} that delegates to other {@link WebSocketHandler} instances |
|
||||||
* based on the sub-protocol value accepted at the handshake. A default handler can also |
|
||||||
* be configured for use by default when a sub-protocol value if the WebSocket session |
|
||||||
* does not have a sub-protocol value associated with it. |
|
||||||
* |
|
||||||
* @author Rossen Stoyanchev |
|
||||||
* @since 4.0 |
|
||||||
*/ |
|
||||||
public class MultiProtocolWebSocketHandler implements WebSocketHandler { |
|
||||||
|
|
||||||
private WebSocketHandler defaultHandler; |
|
||||||
|
|
||||||
private Map<String, WebSocketHandler> handlers = new HashMap<String, WebSocketHandler>(); |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Configure {@link WebSocketHandler}'s to use by sub-protocol. The values for |
|
||||||
* sub-protocols are case insensitive. |
|
||||||
*/ |
|
||||||
public void setProtocolHandlers(Map<String, WebSocketHandler> protocolHandlers) { |
|
||||||
this.handlers.clear(); |
|
||||||
for (String protocol : protocolHandlers.keySet()) { |
|
||||||
this.handlers.put(protocol.toLowerCase(), protocolHandlers.get(protocol)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Return a read-only copy of the sub-protocol handler map. |
|
||||||
*/ |
|
||||||
public Map<String, WebSocketHandler> getProtocolHandlers() { |
|
||||||
return Collections.unmodifiableMap(this.handlers); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Set the default {@link WebSocketHandler} to use if a sub-protocol was not |
|
||||||
* requested. |
|
||||||
*/ |
|
||||||
public void setDefaultProtocolHandler(WebSocketHandler defaultHandler) { |
|
||||||
this.defaultHandler = defaultHandler; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Return the default {@link WebSocketHandler} to be used. |
|
||||||
*/ |
|
||||||
public WebSocketHandler getDefaultProtocolHandler() { |
|
||||||
return this.defaultHandler; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Override |
|
||||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception { |
|
||||||
WebSocketHandler handler = getHandlerForSession(session); |
|
||||||
handler.afterConnectionEstablished(session); |
|
||||||
} |
|
||||||
|
|
||||||
private WebSocketHandler getHandlerForSession(WebSocketSession session) { |
|
||||||
WebSocketHandler handler = null; |
|
||||||
String protocol = session.getAcceptedProtocol(); |
|
||||||
if (protocol != null) { |
|
||||||
handler = this.handlers.get(protocol.toLowerCase()); |
|
||||||
Assert.state(handler != null, |
|
||||||
"No WebSocketHandler for sub-protocol '" + protocol + "', handlers=" + this.handlers); |
|
||||||
} |
|
||||||
else { |
|
||||||
handler = this.defaultHandler; |
|
||||||
Assert.state(handler != null, |
|
||||||
"No sub-protocol was requested and no default WebSocketHandler was configured"); |
|
||||||
} |
|
||||||
return handler; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { |
|
||||||
WebSocketHandler handler = getHandlerForSession(session); |
|
||||||
handler.handleMessage(session, message); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { |
|
||||||
WebSocketHandler handler = getHandlerForSession(session); |
|
||||||
handler.handleTransportError(session, exception); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { |
|
||||||
WebSocketHandler handler = getHandlerForSession(session); |
|
||||||
handler.afterConnectionClosed(session, closeStatus); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public boolean supportsPartialMessages() { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,106 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2002-2013 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 |
|
||||||
* |
|
||||||
* http://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 CONDITIOsNS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
|
|
||||||
package org.springframework.web.socket.support; |
|
||||||
|
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
import org.junit.Before; |
|
||||||
import org.junit.Test; |
|
||||||
import org.mockito.Mock; |
|
||||||
import org.mockito.MockitoAnnotations; |
|
||||||
import org.springframework.web.socket.WebSocketHandler; |
|
||||||
|
|
||||||
import static org.mockito.Mockito.*; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Test fixture for {@link MultiProtocolWebSocketHandler}. |
|
||||||
* |
|
||||||
* @author Rossen Stoyanchev |
|
||||||
*/ |
|
||||||
public class MultiProtocolWebSocketHandlerTests { |
|
||||||
|
|
||||||
private MultiProtocolWebSocketHandler multiProtocolHandler; |
|
||||||
|
|
||||||
@Mock |
|
||||||
WebSocketHandler stompHandler; |
|
||||||
|
|
||||||
@Mock |
|
||||||
WebSocketHandler mqttHandler; |
|
||||||
|
|
||||||
@Mock |
|
||||||
WebSocketHandler defaultHandler; |
|
||||||
|
|
||||||
|
|
||||||
@Before |
|
||||||
public void setup() { |
|
||||||
|
|
||||||
MockitoAnnotations.initMocks(this); |
|
||||||
|
|
||||||
Map<String, WebSocketHandler> handlers = new HashMap<String, WebSocketHandler>(); |
|
||||||
handlers.put("STOMP", this.stompHandler); |
|
||||||
handlers.put("MQTT", this.mqttHandler); |
|
||||||
|
|
||||||
this.multiProtocolHandler = new MultiProtocolWebSocketHandler(); |
|
||||||
this.multiProtocolHandler.setProtocolHandlers(handlers); |
|
||||||
this.multiProtocolHandler.setDefaultProtocolHandler(this.defaultHandler); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Test |
|
||||||
public void subProtocol() throws Exception { |
|
||||||
|
|
||||||
TestWebSocketSession session = new TestWebSocketSession(); |
|
||||||
session.setAcceptedProtocol("sToMp"); |
|
||||||
|
|
||||||
this.multiProtocolHandler.afterConnectionEstablished(session); |
|
||||||
|
|
||||||
verify(this.stompHandler).afterConnectionEstablished(session); |
|
||||||
verifyZeroInteractions(this.mqttHandler); |
|
||||||
} |
|
||||||
|
|
||||||
@Test(expected=IllegalStateException.class) |
|
||||||
public void subProtocolNoMatch() throws Exception { |
|
||||||
|
|
||||||
TestWebSocketSession session = new TestWebSocketSession(); |
|
||||||
session.setAcceptedProtocol("wamp"); |
|
||||||
|
|
||||||
this.multiProtocolHandler.afterConnectionEstablished(session); |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
public void noSubProtocol() throws Exception { |
|
||||||
|
|
||||||
TestWebSocketSession session = new TestWebSocketSession(); |
|
||||||
|
|
||||||
this.multiProtocolHandler.afterConnectionEstablished(session); |
|
||||||
|
|
||||||
verify(this.defaultHandler).afterConnectionEstablished(session); |
|
||||||
verifyZeroInteractions(this.stompHandler, this.mqttHandler); |
|
||||||
} |
|
||||||
|
|
||||||
@Test(expected=IllegalStateException.class) |
|
||||||
public void noSubProtocolNoDefaultHandler() throws Exception { |
|
||||||
|
|
||||||
TestWebSocketSession session = new TestWebSocketSession(); |
|
||||||
|
|
||||||
this.multiProtocolHandler.setDefaultProtocolHandler(null); |
|
||||||
this.multiProtocolHandler.afterConnectionEstablished(session); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
Loading…
Reference in new issue