Browse Source
It makes it possible to deploy multiple WebSocketHandler's to a URL, each supporting a different sub-protocol. Issue: SPR-10786pull/325/head
Rossen Stoyanchev
11 years ago
21 changed files with 463 additions and 27 deletions
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
/* |
||||
* 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; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
/* |
||||
* 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.server; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.Mock; |
||||
import org.mockito.MockitoAnnotations; |
||||
import org.springframework.web.socket.AbstractHttpRequestTests; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter; |
||||
|
||||
import static org.mockito.Mockito.*; |
||||
|
||||
|
||||
/** |
||||
* Test fixture for {@link DefaultHandshakeHandler}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class DefaultHandshakeHandlerTests extends AbstractHttpRequestTests { |
||||
|
||||
private DefaultHandshakeHandler handshakeHandler; |
||||
|
||||
@Mock |
||||
private RequestUpgradeStrategy upgradeStrategy; |
||||
|
||||
|
||||
@Before |
||||
public void setup() throws Exception { |
||||
MockitoAnnotations.initMocks(this); |
||||
this.handshakeHandler = new DefaultHandshakeHandler(this.upgradeStrategy); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void selectSubProtocol() throws Exception { |
||||
|
||||
this.handshakeHandler.setSupportedProtocols("stomp", "mqtt"); |
||||
|
||||
when(this.upgradeStrategy.getSupportedVersions()).thenReturn(new String[] { "13" }); |
||||
|
||||
this.servletRequest.setMethod("GET"); |
||||
this.request.getHeaders().setUpgrade("WebSocket"); |
||||
this.request.getHeaders().setConnection("Upgrade"); |
||||
this.request.getHeaders().setSecWebSocketVersion("13"); |
||||
this.request.getHeaders().setSecWebSocketKey("82/ZS2YHjEnUN97HLL8tbw=="); |
||||
this.request.getHeaders().setSecWebSocketProtocol("STOMP"); |
||||
|
||||
WebSocketHandler handler = new TextWebSocketHandlerAdapter(); |
||||
|
||||
this.handshakeHandler.doHandshake(this.request, this.response, handler); |
||||
|
||||
verify(this.upgradeStrategy).upgrade(request, response, "STOMP", handler); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
/* |
||||
* 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