Add SubProtocolCapable interface

The addition of SubProtocolCapable simplifies configuration since it is
no longer necessary to explicitly configure DefaultHandshakeHandler
with a list of supported sub-protocols. We will not also check if the
WebSocketHandler to use for the WebSocket request is an instance of
SubProtocolCapable and obtain the list of sub-protocols that way. The
provided SubProtocolWebSocketHandler does implement this interface.

Issue: SPR-11111
This commit is contained in:
Rossen Stoyanchev
2013-11-25 18:02:35 -05:00
parent 59002f2456
commit 4e82416ba9
7 changed files with 161 additions and 52 deletions

View File

@@ -16,12 +16,7 @@
package org.springframework.web.socket.messaging;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
@@ -37,6 +32,7 @@ import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.support.SubProtocolCapable;
/**
@@ -55,7 +51,7 @@ import org.springframework.web.socket.WebSocketSession;
*
* @since 4.0
*/
public class SubProtocolWebSocketHandler implements WebSocketHandler, MessageHandler {
public class SubProtocolWebSocketHandler implements SubProtocolCapable, WebSocketHandler, MessageHandler {
private final Log logger = LogFactory.getLog(SubProtocolWebSocketHandler.class);
@@ -136,8 +132,8 @@ public class SubProtocolWebSocketHandler implements WebSocketHandler, MessageHan
/**
* Return all supported protocols.
*/
public Set<String> getSupportedProtocols() {
return this.protocolHandlers.keySet();
public List<String> getSubProtocols() {
return new ArrayList<String>(this.protocolHandlers.keySet());
}
@Override

View File

@@ -83,10 +83,9 @@ public class WebMvcStompEndpointRegistry implements StompEndpointRegistry {
public StompWebSocketEndpointRegistration addEndpoint(String... paths) {
this.subProtocolWebSocketHandler.addProtocolHandler(this.stompHandler);
Set<String> subProtocols = this.subProtocolWebSocketHandler.getSupportedProtocols();
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration(
paths, this.webSocketHandler, subProtocols, this.sockJsScheduler);
paths, this.webSocketHandler, this.sockJsScheduler);
this.registrations.add(registration);
return registration;

View File

@@ -46,8 +46,6 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
private final WebSocketHandler webSocketHandler;
private final String[] subProtocols;
private final TaskScheduler sockJsTaskScheduler;
private HandshakeHandler handshakeHandler;
@@ -56,28 +54,14 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
public WebMvcStompWebSocketEndpointRegistration(String[] paths, WebSocketHandler webSocketHandler,
Set<String> subProtocols, TaskScheduler sockJsTaskScheduler) {
TaskScheduler sockJsTaskScheduler) {
Assert.notEmpty(paths, "No paths specified");
Assert.notNull(webSocketHandler, "'webSocketHandler' is required");
Assert.notNull(subProtocols, "'subProtocols' is required");
this.paths = paths;
this.webSocketHandler = webSocketHandler;
this.subProtocols = subProtocols.toArray(new String[subProtocols.size()]);
this.sockJsTaskScheduler = sockJsTaskScheduler;
this.handshakeHandler = new DefaultHandshakeHandler();
updateHandshakeHandler();
}
private void updateHandshakeHandler() {
if (handshakeHandler instanceof DefaultHandshakeHandler) {
DefaultHandshakeHandler defaultHandshakeHandler = (DefaultHandshakeHandler) handshakeHandler;
if (ObjectUtils.isEmpty(defaultHandshakeHandler.getSupportedProtocols())) {
defaultHandshakeHandler.setSupportedProtocols(this.subProtocols);
}
}
}
/**
@@ -87,7 +71,6 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
public StompWebSocketEndpointRegistration setHandshakeHandler(HandshakeHandler handshakeHandler) {
Assert.notNull(handshakeHandler, "'handshakeHandler' must not be null");
this.handshakeHandler = handshakeHandler;
updateHandshakeHandler();
return this;
}
@@ -97,8 +80,10 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
@Override
public SockJsServiceRegistration withSockJS() {
this.registration = new StompSockJsServiceRegistration(this.sockJsTaskScheduler);
WebSocketTransportHandler transportHandler = new WebSocketTransportHandler(this.handshakeHandler);
this.registration.setTransportHandlerOverrides(transportHandler);
if (this.handshakeHandler != null) {
WebSocketTransportHandler transportHandler = new WebSocketTransportHandler(this.handshakeHandler);
this.registration.setTransportHandlerOverrides(transportHandler);
}
return this.registration;
}
@@ -114,8 +99,9 @@ public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketE
}
else {
for (String path : this.paths) {
WebSocketHttpRequestHandler handler =
new WebSocketHttpRequestHandler(this.webSocketHandler, this.handshakeHandler);
WebSocketHttpRequestHandler handler = (this.handshakeHandler != null) ?
new WebSocketHttpRequestHandler(this.webSocketHandler, this.handshakeHandler) :
new WebSocketHttpRequestHandler(this.webSocketHandler);
mappings.add(handler, path);
}
}

View File

@@ -32,8 +32,10 @@ import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.support.SubProtocolCapable;
import org.springframework.web.socket.support.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.support.WebSocketHandlerDecorator;
import org.springframework.web.socket.support.WebSocketHttpHeaders;
/**
@@ -122,10 +124,16 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
}
/**
* Use this property to configure a list of sub-protocols that are supported.
* The first protocol that matches what the client requested is selected.
* If no protocol matches or this property is not configured, then the
* response will not contain a Sec-WebSocket-Protocol header.
* Use this property to configure the list of supported sub-protocols.
* The first configured sub-protocol that matches a client-requested sub-protocol
* is accepted. If there are no matches the response will not contain a
* {@literal Sec-WebSocket-Protocol} header.
* <p>
* Note that if the WebSocketHandler passed in at runtime is an instance of
* {@link SubProtocolCapable} then there is not need to explicitly configure
* this property. That is certainly the case with the built-in STOMP over
* WebSocket support. Therefore this property should be configured explicitly
* only if the WebSocketHandler does not implement {@code SubProtocolCapable}.
*/
public void setSupportedProtocols(String... protocols) {
this.supportedProtocols.clear();
@@ -187,7 +195,10 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
"Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex);
}
String subProtocol = selectProtocol(headers.getSecWebSocketProtocol());
String subProtocol = selectProtocol(headers.getSecWebSocketProtocol(), wsHandler);
if (logger.isDebugEnabled()) {
logger.debug("Selected sub-protocol: '" + subProtocol + "'");
}
List<WebSocketExtension> requested = headers.getSecWebSocketExtensions();
List<WebSocketExtension> supported = this.requestUpgradeStrategy.getSupportedExtensions(request);
@@ -246,17 +257,32 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
return true;
}
protected String selectProtocol(List<String> requestedProtocols) {
/**
* Perform the sub-protocol negotiation based on requested and supported sub-protocols.
* For the list of supported sub-protocols, this method first checks if the target
* WebSocketHandler is a {@link SubProtocolCapable} and then also checks if any
* sub-protocols have been explicitly configured with
* {@link #setSupportedProtocols(String...)}.
*
* @param requestedProtocols the requested sub-protocols
* @param webSocketHandler the WebSocketHandler that will be used
* @return the selected protocols or {@code null}
*
* @see #determineHandlerSupportedProtocols(org.springframework.web.socket.WebSocketHandler)
*/
protected String selectProtocol(List<String> requestedProtocols, WebSocketHandler webSocketHandler) {
if (requestedProtocols != null) {
List<String> handlerProtocols = determineHandlerSupportedProtocols(webSocketHandler);
if (logger.isDebugEnabled()) {
logger.debug("Requested sub-protocol(s): " + requestedProtocols
+ ", supported sub-protocol(s): " + this.supportedProtocols);
logger.debug("Requested sub-protocol(s): " + requestedProtocols +
", WebSocketHandler supported sub-protocol(s): " + handlerProtocols +
", configured sub-protocol(s): " + this.supportedProtocols);
}
for (String protocol : requestedProtocols) {
if (handlerProtocols.contains(protocol.toLowerCase())) {
return protocol;
}
if (this.supportedProtocols.contains(protocol.toLowerCase())) {
if (logger.isDebugEnabled()) {
logger.debug("Selected sub-protocol: '" + protocol + "'");
}
return protocol;
}
}
@@ -264,6 +290,27 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
return null;
}
/**
* Determine the sub-protocols supported by the given WebSocketHandler by checking
* whether it is an instance of {@link SubProtocolCapable}.
*
* @param handler the handler to check
* @return a list of supported protocols or an empty list
*/
protected final List<String> determineHandlerSupportedProtocols(WebSocketHandler handler) {
List<String> subProtocols = null;
if (handler instanceof SubProtocolCapable) {
subProtocols = ((SubProtocolCapable) handler).getSubProtocols();
}
else if (handler instanceof WebSocketHandlerDecorator) {
WebSocketHandler lastHandler = ((WebSocketHandlerDecorator) handler).getLastHandler();
if (lastHandler instanceof SubProtocolCapable) {
subProtocols = ((SubProtocolCapable) lastHandler).getSubProtocols();;
}
}
return (subProtocols != null) ? subProtocols : Collections.<String>emptyList();
}
/**
* Filter the list of requested WebSocket extensions.
* <p>

View File

@@ -0,0 +1,19 @@
package org.springframework.web.socket.support;
import java.util.List;
/**
* An interface for WebSocket handlers that support sub-protocols as defined in RFC 6455.
*
* @author Rossen Stoyanchev
* @since 4.0
*
* @see <a href="http://tools.ietf.org/html/rfc6455#section-1.9">RFC-6455 section 1.9</a>
*/
public interface SubProtocolCapable {
/**
* Return the list of supported sub-protocols.
*/
List<String> getSubProtocols();
}