/*
* Copyright 2002-2015 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.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
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.handler.WebSocketHandlerDecorator;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.web.socket.server.support.OriginHandshakeInterceptor;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
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.EventSourceTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.HtmlFileTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.JsonpPollingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.JsonpReceivingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.XhrPollingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.XhrReceivingTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.XhrStreamingTransportHandler;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Test fixture for HandlersBeanDefinitionParser.
* See test configuration files websocket-config-handlers-*.xml.
*
* @author Brian Clozel
* @author Rossen Stoyanchev
*/
public class HandlersBeanDefinitionParserTests {
private final GenericWebApplicationContext appContext = new GenericWebApplicationContext();
@Test
public void webSocketHandlers() {
loadBeanDefinitions("websocket-config-handlers.xml");
Map<String, HandlerMapping> handlersMap = this.appContext.getBeansOfType(HandlerMapping.class);
assertNotNull(handlersMap);
assertThat(handlersMap.values(), hasSize(2));
for (HandlerMapping hm : handlersMap.values()) {
assertTrue(hm instanceof SimpleUrlHandlerMapping);
SimpleUrlHandlerMapping shm = (SimpleUrlHandlerMapping) hm;
if (shm.getUrlMap().keySet().contains("/foo")) {
assertThat(shm.getUrlMap().keySet(), contains("/foo", "/bar"));
WebSocketHttpRequestHandler handler = (WebSocketHttpRequestHandler) shm.getUrlMap().get("/foo");
assertNotNull(handler);
unwrapAndCheckDecoratedHandlerType(handler.getWebSocketHandler(), FooWebSocketHandler.class);
HandshakeHandler handshakeHandler = handler.getHandshakeHandler();
assertNotNull(handshakeHandler);
assertTrue(handshakeHandler instanceof DefaultHandshakeHandler);
assertFalse(handler.getHandshakeInterceptors().isEmpty());
assertTrue(handler.getHandshakeInterceptors().get(0) instanceof OriginHandshakeInterceptor);
}
else {
assertThat(shm.getUrlMap().keySet(), contains("/test"));
WebSocketHttpRequestHandler handler = (WebSocketHttpRequestHandler) shm.getUrlMap().get("/test");
assertNotNull(handler);
unwrapAndCheckDecoratedHandlerType(handler.getWebSocketHandler(), TestWebSocketHandler.class);
HandshakeHandler handshakeHandler = handler.getHandshakeHandler();
assertNotNull(handshakeHandler);
assertTrue(handshakeHandler instanceof DefaultHandshakeHandler);
assertFalse(handler.getHandshakeInterceptors().isEmpty());
assertTrue(handler.getHandshakeInterceptors().get(0) instanceof OriginHandshakeInterceptor);
}
}
}
@Test
@SuppressWarnings("unchecked")
public void webSocketHandlersAttributes() {
loadBeanDefinitions("websocket-config-handlers-attributes.xml");
HandlerMapping handlerMapping = this.appContext.getBean(HandlerMapping.class);
assertNotNull(handlerMapping);
assertTrue(handlerMapping instanceof SimpleUrlHandlerMapping);
SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping;
assertEquals(2, urlHandlerMapping.getOrder());
WebSocketHttpRequestHandler handler = (WebSocketHttpRequestHandler) urlHandlerMapping.getUrlMap().get("/foo");
assertNotNull(handler);
unwrapAndCheckDecoratedHandlerType(handler.getWebSocketHandler(), FooWebSocketHandler.class);
HandshakeHandler handshakeHandler = handler.getHandshakeHandler();
assertNotNull(handshakeHandler);
assertTrue(handshakeHandler instanceof TestHandshakeHandler);
List<HandshakeInterceptor> interceptors = handler.getHandshakeInterceptors();
assertThat(interceptors, contains(instanceOf(FooTestInterceptor.class),
instanceOf(BarTestInterceptor.class), instanceOf(OriginHandshakeInterceptor.class)));
handler = (WebSocketHttpRequestHandler) urlHandlerMapping.getUrlMap().get("/test");
assertNotNull(handler);
unwrapAndCheckDecoratedHandlerType(handler.getWebSocketHandler(), TestWebSocketHandler.class);
handshakeHandler = handler.getHandshakeHandler();
assertNotNull(handshakeHandler);
assertTrue(handshakeHandler instanceof TestHandshakeHandler);
interceptors = handler.getHandshakeInterceptors();
assertThat(interceptors, contains(instanceOf(FooTestInterceptor.class),
instanceOf(BarTestInterceptor.class), instanceOf(OriginHandshakeInterceptor.class)));
}
@Test
@SuppressWarnings("unchecked")
public void sockJs() {
loadBeanDefinitions("websocket-config-handlers-sockjs.xml");
SimpleUrlHandlerMapping handlerMapping = this.appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(handlerMapping);
SockJsHttpRequestHandler testHandler = (SockJsHttpRequestHandler) handlerMapping.getUrlMap().get("/test/**");
assertNotNull(testHandler);
unwrapAndCheckDecoratedHandlerType(testHandler.getWebSocketHandler(), TestWebSocketHandler.class);
SockJsService testSockJsService = testHandler.getSockJsService();
SockJsHttpRequestHandler fooHandler = (SockJsHttpRequestHandler) handlerMapping.getUrlMap().get("/foo/**");
assertNotNull(fooHandler);
unwrapAndCheckDecoratedHandlerType(fooHandler.getWebSocketHandler(), FooWebSocketHandler.class);
SockJsService sockJsService = fooHandler.getSockJsService();
assertNotNull(sockJsService);
assertSame(testSockJsService, sockJsService);
assertThat(sockJsService, instanceOf(DefaultSockJsService.class));
DefaultSockJsService defaultSockJsService = (DefaultSockJsService) sockJsService;
assertThat(defaultSockJsService.getTaskScheduler(), instanceOf(ThreadPoolTaskScheduler.class));
assertFalse(defaultSockJsService.shouldSuppressCors());
Map<TransportType, TransportHandler> transportHandlers = defaultSockJsService.getTransportHandlers();
assertThat(transportHandlers.values(),
containsInAnyOrder(
instanceOf(XhrPollingTransportHandler.class),
instanceOf(XhrReceivingTransportHandler.class),
instanceOf(JsonpPollingTransportHandler.class),
instanceOf(JsonpReceivingTransportHandler.class),
instanceOf(XhrStreamingTransportHandler.class),
instanceOf(EventSourceTransportHandler.class),
instanceOf(HtmlFileTransportHandler.class),
instanceOf(WebSocketTransportHandler.class)));
WebSocketTransportHandler handler = (WebSocketTransportHandler) transportHandlers.get(TransportType.WEBSOCKET);
assertEquals(TestHandshakeHandler.class, handler.getHandshakeHandler().getClass());
List<HandshakeInterceptor> interceptors = defaultSockJsService.getHandshakeInterceptors();
assertThat(interceptors, contains(instanceOf(FooTestInterceptor.class), instanceOf(BarTestInterceptor.class), instanceOf(OriginHandshakeInterceptor.class)));
}
@Test
@SuppressWarnings("unchecked")
public void sockJsAttributes() {
loadBeanDefinitions("websocket-config-handlers-sockjs-attributes.xml");
SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(handlerMapping);
SockJsHttpRequestHandler handler = (SockJsHttpRequestHandler) handlerMapping.getUrlMap().get("/test/**");
assertNotNull(handler);
unwrapAndCheckDecoratedHandlerType(handler.getWebSocketHandler(), TestWebSocketHandler.class);
SockJsService sockJsService = handler.getSockJsService();
assertNotNull(sockJsService);
assertThat(sockJsService, instanceOf(TransportHandlingSockJsService.class));
TransportHandlingSockJsService transportService = (TransportHandlingSockJsService) sockJsService;
assertThat(transportService.getTaskScheduler(), instanceOf(TestTaskScheduler.class));
assertThat(transportService.getTransportHandlers().values(),
containsInAnyOrder(
instanceOf(XhrPollingTransportHandler.class),
instanceOf(XhrStreamingTransportHandler.class)));
assertEquals("testSockJsService", transportService.getName());
assertFalse(transportService.isWebSocketEnabled());
assertFalse(transportService.isSessionCookieNeeded());
assertEquals(2048, transportService.getStreamBytesLimit());
assertEquals(256, transportService.getDisconnectDelay());
assertEquals(1024, transportService.getHttpMessageCacheSize());
assertEquals(20, transportService.getHeartbeatTime());
assertEquals("/js/sockjs.min.js", transportService.getSockJsClientLibraryUrl());
assertEquals(TestMessageCodec.class, transportService.getMessageCodec().getClass());
List<HandshakeInterceptor> interceptors = transportService.getHandshakeInterceptors();
assertThat(interceptors, contains(instanceOf(OriginHandshakeInterceptor.class)));
assertTrue(transportService.shouldSuppressCors());
assertTrue(transportService.getAllowedOrigins().contains("http://mydomain1.com"));
assertTrue(transportService.getAllowedOrigins().contains("http://mydomain2.com"));
}
private void loadBeanDefinitions(String fileName) {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.appContext);
ClassPathResource resource = new ClassPathResource(fileName, HandlersBeanDefinitionParserTests.class);
reader.loadBeanDefinitions(resource);
this.appContext.refresh();
}
private static void unwrapAndCheckDecoratedHandlerType(WebSocketHandler handler, Class<?> handlerClass) {
if (handler instanceof WebSocketHandlerDecorator) {
handler = ((WebSocketHandlerDecorator) handler).getLastHandler();
}
assertTrue(handlerClass.isInstance(handler));
}
}
class TestWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) {
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
class FooWebSocketHandler extends TestWebSocketHandler {
}
class TestHandshakeHandler implements HandshakeHandler {
@Override
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
return false;
}
}
class TestChannelInterceptor extends ChannelInterceptorAdapter {
}
class FooTestInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<String, Object> attributes) {
return false;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Exception exception) {
}
}
class BarTestInterceptor extends FooTestInterceptor {
}
@SuppressWarnings({ "unchecked", "rawtypes" })
class TestTaskScheduler implements TaskScheduler {
@Override
public ScheduledFuture schedule(Runnable task, Trigger trigger) {
return null;
}
@Override
public ScheduledFuture schedule(Runnable task, Date startTime) {
return null;
}
@Override
public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) {
return null;
}
@Override
public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) {
return null;
}
@Override
public ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
return null;
}
@Override
public ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay) {
return null;
}
}
class TestMessageCodec implements SockJsMessageCodec {
@Override
public String encode(String... messages) {
return null;
}
@Override
public String[] decode(String content) throws IOException {
return new String[0];
}
@Override
public String[] decodeInputStream(InputStream content) throws IOException {
return new String[0];
}
}