Consolidate websocket/messaging code

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.
This commit is contained in:
Rossen Stoyanchev
2013-11-22 13:49:38 -05:00
parent f888b8816f
commit 4de3291dc7
34 changed files with 966 additions and 870 deletions

View File

@@ -0,0 +1,247 @@
/*
* 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;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.support.channel.AbstractSubscribableChannel;
import org.springframework.messaging.support.channel.ExecutorSubscribableChannel;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.AbstractWebSocketIntegrationTests;
import org.springframework.web.socket.JettyWebSocketTestServer;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.TomcatWebSocketTestServer;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter;
import org.springframework.web.socket.client.endpoint.StandardWebSocketClient;
import org.springframework.web.socket.client.jetty.JettyWebSocketClient;
import org.springframework.web.socket.messaging.config.DelegatingWebSocketMessageBrokerConfiguration;
import org.springframework.web.socket.messaging.config.StompEndpointRegistry;
import org.springframework.web.socket.messaging.config.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.HandshakeHandler;
import static org.junit.Assert.*;
import static org.springframework.web.socket.messaging.StompTextMessageBuilder.*;
/**
* Integration tests with annotated message-handling methods.
* @author Rossen Stoyanchev
*/
@RunWith(Parameterized.class)
public class SimpAnnotationMethodIntegrationTests extends AbstractWebSocketIntegrationTests {
@Parameters
public static Iterable<Object[]> arguments() {
return Arrays.asList(new Object[][] {
{new JettyWebSocketTestServer(), new JettyWebSocketClient()},
{new TomcatWebSocketTestServer(), new StandardWebSocketClient()}
});
}
@Override
protected Class<?>[] getAnnotatedConfigClasses() {
return new Class<?>[] { TestMessageBrokerConfiguration.class, TestMessageBrokerConfigurer.class };
}
@Test
public void sendMessageToController() throws Exception {
TextMessage message = create(StompCommand.SEND).headers("destination:/app/simple").build();
WebSocketSession session = doHandshake(new TestClientWebSocketHandler(0, message), "/ws").get();
SimpleController controller = this.wac.getBean(SimpleController.class);
try {
assertTrue(controller.latch.await(2, TimeUnit.SECONDS));
}
finally {
session.close();
}
}
@Test
public void sendMessageToControllerAndReceiveReplyViaTopic() throws Exception {
TextMessage message1 = create(StompCommand.SUBSCRIBE).headers(
"id:subs1", "destination:/topic/increment").build();
TextMessage message2 = create(StompCommand.SEND).headers(
"destination:/app/increment").body("5").build();
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, message1, message2);
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
try {
assertTrue(clientHandler.latch.await(2, TimeUnit.SECONDS));
}
finally {
session.close();
}
}
// SPR-10930
@Test
public void sendMessageToBrokerAndReceiveReplyViaTopic() throws Exception {
TextMessage message1 = create(StompCommand.SUBSCRIBE).headers("id:subs1", "destination:/topic/foo").build();
TextMessage message2 = create(StompCommand.SEND).headers("destination:/topic/foo").body("5").build();
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(1, message1, message2);
WebSocketSession session = doHandshake(clientHandler, "/ws").get();
try {
assertTrue(clientHandler.latch.await(2, TimeUnit.SECONDS));
String payload = clientHandler.actual.get(0).getPayload();
assertTrue("Expected STOMP Command=MESSAGE, got " + payload, payload.startsWith("MESSAGE\n"));
}
finally {
session.close();
}
}
@IntegrationTestController
static class SimpleController {
private CountDownLatch latch = new CountDownLatch(1);
@MessageMapping(value="/simple")
public void handle() {
this.latch.countDown();
}
@MessageMapping(value="/exception")
public void handleWithError() {
throw new IllegalArgumentException("Bad input");
}
@MessageExceptionHandler
public void handleException(IllegalArgumentException ex) {
}
}
@IntegrationTestController
static class IncrementController {
@MessageMapping(value="/increment")
public int handle(int i) {
return i + 1;
}
}
private static class TestClientWebSocketHandler extends TextWebSocketHandlerAdapter {
private final TextMessage[] messagesToSend;
private final int expected;
private final List<TextMessage> actual = new CopyOnWriteArrayList<>();
private final CountDownLatch latch;
public TestClientWebSocketHandler(int expectedNumberOfMessages, TextMessage... messagesToSend) {
this.messagesToSend = messagesToSend;
this.expected = expectedNumberOfMessages;
this.latch = new CountDownLatch(this.expected);
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
for (TextMessage message : this.messagesToSend) {
session.sendMessage(message);
}
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
this.actual.add(message);
this.latch.countDown();
}
}
@Configuration
@ComponentScan(basePackageClasses=SimpAnnotationMethodIntegrationTests.class,
useDefaultFilters=false,
includeFilters=@ComponentScan.Filter(IntegrationTestController.class))
static class TestMessageBrokerConfigurer implements WebSocketMessageBrokerConfigurer {
@Autowired
private HandshakeHandler handshakeHandler; // can't rely on classpath for server detection
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setHandshakeHandler(this.handshakeHandler);
}
@Override
public void configureMessageBroker(MessageBrokerRegistry configurer) {
configurer.setApplicationDestinationPrefixes("/app");
configurer.enableSimpleBroker("/topic", "/queue");
}
}
@Configuration
static class TestMessageBrokerConfiguration extends DelegatingWebSocketMessageBrokerConfiguration {
@Override
@Bean
public AbstractSubscribableChannel clientInboundChannel() {
return new ExecutorSubscribableChannel(); // synchronous
}
@Override
@Bean
public AbstractSubscribableChannel clientOutboundChannel() {
return new ExecutorSubscribableChannel(); // synchronous
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Controller
private @interface IntegrationTestController {
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.TestPrincipal;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompDecoder;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.support.TestWebSocketSession;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Test fixture for {@link StompSubProtocolHandler} tests.
*
* @author Rossen Stoyanchev
*/
public class StompSubProtocolHandlerTests {
private StompSubProtocolHandler stompHandler;
private TestWebSocketSession session;
private MessageChannel channel;
private ArgumentCaptor<Message> messageCaptor;
@Before
public void setup() {
this.stompHandler = new StompSubProtocolHandler();
this.channel = Mockito.mock(MessageChannel.class);
this.messageCaptor = ArgumentCaptor.forClass(Message.class);
this.session = new TestWebSocketSession();
this.session.setId("s1");
this.session.setPrincipal(new TestPrincipal("joe"));
}
@Test
public void connectedResponseIsSentWhenConnectAckIsToBeSentToClient() {
StompHeaderAccessor connectHeaders = StompHeaderAccessor.create(StompCommand.CONNECT);
connectHeaders.setHeartbeat(10000, 10000);
connectHeaders.setNativeHeader(StompHeaderAccessor.STOMP_ACCEPT_VERSION_HEADER, "1.0,1.1");
Message<?> connectMessage = MessageBuilder.withPayload(new byte[0]).setHeaders(connectHeaders).build();
SimpMessageHeaderAccessor connectAckHeaders = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT_ACK);
connectAckHeaders.setHeader(SimpMessageHeaderAccessor.CONNECT_MESSAGE_HEADER, connectMessage);
Message<byte[]> connectAck = MessageBuilder.withPayload(new byte[0]).setHeaders(connectAckHeaders).build();
this.stompHandler.handleMessageToClient(this.session, connectAck);
verifyNoMoreInteractions(this.channel);
// Check CONNECTED reply
assertEquals(1, this.session.getSentMessages().size());
TextMessage textMessage = (TextMessage) this.session.getSentMessages().get(0);
Message<?> message = new StompDecoder().decode(ByteBuffer.wrap(textMessage.getPayload().getBytes()));
StompHeaderAccessor replyHeaders = StompHeaderAccessor.wrap(message);
assertEquals(StompCommand.CONNECTED, replyHeaders.getCommand());
assertEquals("1.1", replyHeaders.getVersion());
assertArrayEquals(new long[] {0, 0}, replyHeaders.getHeartbeat());
assertEquals("joe", replyHeaders.getNativeHeader("user-name").get(0));
}
@Test
public void messagesAreAugmentedAndForwarded() {
TextMessage textMessage = StompTextMessageBuilder.create(StompCommand.CONNECT).headers(
"login:guest", "passcode:guest", "accept-version:1.1,1.0", "heart-beat:10000,10000").build();
this.stompHandler.handleMessageFromClient(this.session, textMessage, this.channel);
verify(this.channel).send(this.messageCaptor.capture());
Message<?> actual = this.messageCaptor.getValue();
assertNotNull(actual);
StompHeaderAccessor headers = StompHeaderAccessor.wrap(actual);
assertEquals(StompCommand.CONNECT, headers.getCommand());
assertEquals("s1", headers.getSessionId());
assertEquals("joe", headers.getUser().getName());
assertEquals("guest", headers.getLogin());
assertEquals("PROTECTED", headers.getPasscode());
assertArrayEquals(new long[] {10000, 10000}, headers.getHeartbeat());
assertEquals(new HashSet<>(Arrays.asList("1.1","1.0")), headers.getAcceptVersion());
assertEquals(0, this.session.getSentMessages().size());
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.web.socket.TextMessage;
/**
* A builder for creating WebSocket messages with STOMP frame content.
*
* @author Rossen Stoyanchev
*/
public class StompTextMessageBuilder {
private StompCommand command;
private final List<String> headerLines = new ArrayList<String>();
private String body;
private StompTextMessageBuilder(StompCommand command) {
this.command = command;
}
public static StompTextMessageBuilder create(StompCommand command) {
return new StompTextMessageBuilder(command);
}
public StompTextMessageBuilder headers(String... headerLines) {
this.headerLines.addAll(Arrays.asList(headerLines));
return this;
}
public StompTextMessageBuilder body(String body) {
this.body = body;
return this;
}
public TextMessage build() {
StringBuilder sb = new StringBuilder(this.command.name()).append("\n");
for (String line : this.headerLines) {
sb.append(line).append("\n");
}
sb.append("\n");
if (this.body != null) {
sb.append(this.body);
}
sb.append("\u0000");
return new TextMessage(sb.toString());
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.messaging;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.messaging.MessageChannel;
import org.springframework.web.socket.support.TestWebSocketSession;
import static org.mockito.Mockito.*;
/**
* Test fixture for {@link SubProtocolWebSocketHandler}.
*
* @author Rossen Stoyanchev
* @author Andy Wilkinson
*/
public class SubProtocolWebSocketHandlerTests {
private SubProtocolWebSocketHandler webSocketHandler;
private TestWebSocketSession session;
@Mock SubProtocolHandler stompHandler;
@Mock SubProtocolHandler mqttHandler;
@Mock SubProtocolHandler defaultHandler;
@Mock MessageChannel channel;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.webSocketHandler = new SubProtocolWebSocketHandler(this.channel);
when(stompHandler.getSupportedProtocols()).thenReturn(Arrays.asList("v10.stomp", "v11.stomp", "v12.stomp"));
when(mqttHandler.getSupportedProtocols()).thenReturn(Arrays.asList("MQTT"));
this.session = new TestWebSocketSession();
this.session.setId("1");
}
@Test
public void subProtocolMatch() throws Exception {
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler));
this.session.setAcceptedProtocol("v12.sToMp");
this.webSocketHandler.afterConnectionEstablished(session);
verify(this.stompHandler).afterSessionStarted(session, this.channel);
verify(this.mqttHandler, times(0)).afterSessionStarted(session, this.channel);
}
@Test
public void subProtocolDefaultHandlerOnly() throws Exception {
this.webSocketHandler.setDefaultProtocolHandler(stompHandler);
this.session.setAcceptedProtocol("v12.sToMp");
this.webSocketHandler.afterConnectionEstablished(session);
verify(this.stompHandler).afterSessionStarted(session, this.channel);
}
@Test(expected=IllegalStateException.class)
public void subProtocolNoMatch() throws Exception {
this.webSocketHandler.setDefaultProtocolHandler(defaultHandler);
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler));
this.session.setAcceptedProtocol("wamp");
this.webSocketHandler.afterConnectionEstablished(session);
}
@Test
public void nullSubProtocol() throws Exception {
this.webSocketHandler.setDefaultProtocolHandler(defaultHandler);
this.webSocketHandler.afterConnectionEstablished(session);
verify(this.defaultHandler).afterSessionStarted(session, this.channel);
verify(this.stompHandler, times(0)).afterSessionStarted(session, this.channel);
verify(this.mqttHandler, times(0)).afterSessionStarted(session, this.channel);
}
@Test
public void emptySubProtocol() throws Exception {
this.session.setAcceptedProtocol("");
this.webSocketHandler.setDefaultProtocolHandler(defaultHandler);
this.webSocketHandler.afterConnectionEstablished(session);
verify(this.defaultHandler).afterSessionStarted(session, this.channel);
verify(this.stompHandler, times(0)).afterSessionStarted(session, this.channel);
verify(this.mqttHandler, times(0)).afterSessionStarted(session, this.channel);
}
@Test
public void noSubProtocolOneHandler() throws Exception {
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler));
this.webSocketHandler.afterConnectionEstablished(session);
verify(this.stompHandler).afterSessionStarted(session, this.channel);
}
@Test(expected=IllegalStateException.class)
public void noSubProtocolTwoHandlers() throws Exception {
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler));
this.webSocketHandler.afterConnectionEstablished(session);
}
@Test(expected=IllegalStateException.class)
public void noSubProtocolNoDefaultHandler() throws Exception {
this.webSocketHandler.setProtocolHandlers(Arrays.asList(stompHandler, mqttHandler));
this.webSocketHandler.afterConnectionEstablished(session);
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.handler.DefaultUserSessionRegistry;
import org.springframework.messaging.simp.handler.UserSessionRegistry;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.socket.messaging.StompSubProtocolHandler;
import org.springframework.web.socket.messaging.SubProtocolHandler;
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* Test fixture for {@link WebMvcStompEndpointRegistry}.
*
* @author Rossen Stoyanchev
*/
public class WebMvcStompEndpointRegistryTests {
private WebMvcStompEndpointRegistry registry;
private SubProtocolWebSocketHandler webSocketHandler;
private UserSessionRegistry userSessionRegistry;
@Before
public void setup() {
MessageChannel channel = Mockito.mock(MessageChannel.class);
this.webSocketHandler = new SubProtocolWebSocketHandler(channel);
this.userSessionRegistry = new DefaultUserSessionRegistry();
TaskScheduler taskScheduler = Mockito.mock(TaskScheduler.class);
this.registry = new WebMvcStompEndpointRegistry(webSocketHandler, userSessionRegistry, taskScheduler);
}
@Test
public void stompProtocolHandler() {
this.registry.addEndpoint("/stomp");
Map<String, SubProtocolHandler> protocolHandlers = webSocketHandler.getProtocolHandlers();
assertEquals(3, protocolHandlers.size());
assertNotNull(protocolHandlers.get("v10.stomp"));
assertNotNull(protocolHandlers.get("v11.stomp"));
assertNotNull(protocolHandlers.get("v12.stomp"));
StompSubProtocolHandler stompHandler = (StompSubProtocolHandler) protocolHandlers.get("v10.stomp");
assertSame(this.userSessionRegistry, stompHandler.getUserSessionRegistry());
}
@Test
public void handlerMapping() {
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
assertEquals(0, hm.getUrlMap().size());
this.registry.addEndpoint("/stompOverWebSocket");
this.registry.addEndpoint("/stompOverSockJS").withSockJS();
hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
assertEquals(2, hm.getUrlMap().size());
assertNotNull(hm.getUrlMap().get("/stompOverWebSocket"));
assertNotNull(hm.getUrlMap().get("/stompOverSockJS/**"));
}
}

View File

@@ -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;
}
}
}