Browse Source
Before this change spring-messaging contained a few WebSocket-related classes including WebSocket sub-protocol support for STOMP as well as @EnableWebSocketMessageBroker and related configuration classes. After this change those classes are located in the spring-websocket module under org.springframework.web.socket.messaging. This means the following classes in application configuration must have their packages updated: org.springframework.web.socket.messaging.config.EnableWebSocketMessageBroker org.springframework.web.socket.messaging.config.StompEndpointRegistry org.springframework.web.socket.messaging.config.WebSocketMessageBrokerConfigurer MessageBrokerConfigurer has been renamed to MessageBrokerRegistry and is also located in the above package.pull/419/head
34 changed files with 965 additions and 869 deletions
@ -0,0 +1,223 @@
@@ -0,0 +1,223 @@
|
||||
/* |
||||
* 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.simp.config; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.simp.SimpMessagingTemplate; |
||||
import org.springframework.messaging.simp.handler.*; |
||||
import org.springframework.messaging.support.channel.AbstractSubscribableChannel; |
||||
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel; |
||||
import org.springframework.messaging.support.converter.*; |
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.MimeTypeUtils; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
|
||||
/** |
||||
* Provides essential configuration for handling messages with simple messaging |
||||
* protocols such as STOMP. |
||||
* <p> |
||||
* {@link #clientInboundChannel()} and {@link #clientOutboundChannel()} deliver messages |
||||
* to and from remote clients to several message handlers such as |
||||
* <ul> |
||||
* <li>{@link #simpAnnotationMethodMessageHandler()}</li> |
||||
* <li>{@link #simpleBrokerMessageHandler()}</li> |
||||
* <li>{@link #stompBrokerRelayMessageHandler()}</li> |
||||
* <li>{@link #userDestinationMessageHandler()}</li> |
||||
* </ul> |
||||
* while {@link #brokerChannel()} delivers messages from within the application to the |
||||
* the respective message handlers. {@link #brokerMessagingTemplate()} can be injected |
||||
* into any application component to send messages. |
||||
* <p> |
||||
* Sub-classes are responsible for the part of the configuration that feed messages |
||||
* to and from the client inbound/outbound channels (e.g. STOMP over WebSokcet). |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public abstract class AbstractMessageBrokerConfiguration { |
||||
|
||||
private static final boolean jackson2Present= ClassUtils.isPresent( |
||||
"com.fasterxml.jackson.databind.ObjectMapper", AbstractMessageBrokerConfiguration.class.getClassLoader()); |
||||
|
||||
|
||||
private MessageBrokerRegistry brokerRegistry; |
||||
|
||||
|
||||
/** |
||||
* Protected constructor. |
||||
*/ |
||||
protected AbstractMessageBrokerConfiguration() { |
||||
} |
||||
|
||||
|
||||
/** |
||||
* An accessor for the {@link MessageBrokerRegistry} that ensures its one-time creation |
||||
* and initialization through {@link #configureMessageBroker(MessageBrokerRegistry)}. |
||||
*/ |
||||
protected final MessageBrokerRegistry getBrokerRegistry() { |
||||
if (this.brokerRegistry == null) { |
||||
MessageBrokerRegistry registry = new MessageBrokerRegistry(clientOutboundChannel()); |
||||
configureMessageBroker(registry); |
||||
this.brokerRegistry = registry; |
||||
} |
||||
return this.brokerRegistry; |
||||
} |
||||
|
||||
/** |
||||
* A hook for sub-classes to customize message broker configuration through the |
||||
* provided {@link MessageBrokerRegistry} instance. |
||||
*/ |
||||
protected abstract void configureMessageBroker(MessageBrokerRegistry registry); |
||||
|
||||
|
||||
@Bean |
||||
public AbstractSubscribableChannel clientInboundChannel() { |
||||
return new ExecutorSubscribableChannel(clientInboundChannelExecutor()); |
||||
} |
||||
|
||||
@Bean |
||||
public ThreadPoolTaskExecutor clientInboundChannelExecutor() { |
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||
executor.setThreadNamePrefix("ClientInboundChannel-"); |
||||
return executor; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractSubscribableChannel clientOutboundChannel() { |
||||
return new ExecutorSubscribableChannel(clientOutboundChannelExecutor()); |
||||
} |
||||
|
||||
@Bean |
||||
public ThreadPoolTaskExecutor clientOutboundChannelExecutor() { |
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||
executor.setThreadNamePrefix("ClientOutboundChannel-"); |
||||
return executor; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractSubscribableChannel brokerChannel() { |
||||
return new ExecutorSubscribableChannel(); // synchronous
|
||||
} |
||||
|
||||
|
||||
@Bean |
||||
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler() { |
||||
|
||||
SimpAnnotationMethodMessageHandler handler = |
||||
new SimpAnnotationMethodMessageHandler(brokerMessagingTemplate(), clientOutboundChannel()); |
||||
|
||||
handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes()); |
||||
handler.setMessageConverter(brokerMessageConverter()); |
||||
clientInboundChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractBrokerMessageHandler simpleBrokerMessageHandler() { |
||||
SimpleBrokerMessageHandler handler = getBrokerRegistry().getSimpleBroker(); |
||||
if (handler != null) { |
||||
clientInboundChannel().subscribe(handler); |
||||
brokerChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
return noopBroker; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() { |
||||
AbstractBrokerMessageHandler handler = getBrokerRegistry().getStompBrokerRelay(); |
||||
if (handler != null) { |
||||
clientInboundChannel().subscribe(handler); |
||||
brokerChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
return noopBroker; |
||||
} |
||||
|
||||
@Bean |
||||
public UserDestinationMessageHandler userDestinationMessageHandler() { |
||||
|
||||
UserDestinationMessageHandler handler = new UserDestinationMessageHandler( |
||||
brokerMessagingTemplate(), userDestinationResolver()); |
||||
|
||||
clientInboundChannel().subscribe(handler); |
||||
brokerChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
|
||||
@Bean |
||||
public SimpMessagingTemplate brokerMessagingTemplate() { |
||||
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel()); |
||||
String prefix = getBrokerRegistry().getUserDestinationPrefix(); |
||||
if (prefix != null) { |
||||
template.setUserDestinationPrefix(prefix); |
||||
} |
||||
template.setMessageConverter(brokerMessageConverter()); |
||||
return template; |
||||
} |
||||
|
||||
@Bean |
||||
public CompositeMessageConverter brokerMessageConverter() { |
||||
|
||||
DefaultContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver(); |
||||
|
||||
List<MessageConverter> converters = new ArrayList<MessageConverter>(); |
||||
if (jackson2Present) { |
||||
converters.add(new MappingJackson2MessageConverter()); |
||||
contentTypeResolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); |
||||
} |
||||
converters.add(new StringMessageConverter()); |
||||
converters.add(new ByteArrayMessageConverter()); |
||||
|
||||
return new CompositeMessageConverter(converters, contentTypeResolver); |
||||
} |
||||
|
||||
@Bean |
||||
public UserDestinationResolver userDestinationResolver() { |
||||
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userSessionRegistry()); |
||||
String prefix = getBrokerRegistry().getUserDestinationPrefix(); |
||||
if (prefix != null) { |
||||
resolver.setUserDestinationPrefix(prefix); |
||||
} |
||||
return resolver; |
||||
} |
||||
|
||||
@Bean |
||||
public UserSessionRegistry userSessionRegistry() { |
||||
return new DefaultUserSessionRegistry(); |
||||
} |
||||
|
||||
|
||||
private static final AbstractBrokerMessageHandler noopBroker = new AbstractBrokerMessageHandler(null) { |
||||
|
||||
@Override |
||||
protected void startInternal() { |
||||
} |
||||
@Override |
||||
protected void stopInternal() { |
||||
} |
||||
@Override |
||||
protected void handleMessageInternal(Message<?> message) { |
||||
} |
||||
}; |
||||
|
||||
} |
||||
@ -1,154 +0,0 @@
@@ -1,154 +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.messaging.simp.config; |
||||
|
||||
import java.util.Set; |
||||
|
||||
import org.springframework.messaging.handler.websocket.SubProtocolWebSocketHandler; |
||||
import org.springframework.scheduling.TaskScheduler; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.DefaultHandshakeHandler; |
||||
import org.springframework.web.socket.server.HandshakeHandler; |
||||
import org.springframework.web.socket.server.config.SockJsServiceRegistration; |
||||
import org.springframework.web.socket.sockjs.SockJsService; |
||||
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; |
||||
import org.springframework.web.socket.support.WebSocketHandlerDecorator; |
||||
|
||||
|
||||
/** |
||||
* An abstract base class class for configuring STOMP over WebSocket/SockJS endpoints. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public abstract class AbstractStompEndpointRegistration<M> implements StompEndpointRegistration { |
||||
|
||||
private final String[] paths; |
||||
|
||||
private final WebSocketHandler wsHandler; |
||||
|
||||
private HandshakeHandler handshakeHandler; |
||||
|
||||
private StompSockJsServiceRegistration sockJsServiceRegistration; |
||||
|
||||
private final TaskScheduler sockJsTaskScheduler; |
||||
|
||||
|
||||
public AbstractStompEndpointRegistration(String[] paths, WebSocketHandler webSocketHandler, |
||||
TaskScheduler sockJsTaskScheduler) { |
||||
|
||||
Assert.notEmpty(paths, "No paths specified"); |
||||
this.paths = paths; |
||||
this.wsHandler = webSocketHandler; |
||||
this.sockJsTaskScheduler = sockJsTaskScheduler; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Provide a custom or pre-configured {@link HandshakeHandler}. This property is |
||||
* optional. |
||||
*/ |
||||
@Override |
||||
public StompEndpointRegistration setHandshakeHandler(HandshakeHandler handshakeHandler) { |
||||
this.handshakeHandler = handshakeHandler; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Enable SockJS fallback options. |
||||
*/ |
||||
@Override |
||||
public SockJsServiceRegistration withSockJS() { |
||||
|
||||
this.sockJsServiceRegistration = new StompSockJsServiceRegistration(this.sockJsTaskScheduler); |
||||
|
||||
if (this.handshakeHandler != null) { |
||||
WebSocketTransportHandler transportHandler = new WebSocketTransportHandler(this.handshakeHandler); |
||||
this.sockJsServiceRegistration.setTransportHandlerOverrides(transportHandler); |
||||
} |
||||
|
||||
return this.sockJsServiceRegistration; |
||||
} |
||||
|
||||
protected final M getMappings() { |
||||
|
||||
M mappings = createMappings(); |
||||
|
||||
if (this.sockJsServiceRegistration != null) { |
||||
SockJsService sockJsService = this.sockJsServiceRegistration.getSockJsService(); |
||||
for (String path : this.paths) { |
||||
String pathPattern = path.endsWith("/") ? path + "**" : path + "/**"; |
||||
addSockJsServiceMapping(mappings, sockJsService, this.wsHandler, pathPattern); |
||||
} |
||||
} |
||||
else { |
||||
HandshakeHandler handshakeHandler = getOrCreateHandshakeHandler(); |
||||
for (String path : this.paths) { |
||||
addWebSocketHandlerMapping(mappings, this.wsHandler, handshakeHandler, path); |
||||
} |
||||
} |
||||
|
||||
return mappings; |
||||
} |
||||
|
||||
protected abstract M createMappings(); |
||||
|
||||
private HandshakeHandler getOrCreateHandshakeHandler() { |
||||
|
||||
HandshakeHandler handler = (this.handshakeHandler != null) |
||||
? this.handshakeHandler : new DefaultHandshakeHandler(); |
||||
|
||||
if (handler instanceof DefaultHandshakeHandler) { |
||||
DefaultHandshakeHandler defaultHandshakeHandler = (DefaultHandshakeHandler) handler; |
||||
if (ObjectUtils.isEmpty(defaultHandshakeHandler.getSupportedProtocols())) { |
||||
Set<String> protocols = findSubProtocolWebSocketHandler(this.wsHandler).getSupportedProtocols(); |
||||
defaultHandshakeHandler.setSupportedProtocols(protocols.toArray(new String[protocols.size()])); |
||||
} |
||||
} |
||||
|
||||
return handler; |
||||
} |
||||
|
||||
private static SubProtocolWebSocketHandler findSubProtocolWebSocketHandler(WebSocketHandler webSocketHandler) { |
||||
WebSocketHandler actual = (webSocketHandler instanceof WebSocketHandlerDecorator) ? |
||||
((WebSocketHandlerDecorator) webSocketHandler).getLastHandler() : webSocketHandler; |
||||
Assert.isInstanceOf(SubProtocolWebSocketHandler.class, actual, |
||||
"No SubProtocolWebSocketHandler found: " + webSocketHandler); |
||||
return (SubProtocolWebSocketHandler) actual; |
||||
} |
||||
|
||||
protected abstract void addSockJsServiceMapping(M mappings, SockJsService sockJsService, |
||||
WebSocketHandler wsHandler, String pathPattern); |
||||
|
||||
protected abstract void addWebSocketHandlerMapping(M mappings, |
||||
WebSocketHandler wsHandler, HandshakeHandler handshakeHandler, String path); |
||||
|
||||
|
||||
private class StompSockJsServiceRegistration extends SockJsServiceRegistration { |
||||
|
||||
public StompSockJsServiceRegistration(TaskScheduler defaultTaskScheduler) { |
||||
super(defaultTaskScheduler); |
||||
} |
||||
|
||||
protected SockJsService getSockJsService() { |
||||
return super.getSockJsService(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,68 +0,0 @@
@@ -1,68 +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.messaging.simp.config; |
||||
|
||||
import org.springframework.scheduling.TaskScheduler; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.HttpRequestHandler; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.HandshakeHandler; |
||||
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsService; |
||||
|
||||
|
||||
/** |
||||
* A helper class for configuring STOMP protocol handling over WebSocket |
||||
* with optional SockJS fallback options. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public class ServletStompEndpointRegistration |
||||
extends AbstractStompEndpointRegistration<MultiValueMap<HttpRequestHandler, String>> { |
||||
|
||||
|
||||
public ServletStompEndpointRegistration(String[] paths, |
||||
WebSocketHandler wsHandler, TaskScheduler sockJsTaskScheduler) { |
||||
|
||||
super(paths, wsHandler, sockJsTaskScheduler); |
||||
} |
||||
|
||||
@Override |
||||
protected MultiValueMap<HttpRequestHandler, String> createMappings() { |
||||
return new LinkedMultiValueMap<HttpRequestHandler, String>(); |
||||
} |
||||
|
||||
@Override |
||||
protected void addSockJsServiceMapping(MultiValueMap<HttpRequestHandler, String> mappings, |
||||
SockJsService sockJsService, WebSocketHandler wsHandler, String pathPattern) { |
||||
|
||||
SockJsHttpRequestHandler httpHandler = new SockJsHttpRequestHandler(sockJsService, wsHandler); |
||||
mappings.add(httpHandler, pathPattern); |
||||
} |
||||
|
||||
@Override |
||||
protected void addWebSocketHandlerMapping(MultiValueMap<HttpRequestHandler, String> mappings, |
||||
WebSocketHandler wsHandler, HandshakeHandler handshakeHandler, String path) { |
||||
|
||||
WebSocketHttpRequestHandler handler = new WebSocketHttpRequestHandler(wsHandler, handshakeHandler); |
||||
mappings.add(handler, path); |
||||
} |
||||
|
||||
} |
||||
@ -1,257 +0,0 @@
@@ -1,257 +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.messaging.simp.config; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.handler.websocket.SubProtocolWebSocketHandler; |
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations; |
||||
import org.springframework.messaging.simp.SimpMessagingTemplate; |
||||
import org.springframework.messaging.simp.handler.*; |
||||
import org.springframework.messaging.support.channel.AbstractSubscribableChannel; |
||||
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel; |
||||
import org.springframework.messaging.support.converter.*; |
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.MimeTypeUtils; |
||||
import org.springframework.web.servlet.HandlerMapping; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; |
||||
import org.springframework.web.servlet.handler.AbstractHandlerMapping; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.config.SockJsServiceRegistration; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
|
||||
/** |
||||
* Configuration support for broker-backed messaging over WebSocket using a higher-level |
||||
* messaging sub-protocol such as STOMP. This class can either be extended directly |
||||
* or its configuration can also be customized in a callback style via |
||||
* {@link EnableWebSocketMessageBroker @EnableWebSocketMessageBroker} and |
||||
* {@link WebSocketMessageBrokerConfigurer}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public abstract class WebSocketMessageBrokerConfigurationSupport { |
||||
|
||||
private static final boolean jackson2Present = |
||||
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) && |
||||
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader()); |
||||
|
||||
private MessageBrokerConfigurer messageBrokerConfigurer; |
||||
|
||||
|
||||
// WebSocket configuration including message channels to/from the application
|
||||
|
||||
@Bean |
||||
public HandlerMapping brokerWebSocketHandlerMapping() { |
||||
|
||||
ServletStompEndpointRegistry registry = new ServletStompEndpointRegistry( |
||||
subProtocolWebSocketHandler(), userSessionRegistry(), brokerDefaultSockJsTaskScheduler()); |
||||
|
||||
registerStompEndpoints(registry); |
||||
AbstractHandlerMapping hm = registry.getHandlerMapping(); |
||||
hm.setOrder(1); |
||||
return hm; |
||||
} |
||||
|
||||
@Bean |
||||
public WebSocketHandler subProtocolWebSocketHandler() { |
||||
SubProtocolWebSocketHandler wsHandler = new SubProtocolWebSocketHandler(webSocketRequestChannel()); |
||||
webSocketResponseChannel().subscribe(wsHandler); |
||||
return wsHandler; |
||||
} |
||||
|
||||
@Bean |
||||
public UserSessionRegistry userSessionRegistry() { |
||||
return new DefaultUserSessionRegistry(); |
||||
} |
||||
|
||||
/** |
||||
* The default TaskScheduler to use if none is configured via |
||||
* {@link SockJsServiceRegistration#setTaskScheduler(org.springframework.scheduling.TaskScheduler)}, i.e. |
||||
* <pre class="code"> |
||||
* @Configuration |
||||
* @EnableWebSocketMessageBroker |
||||
* public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
||||
* |
||||
* public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
* registry.addEndpoint("/stomp").withSockJS().setTaskScheduler(myScheduler()); |
||||
* } |
||||
* |
||||
* // ...
|
||||
* |
||||
* } |
||||
* </pre> |
||||
*/ |
||||
@Bean |
||||
public ThreadPoolTaskScheduler brokerDefaultSockJsTaskScheduler() { |
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); |
||||
scheduler.setThreadNamePrefix("BrokerSockJS-"); |
||||
return scheduler; |
||||
} |
||||
|
||||
protected void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractSubscribableChannel webSocketRequestChannel() { |
||||
return new ExecutorSubscribableChannel(webSocketRequestChannelExecutor()); |
||||
} |
||||
|
||||
@Bean |
||||
public ThreadPoolTaskExecutor webSocketRequestChannelExecutor() { |
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||
executor.setThreadNamePrefix("WebSocketRequestChannel-"); |
||||
return executor; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractSubscribableChannel webSocketResponseChannel() { |
||||
return new ExecutorSubscribableChannel(webSocketResponseChannelExecutor()); |
||||
} |
||||
|
||||
@Bean |
||||
public ThreadPoolTaskExecutor webSocketResponseChannelExecutor() { |
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||
executor.setThreadNamePrefix("WebSocketResponseChannel-"); |
||||
return executor; |
||||
} |
||||
|
||||
// Handling of messages by the application
|
||||
|
||||
@Bean |
||||
public SimpAnnotationMethodMessageHandler annotationMethodMessageHandler() { |
||||
|
||||
SimpAnnotationMethodMessageHandler handler = |
||||
new SimpAnnotationMethodMessageHandler(brokerMessagingTemplate(), webSocketResponseChannel()); |
||||
|
||||
handler.setDestinationPrefixes(getMessageBrokerConfigurer().getApplicationDestinationPrefixes()); |
||||
handler.setMessageConverter(simpMessageConverter()); |
||||
webSocketRequestChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractBrokerMessageHandler simpleBrokerMessageHandler() { |
||||
AbstractBrokerMessageHandler handler = getMessageBrokerConfigurer().getSimpleBroker(); |
||||
if (handler == null) { |
||||
return noopBroker; |
||||
} |
||||
else { |
||||
webSocketRequestChannel().subscribe(handler); |
||||
brokerChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractBrokerMessageHandler stompBrokerRelayMessageHandler() { |
||||
AbstractBrokerMessageHandler handler = getMessageBrokerConfigurer().getStompBrokerRelay(); |
||||
if (handler == null) { |
||||
return noopBroker; |
||||
} |
||||
else { |
||||
webSocketRequestChannel().subscribe(handler); |
||||
brokerChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
} |
||||
|
||||
protected final MessageBrokerConfigurer getMessageBrokerConfigurer() { |
||||
if (this.messageBrokerConfigurer == null) { |
||||
MessageBrokerConfigurer configurer = new MessageBrokerConfigurer(webSocketResponseChannel()); |
||||
configureMessageBroker(configurer); |
||||
this.messageBrokerConfigurer = configurer; |
||||
} |
||||
return this.messageBrokerConfigurer; |
||||
} |
||||
|
||||
protected void configureMessageBroker(MessageBrokerConfigurer configurer) { |
||||
} |
||||
|
||||
@Bean |
||||
public UserDestinationMessageHandler userDestinationMessageHandler() { |
||||
|
||||
UserDestinationMessageHandler handler = new UserDestinationMessageHandler( |
||||
brokerMessagingTemplate(), userDestinationResolver()); |
||||
|
||||
webSocketRequestChannel().subscribe(handler); |
||||
brokerChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
|
||||
@Bean |
||||
public SimpMessageSendingOperations brokerMessagingTemplate() { |
||||
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel()); |
||||
String userDestinationPrefix = getMessageBrokerConfigurer().getUserDestinationPrefix(); |
||||
if (userDestinationPrefix != null) { |
||||
template.setUserDestinationPrefix(userDestinationPrefix); |
||||
} |
||||
template.setMessageConverter(simpMessageConverter()); |
||||
return template; |
||||
} |
||||
|
||||
@Bean |
||||
public AbstractSubscribableChannel brokerChannel() { |
||||
return new ExecutorSubscribableChannel(); // synchronous
|
||||
} |
||||
|
||||
@Bean |
||||
public CompositeMessageConverter simpMessageConverter() { |
||||
|
||||
DefaultContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver(); |
||||
|
||||
List<MessageConverter> converters = new ArrayList<MessageConverter>(); |
||||
if (jackson2Present) { |
||||
converters.add(new MappingJackson2MessageConverter()); |
||||
contentTypeResolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); |
||||
} |
||||
converters.add(new StringMessageConverter()); |
||||
converters.add(new ByteArrayMessageConverter()); |
||||
|
||||
return new CompositeMessageConverter(converters, contentTypeResolver); |
||||
} |
||||
|
||||
@Bean |
||||
public UserDestinationResolver userDestinationResolver() { |
||||
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userSessionRegistry()); |
||||
String prefix = getMessageBrokerConfigurer().getUserDestinationPrefix(); |
||||
if (prefix != null) { |
||||
resolver.setUserDestinationPrefix(prefix); |
||||
} |
||||
return resolver; |
||||
} |
||||
|
||||
|
||||
private static final AbstractBrokerMessageHandler noopBroker = new AbstractBrokerMessageHandler(null) { |
||||
|
||||
@Override |
||||
protected void startInternal() { |
||||
} |
||||
@Override |
||||
protected void stopInternal() { |
||||
} |
||||
@Override |
||||
protected void handleMessageInternal(Message<?> message) { |
||||
} |
||||
}; |
||||
|
||||
} |
||||
@ -1,164 +0,0 @@
@@ -1,164 +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.messaging.simp.config; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
import org.springframework.messaging.handler.websocket.SubProtocolWebSocketHandler; |
||||
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel; |
||||
import org.springframework.scheduling.TaskScheduler; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.DefaultHandshakeHandler; |
||||
import org.springframework.web.socket.server.HandshakeHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsService; |
||||
import org.springframework.web.socket.sockjs.transport.TransportType; |
||||
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; |
||||
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
|
||||
/** |
||||
* Test fixture for {@link AbstractStompEndpointRegistration}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class AbstractStompEndpointRegistrationTests { |
||||
|
||||
private SubProtocolWebSocketHandler wsHandler; |
||||
|
||||
private TaskScheduler scheduler; |
||||
|
||||
|
||||
@Before |
||||
public void setup() { |
||||
this.wsHandler = new SubProtocolWebSocketHandler(new ExecutorSubscribableChannel()); |
||||
this.scheduler = Mockito.mock(TaskScheduler.class); |
||||
} |
||||
|
||||
@Test |
||||
public void minimalRegistration() { |
||||
|
||||
TestStompEndpointRegistration registration = |
||||
new TestStompEndpointRegistration(new String[] {"/foo"}, this.wsHandler, this.scheduler); |
||||
|
||||
List<Mapping> mappings = registration.getMappings(); |
||||
assertEquals(1, mappings.size()); |
||||
|
||||
Mapping m1 = mappings.get(0); |
||||
assertSame(this.wsHandler, m1.webSocketHandler); |
||||
assertEquals("/foo", m1.path); |
||||
} |
||||
|
||||
@Test |
||||
public void customHandshakeHandler() { |
||||
|
||||
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler(); |
||||
|
||||
TestStompEndpointRegistration registration = |
||||
new TestStompEndpointRegistration(new String[] {"/foo"}, this.wsHandler, this.scheduler); |
||||
registration.setHandshakeHandler(handshakeHandler); |
||||
|
||||
List<Mapping> mappings = registration.getMappings(); |
||||
assertEquals(1, mappings.size()); |
||||
|
||||
Mapping m1 = mappings.get(0); |
||||
assertSame(this.wsHandler, m1.webSocketHandler); |
||||
assertEquals("/foo", m1.path); |
||||
assertSame(handshakeHandler, m1.handshakeHandler); |
||||
} |
||||
|
||||
@Test |
||||
public void customHandshakeHandlerPassedToSockJsService() { |
||||
|
||||
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler(); |
||||
|
||||
TestStompEndpointRegistration registration = |
||||
new TestStompEndpointRegistration(new String[] {"/foo"}, this.wsHandler, this.scheduler); |
||||
registration.setHandshakeHandler(handshakeHandler); |
||||
registration.withSockJS(); |
||||
|
||||
List<Mapping> mappings = registration.getMappings(); |
||||
assertEquals(1, mappings.size()); |
||||
|
||||
Mapping m1 = mappings.get(0); |
||||
assertSame(this.wsHandler, m1.webSocketHandler); |
||||
assertEquals("/foo/**", m1.path); |
||||
assertNotNull(m1.sockJsService); |
||||
|
||||
WebSocketTransportHandler transportHandler = |
||||
(WebSocketTransportHandler) m1.sockJsService.getTransportHandlers().get(TransportType.WEBSOCKET); |
||||
assertSame(handshakeHandler, transportHandler.getHandshakeHandler()); |
||||
} |
||||
|
||||
|
||||
private static class TestStompEndpointRegistration extends AbstractStompEndpointRegistration<List<Mapping>> { |
||||
|
||||
public TestStompEndpointRegistration(String[] paths, SubProtocolWebSocketHandler wsh, TaskScheduler scheduler) { |
||||
super(paths, wsh, scheduler); |
||||
} |
||||
|
||||
@Override |
||||
protected List<Mapping> createMappings() { |
||||
return new ArrayList<>(); |
||||
} |
||||
|
||||
@Override |
||||
protected void addSockJsServiceMapping(List<Mapping> mappings, SockJsService sockJsService, |
||||
WebSocketHandler wsHandler, String pathPattern) { |
||||
|
||||
mappings.add(new Mapping(wsHandler, pathPattern, sockJsService)); |
||||
} |
||||
|
||||
@Override |
||||
protected void addWebSocketHandlerMapping(List<Mapping> mappings, WebSocketHandler wsHandler, |
||||
HandshakeHandler handshakeHandler, String path) { |
||||
|
||||
mappings.add(new Mapping(wsHandler, path, handshakeHandler)); |
||||
} |
||||
} |
||||
|
||||
private static class Mapping { |
||||
|
||||
private final WebSocketHandler webSocketHandler; |
||||
|
||||
private final String path; |
||||
|
||||
private final HandshakeHandler handshakeHandler; |
||||
|
||||
private final DefaultSockJsService sockJsService; |
||||
|
||||
public Mapping(WebSocketHandler handler, String path, SockJsService sockJsService) { |
||||
this.webSocketHandler = handler; |
||||
this.path = path; |
||||
this.handshakeHandler = null; |
||||
this.sockJsService = (DefaultSockJsService) sockJsService; |
||||
} |
||||
|
||||
public Mapping(WebSocketHandler h, String path, HandshakeHandler hh) { |
||||
this.webSocketHandler = h; |
||||
this.path = path; |
||||
this.handshakeHandler = hh; |
||||
this.sockJsService = null; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
/* |
||||
* 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.messaging.config; |
||||
|
||||
import java.util.Set; |
||||
|
||||
import org.springframework.scheduling.TaskScheduler; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.web.HttpRequestHandler; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.DefaultHandshakeHandler; |
||||
import org.springframework.web.socket.server.HandshakeHandler; |
||||
import org.springframework.web.socket.server.config.SockJsServiceRegistration; |
||||
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsService; |
||||
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; |
||||
|
||||
|
||||
/** |
||||
* An abstract base class class for configuring STOMP over WebSocket/SockJS endpoints. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public class WebMvcStompWebSocketEndpointRegistration implements StompWebSocketEndpointRegistration { |
||||
|
||||
private final String[] paths; |
||||
|
||||
private final WebSocketHandler webSocketHandler; |
||||
|
||||
private final String[] subProtocols; |
||||
|
||||
private final TaskScheduler sockJsTaskScheduler; |
||||
|
||||
private HandshakeHandler handshakeHandler; |
||||
|
||||
private StompSockJsServiceRegistration registration; |
||||
|
||||
|
||||
public WebMvcStompWebSocketEndpointRegistration(String[] paths, WebSocketHandler webSocketHandler, |
||||
Set<String> subProtocols, 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); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Provide a custom or pre-configured {@link HandshakeHandler}. |
||||
*/ |
||||
@Override |
||||
public StompWebSocketEndpointRegistration setHandshakeHandler(HandshakeHandler handshakeHandler) { |
||||
Assert.notNull(handshakeHandler, "'handshakeHandler' must not be null"); |
||||
this.handshakeHandler = handshakeHandler; |
||||
updateHandshakeHandler(); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Enable SockJS fallback options. |
||||
*/ |
||||
@Override |
||||
public SockJsServiceRegistration withSockJS() { |
||||
this.registration = new StompSockJsServiceRegistration(this.sockJsTaskScheduler); |
||||
WebSocketTransportHandler transportHandler = new WebSocketTransportHandler(this.handshakeHandler); |
||||
this.registration.setTransportHandlerOverrides(transportHandler); |
||||
return this.registration; |
||||
} |
||||
|
||||
protected final MultiValueMap<HttpRequestHandler, String> getMappings() { |
||||
MultiValueMap<HttpRequestHandler, String> mappings = new LinkedMultiValueMap<HttpRequestHandler, String>(); |
||||
if (this.registration != null) { |
||||
SockJsService sockJsService = this.registration.getSockJsService(); |
||||
for (String path : this.paths) { |
||||
String pattern = path.endsWith("/") ? path + "**" : path + "/**"; |
||||
SockJsHttpRequestHandler handler = new SockJsHttpRequestHandler(sockJsService, this.webSocketHandler); |
||||
mappings.add(handler, pattern); |
||||
} |
||||
} |
||||
else { |
||||
for (String path : this.paths) { |
||||
WebSocketHttpRequestHandler handler = |
||||
new WebSocketHttpRequestHandler(this.webSocketHandler, this.handshakeHandler); |
||||
mappings.add(handler, path); |
||||
} |
||||
} |
||||
return mappings; |
||||
} |
||||
|
||||
|
||||
private static class StompSockJsServiceRegistration extends SockJsServiceRegistration { |
||||
|
||||
public StompSockJsServiceRegistration(TaskScheduler defaultTaskScheduler) { |
||||
super(defaultTaskScheduler); |
||||
} |
||||
|
||||
protected SockJsService getSockJsService() { |
||||
return super.getSockJsService(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
/* |
||||
* 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.messaging.config; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration; |
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; |
||||
import org.springframework.web.servlet.HandlerMapping; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.server.config.SockJsServiceRegistration; |
||||
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; |
||||
|
||||
|
||||
/** |
||||
* Extends {@link AbstractMessageBrokerConfiguration} and adds configuration for |
||||
* receiving and responding to STOMP messages from WebSocket clients. |
||||
* <p> |
||||
* Typically used in conjunction with |
||||
* {@link EnableWebSocketMessageBroker @EnableWebSocketMessageBroker} but can |
||||
* also be extended directly. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.0 |
||||
*/ |
||||
public abstract class WebSocketMessageBrokerConfigurationSupport extends AbstractMessageBrokerConfiguration { |
||||
|
||||
|
||||
protected WebSocketMessageBrokerConfigurationSupport() { |
||||
} |
||||
|
||||
@Bean |
||||
public HandlerMapping stompWebSocketHandlerMapping() { |
||||
|
||||
WebMvcStompEndpointRegistry registry = new WebMvcStompEndpointRegistry( |
||||
subProtocolWebSocketHandler(), userSessionRegistry(), messageBrokerSockJsTaskScheduler()); |
||||
|
||||
registerStompEndpoints(registry); |
||||
return registry.getHandlerMapping(); |
||||
} |
||||
|
||||
@Bean |
||||
public WebSocketHandler subProtocolWebSocketHandler() { |
||||
SubProtocolWebSocketHandler handler = new SubProtocolWebSocketHandler(clientInboundChannel()); |
||||
clientOutboundChannel().subscribe(handler); |
||||
return handler; |
||||
} |
||||
|
||||
/** |
||||
* The default TaskScheduler to use if none is configured via |
||||
* {@link SockJsServiceRegistration#setTaskScheduler(org.springframework.scheduling.TaskScheduler)}, i.e. |
||||
* <pre class="code"> |
||||
* @Configuration |
||||
* @EnableWebSocketMessageBroker |
||||
* public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
||||
* |
||||
* public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
* registry.addEndpoint("/stomp").withSockJS().setTaskScheduler(myScheduler()); |
||||
* } |
||||
* |
||||
* // ...
|
||||
* |
||||
* } |
||||
* </pre> |
||||
*/ |
||||
@Bean |
||||
public ThreadPoolTaskScheduler messageBrokerSockJsTaskScheduler() { |
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); |
||||
scheduler.setThreadNamePrefix("MessageBrokerSockJS-"); |
||||
return scheduler; |
||||
} |
||||
|
||||
protected abstract void registerStompEndpoints(StompEndpointRegistry registry); |
||||
|
||||
} |
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
/* |
||||
* 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.messaging.config; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.mockito.Mockito; |
||||
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel; |
||||
import org.springframework.scheduling.TaskScheduler; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.HttpRequestHandler; |
||||
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; |
||||
import org.springframework.web.socket.server.DefaultHandshakeHandler; |
||||
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; |
||||
import org.springframework.web.socket.sockjs.transport.TransportType; |
||||
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; |
||||
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
|
||||
/** |
||||
* Test fixture for {@link WebMvcStompWebSocketEndpointRegistration}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class WebMvcStompEndpointRegistrationTests { |
||||
|
||||
private SubProtocolWebSocketHandler wsHandler; |
||||
|
||||
private TaskScheduler scheduler; |
||||
|
||||
|
||||
@Before |
||||
public void setup() { |
||||
this.wsHandler = new SubProtocolWebSocketHandler(new ExecutorSubscribableChannel()); |
||||
this.scheduler = Mockito.mock(TaskScheduler.class); |
||||
} |
||||
|
||||
@Test |
||||
public void minimalRegistration() { |
||||
|
||||
|
||||
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration( |
||||
new String[] {"/foo"}, this.wsHandler, Collections.<String>emptySet(), this.scheduler); |
||||
|
||||
MultiValueMap<HttpRequestHandler, String> mappings = registration.getMappings(); |
||||
assertEquals(1, mappings.size()); |
||||
|
||||
Map.Entry<HttpRequestHandler, List<String>> entry = mappings.entrySet().iterator().next(); |
||||
assertNotNull(((WebSocketHttpRequestHandler) entry.getKey()).getWebSocketHandler()); |
||||
assertEquals(Arrays.asList("/foo"), entry.getValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void customHandshakeHandler() { |
||||
|
||||
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler(); |
||||
|
||||
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration( |
||||
new String[] {"/foo"}, this.wsHandler, Collections.<String>emptySet(), this.scheduler); |
||||
|
||||
registration.setHandshakeHandler(handshakeHandler); |
||||
|
||||
MultiValueMap<HttpRequestHandler, String> mappings = registration.getMappings(); |
||||
assertEquals(1, mappings.size()); |
||||
|
||||
Map.Entry<HttpRequestHandler, List<String>> entry = mappings.entrySet().iterator().next(); |
||||
assertEquals(Arrays.asList("/foo"), entry.getValue()); |
||||
|
||||
WebSocketHttpRequestHandler requestHandler = (WebSocketHttpRequestHandler) entry.getKey(); |
||||
assertNotNull(requestHandler.getWebSocketHandler()); |
||||
assertSame(handshakeHandler, requestHandler.getHandshakeHandler()); |
||||
} |
||||
|
||||
@Test |
||||
public void customHandshakeHandlerPassedToSockJsService() { |
||||
|
||||
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler(); |
||||
|
||||
WebMvcStompWebSocketEndpointRegistration registration = new WebMvcStompWebSocketEndpointRegistration( |
||||
new String[] {"/foo"}, this.wsHandler, Collections.<String>emptySet(), this.scheduler); |
||||
|
||||
registration.setHandshakeHandler(handshakeHandler); |
||||
registration.withSockJS(); |
||||
|
||||
MultiValueMap<HttpRequestHandler, String> mappings = registration.getMappings(); |
||||
assertEquals(1, mappings.size()); |
||||
|
||||
Map.Entry<HttpRequestHandler, List<String>> entry = mappings.entrySet().iterator().next(); |
||||
assertEquals(Arrays.asList("/foo/**"), entry.getValue()); |
||||
|
||||
SockJsHttpRequestHandler requestHandler = (SockJsHttpRequestHandler) entry.getKey(); |
||||
assertNotNull(requestHandler.getWebSocketHandler()); |
||||
|
||||
DefaultSockJsService sockJsService = (DefaultSockJsService) requestHandler.getSockJsService(); |
||||
assertNotNull(sockJsService); |
||||
|
||||
WebSocketTransportHandler transportHandler = |
||||
(WebSocketTransportHandler) sockJsService.getTransportHandlers().get(TransportType.WEBSOCKET); |
||||
assertSame(handshakeHandler, transportHandler.getHandshakeHandler()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,179 @@
@@ -0,0 +1,179 @@
|
||||
/* |
||||
* 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.messaging.config; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.messaging.Message; |
||||
import org.springframework.messaging.MessageHandler; |
||||
import org.springframework.messaging.handler.annotation.MessageMapping; |
||||
import org.springframework.messaging.handler.annotation.SendTo; |
||||
import org.springframework.messaging.simp.SimpMessageType; |
||||
import org.springframework.messaging.simp.annotation.SubscribeMapping; |
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry; |
||||
import org.springframework.messaging.simp.stomp.StompCommand; |
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor; |
||||
import org.springframework.web.socket.messaging.StompTextMessageBuilder; |
||||
import org.springframework.messaging.support.channel.AbstractSubscribableChannel; |
||||
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.servlet.HandlerMapping; |
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; |
||||
import org.springframework.web.socket.TextMessage; |
||||
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; |
||||
import org.springframework.web.socket.support.TestWebSocketSession; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
|
||||
/** |
||||
* Test fixture for {@link WebSocketMessageBrokerConfigurationSupport}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class WebSocketMessageBrokerConfigurationSupportTests { |
||||
|
||||
private AnnotationConfigApplicationContext config; |
||||
|
||||
|
||||
@Before |
||||
public void setupOnce() { |
||||
this.config = new AnnotationConfigApplicationContext(); |
||||
this.config.register(TestWebSocketMessageBrokerConfiguration.class, TestSimpleMessageBrokerConfig.class); |
||||
this.config.refresh(); |
||||
} |
||||
|
||||
@Test |
||||
public void handlerMapping() { |
||||
|
||||
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.config.getBean(HandlerMapping.class); |
||||
assertEquals(1, hm.getOrder()); |
||||
|
||||
Map<String, Object> handlerMap = hm.getHandlerMap(); |
||||
assertEquals(1, handlerMap.size()); |
||||
assertNotNull(handlerMap.get("/simpleBroker")); |
||||
} |
||||
|
||||
@Test |
||||
public void clientInboundChannelSendMessage() throws Exception { |
||||
|
||||
TestChannel channel = this.config.getBean("clientInboundChannel", TestChannel.class); |
||||
SubProtocolWebSocketHandler webSocketHandler = this.config.getBean(SubProtocolWebSocketHandler.class); |
||||
|
||||
TextMessage textMessage = StompTextMessageBuilder.create(StompCommand.SEND).headers("destination:/foo").build(); |
||||
webSocketHandler.handleMessage(new TestWebSocketSession(), textMessage); |
||||
|
||||
Message<?> message = channel.messages.get(0); |
||||
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message); |
||||
|
||||
assertEquals(SimpMessageType.MESSAGE, headers.getMessageType()); |
||||
assertEquals("/foo", headers.getDestination()); |
||||
} |
||||
|
||||
@Test |
||||
public void clientOutboundChannelChannel() { |
||||
TestChannel channel = this.config.getBean("clientOutboundChannel", TestChannel.class); |
||||
List<MessageHandler> values = channel.handlers; |
||||
|
||||
assertEquals(1, values.size()); |
||||
assertTrue(values.get(0) instanceof SubProtocolWebSocketHandler); |
||||
} |
||||
|
||||
|
||||
@Controller |
||||
static class TestController { |
||||
|
||||
@SubscribeMapping("/foo") |
||||
public String handleSubscribe() { |
||||
return "bar"; |
||||
} |
||||
|
||||
@MessageMapping("/foo") |
||||
@SendTo("/bar") |
||||
public String handleMessage() { |
||||
return "bar"; |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class TestSimpleMessageBrokerConfig implements WebSocketMessageBrokerConfigurer { |
||||
|
||||
@Override |
||||
public void registerStompEndpoints(StompEndpointRegistry registry) { |
||||
registry.addEndpoint("/simpleBroker"); |
||||
} |
||||
|
||||
@Override |
||||
public void configureMessageBroker(MessageBrokerRegistry configurer) { |
||||
// SimpleBroker used by default
|
||||
} |
||||
|
||||
@Bean |
||||
public TestController subscriptionController() { |
||||
return new TestController(); |
||||
} |
||||
} |
||||
|
||||
@Configuration |
||||
static class TestWebSocketMessageBrokerConfiguration extends DelegatingWebSocketMessageBrokerConfiguration { |
||||
|
||||
@Override |
||||
@Bean |
||||
public AbstractSubscribableChannel clientInboundChannel() { |
||||
return new TestChannel(); |
||||
} |
||||
|
||||
@Override |
||||
@Bean |
||||
public AbstractSubscribableChannel clientOutboundChannel() { |
||||
return new TestChannel(); |
||||
} |
||||
|
||||
@Override |
||||
public AbstractSubscribableChannel brokerChannel() { |
||||
return new TestChannel(); |
||||
} |
||||
} |
||||
|
||||
private static class TestChannel extends ExecutorSubscribableChannel { |
||||
|
||||
private final List<MessageHandler> handlers = new ArrayList<>(); |
||||
|
||||
private final List<Message<?>> messages = new ArrayList<>(); |
||||
|
||||
|
||||
@Override |
||||
public boolean subscribeInternal(MessageHandler handler) { |
||||
this.handlers.add(handler); |
||||
return super.subscribeInternal(handler); |
||||
} |
||||
|
||||
@Override |
||||
public boolean sendInternal(Message<?> message, long timeout) { |
||||
this.messages.add(message); |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue