提交 4e82416b 编写于 作者: R Rossen Stoyanchev

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
上级 59002f24
......@@ -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
......
......@@ -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;
......
......@@ -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);
}
}
......
......@@ -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>
......
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();
}
......@@ -17,7 +17,6 @@
package org.springframework.web.socket.messaging.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -62,7 +61,7 @@ public class WebMvcStompEndpointRegistrationTests {
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration(
new String[] {"/foo"}, this.wsHandler, Collections.<String>emptySet(), this.scheduler);
new String[] {"/foo"}, this.wsHandler, this.scheduler);
MultiValueMap<HttpRequestHandler, String> mappings = registration.getMappings();
assertEquals(1, mappings.size());
......@@ -78,7 +77,7 @@ public class WebMvcStompEndpointRegistrationTests {
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler();
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration(
new String[] {"/foo"}, this.wsHandler, Collections.<String>emptySet(), this.scheduler);
new String[] {"/foo"}, this.wsHandler, this.scheduler);
registration.setHandshakeHandler(handshakeHandler);
......@@ -99,7 +98,7 @@ public class WebMvcStompEndpointRegistrationTests {
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler();
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration(
new String[] {"/foo"}, this.wsHandler, Collections.<String>emptySet(), this.scheduler);
new String[] {"/foo"}, this.wsHandler, this.scheduler);
registration.setHandshakeHandler(handshakeHandler);
registration.withSockJS();
......
......@@ -16,7 +16,9 @@
package org.springframework.web.socket.server;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
......@@ -24,11 +26,13 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.web.socket.AbstractHttpRequestTests;
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.adapter.TextWebSocketHandlerAdapter;
import org.springframework.web.socket.support.WebSocketHttpHeaders;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
......@@ -53,15 +57,15 @@ public class DefaultHandshakeHandlerTests extends AbstractHttpRequestTests {
@Test
public void selectSubProtocol() throws Exception {
public void supportedSubProtocols() throws Exception {
this.handshakeHandler.setSupportedProtocols("stomp", "mqtt");
when(this.upgradeStrategy.getSupportedVersions()).thenReturn(new String[] { "13" });
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(this.request.getHeaders());
this.servletRequest.setMethod("GET");
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(this.request.getHeaders());
headers.setUpgrade("WebSocket");
headers.setConnection("Upgrade");
headers.setSecWebSocketVersion("13");
......@@ -70,11 +74,70 @@ public class DefaultHandshakeHandlerTests extends AbstractHttpRequestTests {
WebSocketHandler handler = new TextWebSocketHandlerAdapter();
Map<String, Object> attributes = Collections.<String, Object>emptyMap();
this.handshakeHandler.doHandshake(this.request, this.response, handler, attributes);
verify(this.upgradeStrategy).upgrade(this.request, this.response,
"STOMP", Collections.<WebSocketExtension>emptyList(), handler, attributes);
}
@Test
public void subProtocolCapableHandler() throws Exception {
when(this.upgradeStrategy.getSupportedVersions()).thenReturn(new String[]{"13"});
this.servletRequest.setMethod("GET");
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(this.request.getHeaders());
headers.setUpgrade("WebSocket");
headers.setConnection("Upgrade");
headers.setSecWebSocketVersion("13");
headers.setSecWebSocketKey("82/ZS2YHjEnUN97HLL8tbw==");
headers.setSecWebSocketProtocol("v11.stomp");
WebSocketHandler handler = new SubProtocolCapableHandler("v12.stomp", "v11.stomp");
Map<String, Object> attributes = Collections.<String, Object>emptyMap();
this.handshakeHandler.doHandshake(this.request, this.response, handler, attributes);
verify(this.upgradeStrategy).upgrade(this.request, this.response,
"v11.stomp", Collections.<WebSocketExtension>emptyList(), handler, attributes);
}
@Test
public void subProtocolCapableHandlerNoMatch() throws Exception {
when(this.upgradeStrategy.getSupportedVersions()).thenReturn(new String[]{"13"});
this.servletRequest.setMethod("GET");
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(this.request.getHeaders());
headers.setUpgrade("WebSocket");
headers.setConnection("Upgrade");
headers.setSecWebSocketVersion("13");
headers.setSecWebSocketKey("82/ZS2YHjEnUN97HLL8tbw==");
headers.setSecWebSocketProtocol("v10.stomp");
WebSocketHandler handler = new SubProtocolCapableHandler("v12.stomp", "v11.stomp");
Map<String, Object> attributes = Collections.<String, Object>emptyMap();
this.handshakeHandler.doHandshake(this.request, this.response, handler, attributes);
verify(this.upgradeStrategy).upgrade(this.request, this.response,
null, Collections.<WebSocketExtension>emptyList(), handler, attributes);
}
private static class SubProtocolCapableHandler extends TextWebSocketHandlerAdapter implements SubProtocolCapable {
private final List<String> subProtocols;
private SubProtocolCapableHandler(String... subProtocols) {
this.subProtocols = Arrays.asList(subProtocols);
}
@Override
public List<String> getSubProtocols() {
return this.subProtocols;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册