From 1f9b833c4df1c065f9e7d886caacbdb4a216953f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 8 Dec 2013 01:25:52 +0100 Subject: [PATCH] Introduced SockJsSession interface and moved SockJsSessionFactory and SockJsServiceConfig to sockjs.transport; added initialize(SockJsServiceConfig) method to TransportHandler interface; extracted TransportHandlingSockJsService from DefaultSockJsService; moved sockjs.support.frame to sockjs.frame and extracted (Default)SockJsFrameFormat from SockJsFrame; moved SockJsHttpRequestHandler to sockjs.support; removed Jackson 1.x support --- .../web/socket/WebSocketSession.java | 5 +- .../config/HandlersBeanDefinitionParser.java | 4 +- .../MessageBrokerBeanDefinitionParser.java | 2 +- .../config/WebSocketNamespaceUtils.java | 46 +-- .../ServletWebSocketHandlerRegistration.java | 2 +- .../annotation/SockJsServiceRegistration.java | 22 +- ...MvcStompWebSocketEndpointRegistration.java | 2 +- .../messaging/StompSubProtocolHandler.java | 51 ++- .../socket/server/HandshakeInterceptor.java | 5 +- .../web/socket/sockjs/SockJsService.java | 10 +- .../frame/AbstractSockJsMessageCodec.java | 2 +- .../frame/DefaultSockJsFrameFormat.java | 50 +++ .../frame/Jackson2SockJsMessageCodec.java | 9 +- .../{support => }/frame/SockJsFrame.java | 72 +--- .../sockjs/frame/SockJsFrameFormat.java | 27 ++ .../frame/SockJsMessageCodec.java | 2 +- .../{support => }/frame/package-info.java | 2 +- .../sockjs/support/AbstractSockJsService.java | 104 ++--- .../SockJsHttpRequestHandler.java | 27 +- .../frame/JacksonSockJsMessageCodec.java | 61 --- .../{session => }/SockJsServiceConfig.java | 32 +- .../sockjs/transport/SockJsSession.java | 36 ++ .../{handler => }/SockJsSessionFactory.java | 8 +- .../sockjs/transport/TransportHandler.java | 23 +- .../TransportHandlingSockJsService.java | 323 +++++++++++++++ ...AbstractHttpReceivingTransportHandler.java | 25 +- .../AbstractHttpSendingTransportHandler.java | 17 +- ...ort.java => AbstractTransportHandler.java} | 21 +- .../handler/DefaultSockJsService.java | 388 ++---------------- .../handler/EventSourceTransportHandler.java | 18 +- .../handler/HtmlFileTransportHandler.java | 17 +- .../handler/JsonpPollingTransportHandler.java | 24 +- .../JsonpReceivingTransportHandler.java | 6 +- .../handler/SockJsWebSocketHandler.java | 2 +- .../handler/WebSocketTransportHandler.java | 21 +- .../handler/XhrPollingTransportHandler.java | 15 +- .../handler/XhrReceivingTransportHandler.java | 4 +- .../handler/XhrStreamingTransportHandler.java | 18 +- .../session/AbstractHttpSockJsSession.java | 75 ++-- .../session/AbstractSockJsSession.java | 36 +- .../session/PollingSockJsSession.java | 14 +- .../session/StreamingSockJsSession.java | 11 +- .../session/WebSocketServerSockJsSession.java | 51 ++- .../HandlersBeanDefinitionParserTests.java | 11 +- ...essageBrokerBeanDefinitionParserTests.java | 2 +- .../WebMvcStompEndpointRegistrationTests.java | 2 +- .../support/AbstractSockJsServiceTests.java | 11 +- .../handler/DefaultSockJsServiceTests.java | 34 +- .../HttpReceivingTransportHandlerTests.java | 4 +- .../HttpSendingTransportHandlerTests.java | 19 +- .../AbstractHttpSockJsSessionTests.java | 12 +- .../session/AbstractSockJsSessionTests.java | 2 +- .../session/StubSockJsServiceConfig.java | 5 +- .../session/TestHttpSockJsSession.java | 3 +- .../transport/session/TestSockJsSession.java | 3 +- .../WebSocketServerSockJsSessionTests.java | 1 + 56 files changed, 905 insertions(+), 894 deletions(-) rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/{support => }/frame/AbstractSockJsMessageCodec.java (97%) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/DefaultSockJsFrameFormat.java rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/{support => }/frame/Jackson2SockJsMessageCodec.java (93%) rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/{support => }/frame/SockJsFrame.java (75%) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrameFormat.java rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/{support => }/frame/SockJsMessageCodec.java (96%) rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/{support => }/frame/package-info.java (92%) rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/{ => support}/SockJsHttpRequestHandler.java (80%) delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/JacksonSockJsMessageCodec.java rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/{session => }/SockJsServiceConfig.java (89%) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSession.java rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/{handler => }/SockJsSessionFactory.java (73%) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java rename spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/{TransportHandlerSupport.java => AbstractTransportHandler.java} (61%) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java index a4634ddc39..b882aaa747 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java @@ -26,15 +26,14 @@ import java.util.Map; import org.springframework.http.HttpHeaders; /** - * A WebSocket session abstraction. Allows sending messages over a WebSocket connection - * and closing it. + * A WebSocket session abstraction. Allows sending messages over a WebSocket + * connection and closing it. * * @author Rossen Stoyanchev * @since 4.0 */ public interface WebSocketSession { - /** * Return a unique session identifier. */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/HandlersBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/HandlersBeanDefinitionParser.java index 227d0b1a17..68119d5d2e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/HandlersBeanDefinitionParser.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/HandlersBeanDefinitionParser.java @@ -33,9 +33,8 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.xml.DomUtils; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; -import org.springframework.web.socket.config.WebSocketNamespaceUtils; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler; /** * A {@link BeanDefinitionParser} that provides the configuration for the @@ -129,7 +128,6 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser { @Override public ManagedMap createMappings(Element mappingElement, ParserContext parserContext) { - ManagedMap urlMap = new ManagedMap(); Object source = parserContext.extractSource(mappingElement); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java index e5cf43fe07..c152e84ec6 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParser.java @@ -55,7 +55,7 @@ import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.socket.messaging.StompSubProtocolHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler; /** diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java index 5f44c9e315..420a810bb4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/WebSocketNamespaceUtils.java @@ -16,12 +16,9 @@ package org.springframework.web.socket.config; -import java.util.Collections; - import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.ManagedList; @@ -30,6 +27,7 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.xml.DomUtils; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; +import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService; import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; /** @@ -63,54 +61,36 @@ class WebSocketNamespaceUtils { Element sockJsElement = DomUtils.getChildElementByTagName(element, "sockjs"); if (sockJsElement != null) { - ConstructorArgumentValues cavs = new ConstructorArgumentValues(); + RootBeanDefinition sockJsServiceDef = new RootBeanDefinition(DefaultSockJsService.class); + sockJsServiceDef.setSource(source); + Object scheduler; String customTaskSchedulerName = sockJsElement.getAttribute("scheduler"); if (!customTaskSchedulerName.isEmpty()) { - cavs.addIndexedArgumentValue(0, new RuntimeBeanReference(customTaskSchedulerName)); + scheduler = new RuntimeBeanReference(customTaskSchedulerName); } else { - cavs.addIndexedArgumentValue(0, registerSockJsTaskScheduler(sockJsSchedulerName, parserContext, source)); + scheduler = registerSockJsTaskScheduler(sockJsSchedulerName, parserContext, source); } + sockJsServiceDef.getConstructorArgumentValues().addIndexedArgumentValue(0, scheduler); Element transportHandlersElement = DomUtils.getChildElementByTagName(sockJsElement, "transport-handlers"); - boolean registerDefaults = true; if (transportHandlersElement != null) { String registerDefaultsAttribute = transportHandlersElement.getAttribute("register-defaults"); - registerDefaults = !registerDefaultsAttribute.equals("false"); + if (registerDefaultsAttribute.equals("false")) { + sockJsServiceDef.setBeanClass(TransportHandlingSockJsService.class); + } + ManagedList transportHandlersList = parseBeanSubElements(transportHandlersElement, parserContext); + sockJsServiceDef.getConstructorArgumentValues().addIndexedArgumentValue(1, transportHandlersList); } - ManagedList transportHandlersList = parseBeanSubElements(transportHandlersElement, parserContext); - - if (registerDefaults) { - cavs.addIndexedArgumentValue(1, Collections.emptyList()); - if (transportHandlersList.isEmpty()) { - cavs.addIndexedArgumentValue(2, new ConstructorArgumentValues.ValueHolder(null)); - } - else { - cavs.addIndexedArgumentValue(2, transportHandlersList); - } - } - else { - if (transportHandlersList.isEmpty()) { - cavs.addIndexedArgumentValue(1, new ConstructorArgumentValues.ValueHolder(null)); - } - else { - cavs.addIndexedArgumentValue(1, transportHandlersList); - } - cavs.addIndexedArgumentValue(2, new ConstructorArgumentValues.ValueHolder(null)); - } - - RootBeanDefinition sockJsServiceDef = new RootBeanDefinition(DefaultSockJsService.class, cavs, null); - sockJsServiceDef.setSource(source); - String attrValue = sockJsElement.getAttribute("name"); if (!attrValue.isEmpty()) { sockJsServiceDef.getPropertyValues().add("name", attrValue); } attrValue = sockJsElement.getAttribute("websocket-enabled"); if (!attrValue.isEmpty()) { - sockJsServiceDef.getPropertyValues().add("webSocketsEnabled", Boolean.valueOf(attrValue)); + sockJsServiceDef.getPropertyValues().add("webSocketEnabled", Boolean.valueOf(attrValue)); } attrValue = sockJsElement.getAttribute("session-cookie-needed"); if (!attrValue.isEmpty()) { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/ServletWebSocketHandlerRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/ServletWebSocketHandlerRegistration.java index b3709ea722..5905510e7d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/ServletWebSocketHandlerRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/ServletWebSocketHandlerRegistration.java @@ -27,7 +27,7 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler; import org.springframework.web.socket.sockjs.SockJsService; /** diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java index 80f876c9aa..20507b4e76 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java @@ -21,11 +21,13 @@ import java.util.Arrays; import java.util.List; import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.sockjs.SockJsService; import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; +import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService; /** * A helper class for configuring SockJS fallback options, typically used indirectly, in @@ -190,7 +192,8 @@ public class SockJsServiceRegistration { } protected SockJsService getSockJsService() { - DefaultSockJsService service = createSockJsService(); + TransportHandlingSockJsService service = createSockJsService(); + service.setHandshakeInterceptors(this.interceptors); if (this.clientLibraryUrl != null) { service.setSockJsClientLibraryUrl(this.clientLibraryUrl); } @@ -204,21 +207,26 @@ public class SockJsServiceRegistration { service.setHeartbeatTime(this.heartbeatTime); } if (this.disconnectDelay != null) { - service.setDisconnectDelay(this.heartbeatTime); + service.setDisconnectDelay(this.disconnectDelay); } if (this.httpMessageCacheSize != null) { service.setHttpMessageCacheSize(this.httpMessageCacheSize); } if (this.webSocketEnabled != null) { - service.setWebSocketsEnabled(this.webSocketEnabled); + service.setWebSocketEnabled(this.webSocketEnabled); } - service.setHandshakeInterceptors(this.interceptors); return service; } - private DefaultSockJsService createSockJsService() { - return new DefaultSockJsService(this.taskScheduler, this.transportHandlers, - this.transportHandlerOverrides.toArray(new TransportHandler[this.transportHandlerOverrides.size()])); + private TransportHandlingSockJsService createSockJsService() { + if (!this.transportHandlers.isEmpty()) { + Assert.state(this.transportHandlerOverrides.isEmpty(), + "Specify either TransportHandlers or TransportHandler overrides, not both"); + return new TransportHandlingSockJsService(this.taskScheduler, this.transportHandlers); + } + else { + return new DefaultSockJsService(this.taskScheduler, this.transportHandlerOverrides); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java index 0844b4cd5f..8b0bc31a42 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompWebSocketEndpointRegistration.java @@ -24,7 +24,7 @@ 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.support.SockJsHttpRequestHandler; import org.springframework.web.socket.sockjs.SockJsService; import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java index 0075f6b357..636baa5618 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java @@ -26,11 +26,16 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.SimpMessageType; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompConversionException; +import org.springframework.messaging.simp.stomp.StompDecoder; +import org.springframework.messaging.simp.stomp.StompEncoder; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.simp.user.UserSessionRegistry; -import org.springframework.messaging.simp.stomp.*; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.Assert; import org.springframework.web.socket.CloseStatus; @@ -39,8 +44,8 @@ import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; /** - * A {@link SubProtocolHandler} for STOMP that supports versions 1.0, 1.1, and 1.2 of the - * STOMP specification. + * A {@link SubProtocolHandler} for STOMP that supports versions 1.0, 1.1, and 1.2 + * of the STOMP specification. * * @author Rossen Stoyanchev * @author Andy Wilkinson @@ -54,6 +59,8 @@ public class StompSubProtocolHandler implements SubProtocolHandler { */ public static final String CONNECTED_USER_HEADER = "user-name"; + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private static final Log logger = LogFactory.getLog(StompSubProtocolHandler.class); @@ -94,22 +101,24 @@ public class StompSubProtocolHandler implements SubProtocolHandler { try { Assert.isInstanceOf(TextMessage.class, webSocketMessage); String payload = ((TextMessage)webSocketMessage).getPayload(); - ByteBuffer byteBuffer = ByteBuffer.wrap(payload.getBytes(Charset.forName("UTF-8"))); + ByteBuffer byteBuffer = ByteBuffer.wrap(payload.getBytes(UTF8_CHARSET)); message = this.stompDecoder.decode(byteBuffer); } - catch (Throwable error) { - logger.error("Failed to parse STOMP frame, WebSocket message payload: ", error); - sendErrorMessage(session, error); + catch (Throwable ex) { + logger.error("Failed to parse STOMP frame, WebSocket message payload", ex); + sendErrorMessage(session, ex); return; } try { StompHeaderAccessor headers = StompHeaderAccessor.wrap(message); - if (SimpMessageType.HEARTBEAT.equals(headers.getMessageType())) { - logger.trace("Received heartbeat from client session=" + session.getId()); - } - else { - logger.trace("Received message from client session=" + session.getId()); + if (logger.isTraceEnabled()) { + if (SimpMessageType.HEARTBEAT.equals(headers.getMessageType())) { + logger.trace("Received heartbeat from client session=" + session.getId()); + } + else { + logger.trace("Received message from client session=" + session.getId()); + } } headers.setSessionId(session.getId()); @@ -118,9 +127,9 @@ public class StompSubProtocolHandler implements SubProtocolHandler { message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build(); outputChannel.send(message); } - catch (Throwable t) { - logger.error("Terminating STOMP session due to failure to send message: ", t); - sendErrorMessage(session, t); + catch (Throwable ex) { + logger.error("Terminating STOMP session due to failure to send message", ex); + sendErrorMessage(session, ex); } } @@ -129,11 +138,11 @@ public class StompSubProtocolHandler implements SubProtocolHandler { StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.ERROR); headers.setMessage(error.getMessage()); Message message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build(); - String payload = new String(this.stompEncoder.encode(message), Charset.forName("UTF-8")); + String payload = new String(this.stompEncoder.encode(message), UTF8_CHARSET); try { session.sendMessage(new TextMessage(payload)); } - catch (Throwable t) { + catch (Throwable ex) { // ignore } } @@ -176,19 +185,19 @@ public class StompSubProtocolHandler implements SubProtocolHandler { byte[] bytes = this.stompEncoder.encode((Message) message); synchronized(session) { - session.sendMessage(new TextMessage(new String(bytes, Charset.forName("UTF-8")))); + session.sendMessage(new TextMessage(new String(bytes, UTF8_CHARSET))); } } - catch (Throwable t) { - sendErrorMessage(session, t); + catch (Throwable ex) { + sendErrorMessage(session, ex); } finally { if (StompCommand.ERROR.equals(headers.getCommand())) { try { session.close(CloseStatus.PROTOCOL_ERROR); } - catch (IOException e) { + catch (IOException ex) { // Ignore } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java index 02858e652c..262d81e5de 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeInterceptor.java @@ -24,13 +24,12 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; /** - * Interceptor for WebSocket handshake requests. Can be used to inspect the handshake - * request and response as well as to pass attributes to the target + * Interceptor for WebSocket handshake requests. Can be used to inspect the + * handshake request and response as well as to pass attributes to the target * {@link WebSocketHandler}. * * @author Rossen Stoyanchev * @since 4.0 - * * @see org.springframework.web.socket.server.support.WebSocketHttpRequestHandler * @see org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService */ diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java index a5375e1613..f54b2c0fd6 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java @@ -24,15 +24,15 @@ import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator /** * The main entry point for processing HTTP requests from SockJS clients. * - *

In a Servlet 3+ container, {@link SockJsHttpRequestHandler} can be used to invoke this - * service. The processing servlet, as well as all filters involved, must have - * asynchronous support enabled through the ServletContext API or by adding an + *

In a Servlet 3+ container, {@link org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler} + * can be used to invoke this service. The processing servlet, as well as all filters involved, + * must have asynchronous support enabled through the ServletContext API or by adding an * {@code true} element to servlet and filter declarations * in web.xml. * * @author Rossen Stoyanchev * @since 4.0 - * @see SockJsHttpRequestHandler + * @see org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler */ public interface SockJsService { @@ -53,7 +53,7 @@ public interface SockJsService { * exceptions from the WebSocketHandler can be handled internally or through * {@link ExceptionWebSocketHandlerDecorator} or some alternative decorator. * The former is automatically added when using - * {@link SockJsHttpRequestHandler}. + * {@link org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler}. */ void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, WebSocketHandler handler) throws SockJsException; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/AbstractSockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/AbstractSockJsMessageCodec.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/AbstractSockJsMessageCodec.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/AbstractSockJsMessageCodec.java index 87b93dc706..d73bad6c1a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/AbstractSockJsMessageCodec.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/AbstractSockJsMessageCodec.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs.support.frame; +package org.springframework.web.socket.sockjs.frame; import org.springframework.util.Assert; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/DefaultSockJsFrameFormat.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/DefaultSockJsFrameFormat.java new file mode 100644 index 0000000000..56220f5f46 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/DefaultSockJsFrameFormat.java @@ -0,0 +1,50 @@ +/* + * 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.sockjs.frame; + +import org.springframework.util.Assert; + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class DefaultSockJsFrameFormat implements SockJsFrameFormat { + + private final String format; + + + public DefaultSockJsFrameFormat(String format) { + Assert.notNull(format, "format must not be null"); + this.format = format; + } + + + /** + * @param frame the SockJs frame. + * @return new SockJsFrame instance with the formatted content + */ + @Override + public SockJsFrame format(SockJsFrame frame) { + String content = String.format(this.format, preProcessContent(frame.getContent())); + return new SockJsFrame(content); + } + + protected String preProcessContent(String content) { + return content; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/Jackson2SockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/Jackson2SockJsMessageCodec.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java index edd1fbb720..7c10571004 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/Jackson2SockJsMessageCodec.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/Jackson2SockJsMessageCodec.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs.support.frame; +package org.springframework.web.socket.sockjs.frame; import java.io.IOException; import java.io.InputStream; -import org.springframework.util.Assert; - import com.fasterxml.jackson.core.io.JsonStringEncoder; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.util.Assert; + /** - * A Jackson 2 codec for encoding and decoding SockJS messages. + * A Jackson 2.x codec for encoding and decoding SockJS messages. * * @author Rossen Stoyanchev * @since 4.0 @@ -44,6 +44,7 @@ public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec { this.objectMapper = objectMapper; } + @Override public String[] decode(String content) throws IOException { return this.objectMapper.readValue(content, String[].class); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrame.java similarity index 75% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/SockJsFrame.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrame.java index 8f6d39e3e6..fbd5ca13bb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrame.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs.support.frame; +package org.springframework.web.socket.sockjs.frame; import java.nio.charset.Charset; @@ -28,6 +28,8 @@ import org.springframework.util.Assert; */ public class SockJsFrame { + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private static final SockJsFrame openFrame = new SockJsFrame("o"); private static final SockJsFrame heartbeatFrame = new SockJsFrame("h"); @@ -37,15 +39,6 @@ public class SockJsFrame { private static final SockJsFrame closeAnotherConnectionOpenFrame = closeFrame(2010, "Another connection still open"); - private final String content; - - - private SockJsFrame(String content) { - Assert.notNull("Content must not be null"); - this.content = content; - } - - public static SockJsFrame openFrame() { return openFrame; } @@ -72,26 +65,21 @@ public class SockJsFrame { } + private final String content; + + + public SockJsFrame(String content) { + Assert.notNull("Content must not be null"); + this.content = content; + } + + public String getContent() { return this.content; } public byte[] getContentBytes() { - return this.content.getBytes(Charset.forName("UTF-8")); - } - - @Override - public String toString() { - String result = this.content; - if (result.length() > 80) { - result = result.substring(0, 80) + "...(truncated)"; - } - return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'"; - } - - @Override - public int hashCode() { - return this.content.hashCode(); + return this.content.getBytes(UTF8_CHARSET); } @Override @@ -105,34 +93,18 @@ public class SockJsFrame { return this.content.equals(((SockJsFrame) other).content); } - - public interface FrameFormat { - - SockJsFrame format(SockJsFrame frame); + @Override + public int hashCode() { + return this.content.hashCode(); } - public static class DefaultFrameFormat implements FrameFormat { - - private final String format; - - public DefaultFrameFormat(String format) { - Assert.notNull(format, "format must not be null"); - this.format = format; - } - - /** - * @param frame the SockJs frame. - * @return new SockJsFrame instance with the formatted content - */ - @Override - public SockJsFrame format(SockJsFrame frame) { - String content = String.format(this.format, preProcessContent(frame.getContent())); - return new SockJsFrame(content); - } - - protected String preProcessContent(String content) { - return content; + @Override + public String toString() { + String result = this.content; + if (result.length() > 80) { + result = result.substring(0, 80) + "...(truncated)"; } + return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'"; } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrameFormat.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrameFormat.java new file mode 100644 index 0000000000..858d2476ed --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsFrameFormat.java @@ -0,0 +1,27 @@ +/* + * 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.sockjs.frame; + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsFrameFormat { + + SockJsFrame format(SockJsFrame frame); + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/SockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsMessageCodec.java similarity index 96% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/SockJsMessageCodec.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsMessageCodec.java index 47fe0d070e..f2e89158a8 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/SockJsMessageCodec.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/SockJsMessageCodec.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs.support.frame; +package org.springframework.web.socket.sockjs.frame; import java.io.IOException; import java.io.InputStream; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/package-info.java similarity index 92% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/package-info.java index d29096e92e..2a3a8d95a0 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/package-info.java @@ -18,5 +18,5 @@ * Support classes for creating SockJS frames including the encoding and decoding * of SockJS message frames. */ -package org.springframework.web.socket.sockjs.support.frame; +package org.springframework.web.socket.sockjs.frame; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java index 40a538edb0..cdf196c59c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; @@ -53,10 +54,16 @@ import org.springframework.web.socket.sockjs.SockJsService; */ public abstract class AbstractSockJsService implements SockJsService { - protected final Log logger = LogFactory.getLog(getClass()); + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); private static final long ONE_YEAR = TimeUnit.DAYS.toSeconds(365); + private static final Random random = new Random(); + + + protected final Log logger = LogFactory.getLog(getClass()); + + private final TaskScheduler taskScheduler; private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this); @@ -72,13 +79,11 @@ public abstract class AbstractSockJsService implements SockJsService { private int httpMessageCacheSize = 100; - private boolean webSocketsEnabled = true; - - private final TaskScheduler taskScheduler; + private boolean webSocketEnabled = true; public AbstractSockJsService(TaskScheduler scheduler) { - Assert.notNull(scheduler, "scheduler must not be null"); + Assert.notNull(scheduler, "TaskScheduler must not be null"); this.taskScheduler = scheduler; } @@ -223,22 +228,23 @@ public abstract class AbstractSockJsService implements SockJsService { } /** - * Some load balancers don't support websockets. This option can be used to + * Some load balancers don't support WebSocket. This option can be used to * disable the WebSocket transport on the server side. *

The default value is "true". */ - public void setWebSocketsEnabled(boolean webSocketsEnabled) { - this.webSocketsEnabled = webSocketsEnabled; + public void setWebSocketEnabled(boolean webSocketEnabled) { + this.webSocketEnabled = webSocketEnabled; } /** * Whether WebSocket transport is enabled. - * @see #setWebSocketsEnabled(boolean) + * @see #setWebSocketEnabled(boolean) */ public boolean isWebSocketEnabled() { - return this.webSocketsEnabled; + return this.webSocketEnabled; } + /** * {@inheritDoc} *

This method determines the SockJS path and handles SockJS static URLs. Session @@ -249,18 +255,24 @@ public abstract class AbstractSockJsService implements SockJsService { String sockJsPath, WebSocketHandler wsHandler) throws SockJsException { if (sockJsPath == null) { - logger.warn("No SockJS path provided, URI=\"" + request.getURI()); + if (logger.isWarnEnabled()) { + logger.warn("No SockJS path provided, URI=\"" + request.getURI()); + } response.setStatusCode(HttpStatus.NOT_FOUND); return; } - logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]"); + if (logger.isDebugEnabled()) { + logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]"); + } try { request.getHeaders(); } catch (InvalidMediaTypeException ex) { - logger.warn("Invalid media type ignored: " + ex.getMediaType()); + if (logger.isWarnEnabled()) { + logger.warn("Invalid media type ignored: " + ex.getMediaType()); + } } try { @@ -275,12 +287,16 @@ public abstract class AbstractSockJsService implements SockJsService { this.iframeHandler.handle(request, response); } else if (sockJsPath.equals("/websocket")) { - handleRawWebSocketRequest(request, response, wsHandler); + if (isWebSocketEnabled()) { + handleRawWebSocketRequest(request, response, wsHandler); + } } else { String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); if (pathSegments.length != 3) { - logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\""); + if (logger.isWarnEnabled()) { + logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\""); + } response.setStatusCode(HttpStatus.NOT_FOUND); return; } @@ -301,27 +317,7 @@ public abstract class AbstractSockJsService implements SockJsService { } } - /** - * Validate whether the given transport String extracted from the URL is a valid - * SockJS transport type (regardless of whether a transport handler is configured). - */ - protected abstract boolean isValidTransportType(String transportType); - - /** - * Handle request for raw WebSocket communication, i.e. without any SockJS message framing. - */ - protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, - ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException; - - /** - * Handle a SockJS session URL (i.e. transport-specific request). - */ - protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, String sessionId, String transport) throws SockJsException; - - protected boolean validateRequest(String serverId, String sessionId, String transport) { - if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) { logger.warn("Empty server, session, or transport value"); return false; @@ -341,8 +337,21 @@ public abstract class AbstractSockJsService implements SockJsService { return true; } - protected void addCorsHeaders(ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) { + /** + * Handle request for raw WebSocket communication, i.e. without any SockJS message framing. + */ + protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, + ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException; + + /** + * Handle a SockJS session URL (i.e. transport-specific request). + */ + protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler webSocketHandler, String sessionId, String transport) throws SockJsException; + + + protected void addCorsHeaders(ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) { String origin = request.getHeaders().getFirst("origin"); origin = ((origin == null) || origin.equals("null")) ? "*" : origin; @@ -371,10 +380,10 @@ public abstract class AbstractSockJsService implements SockJsService { response.getHeaders().setCacheControl("no-store, no-cache, must-revalidate, max-age=0"); } - protected void sendMethodNotAllowed(ServerHttpResponse response, List httpMethods) { + protected void sendMethodNotAllowed(ServerHttpResponse response, HttpMethod... httpMethods) { logger.debug("Sending Method Not Allowed (405)"); response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED); - response.getHeaders().setAllow(new HashSet(httpMethods)); + response.getHeaders().setAllow(new HashSet(Arrays.asList(httpMethods))); } @@ -384,8 +393,6 @@ public abstract class AbstractSockJsService implements SockJsService { } - private static final Random random = new Random(); - private final SockJsRequestHandler infoHandler = new SockJsRequestHandler() { private static final String INFO_CONTENT = @@ -393,26 +400,20 @@ public abstract class AbstractSockJsService implements SockJsService { @Override public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { - if (HttpMethod.GET.equals(request.getMethod())) { - - response.getHeaders().setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); - + response.getHeaders().setContentType(new MediaType("application", "json", UTF8_CHARSET)); addCorsHeaders(request, response); addNoCacheHeaders(response); - String content = String.format(INFO_CONTENT, random.nextInt(), isSessionCookieNeeded(), isWebSocketEnabled()); response.getBody().write(content.getBytes()); } else if (HttpMethod.OPTIONS.equals(request.getMethod())) { - response.setStatusCode(HttpStatus.NO_CONTENT); - addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET); addCacheHeaders(response); } else { - sendMethodNotAllowed(response, Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET)); + sendMethodNotAllowed(response, HttpMethod.OPTIONS, HttpMethod.GET); } } }; @@ -439,14 +440,13 @@ public abstract class AbstractSockJsService implements SockJsService { @Override public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { - if (!HttpMethod.GET.equals(request.getMethod())) { - sendMethodNotAllowed(response, Arrays.asList(HttpMethod.GET)); + sendMethodNotAllowed(response, HttpMethod.GET); return; } String content = String.format(IFRAME_CONTENT, getSockJsClientLibraryUrl()); - byte[] contentBytes = content.getBytes(Charset.forName("UTF-8")); + byte[] contentBytes = content.getBytes(UTF8_CHARSET); StringBuilder builder = new StringBuilder("\"0"); DigestUtils.appendMd5DigestAsHex(contentBytes, builder); builder.append('"'); @@ -458,7 +458,7 @@ public abstract class AbstractSockJsService implements SockJsService { return; } - response.getHeaders().setContentType(new MediaType("text", "html", Charset.forName("UTF-8"))); + response.getHeaders().setContentType(new MediaType("text", "html", UTF8_CHARSET)); response.getHeaders().setContentLength(contentBytes.length); addCacheHeaders(response); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java similarity index 80% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java index 477a912155..c54be5ec3c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs; +package org.springframework.web.socket.sockjs.support; import java.io.IOException; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,6 +31,8 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator; import org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator; +import org.springframework.web.socket.sockjs.SockJsException; +import org.springframework.web.socket.sockjs.SockJsService; /** * An {@link HttpRequestHandler} that allows mapping a {@link SockJsService} to requests @@ -44,19 +45,20 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { private final SockJsService sockJsService; - private final WebSocketHandler wsHandler; + private final WebSocketHandler webSocketHandler; /** - * Create a new {@link SockJsHttpRequestHandler}. + * Create a new SockJsHttpRequestHandler. * @param sockJsService the SockJS service - * @param wsHandler the websocket handler + * @param webSocketHandler the websocket handler */ - public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler wsHandler) { + public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler webSocketHandler) { Assert.notNull(sockJsService, "sockJsService must not be null"); - Assert.notNull(wsHandler, "webSocketHandler must not be null"); + Assert.notNull(webSocketHandler, "webSocketHandler must not be null"); this.sockJsService = sockJsService; - this.wsHandler = new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(wsHandler)); + this.webSocketHandler = + new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(webSocketHandler)); } @@ -71,9 +73,10 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { * Return the {@link WebSocketHandler}. */ public WebSocketHandler getWebSocketHandler() { - return this.wsHandler; + return this.webSocketHandler; } + @Override public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException { @@ -82,10 +85,10 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { ServerHttpResponse response = new ServletServerHttpResponse(servletResponse); try { - this.sockJsService.handleRequest(request, response, getSockJsPath(servletRequest), this.wsHandler); + this.sockJsService.handleRequest(request, response, getSockJsPath(servletRequest), this.webSocketHandler); } - catch (Throwable t) { - throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), t); + catch (Throwable ex) { + throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/JacksonSockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/JacksonSockJsMessageCodec.java deleted file mode 100644 index a16a180cfc..0000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/frame/JacksonSockJsMessageCodec.java +++ /dev/null @@ -1,61 +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.web.socket.sockjs.support.frame; - -import java.io.IOException; -import java.io.InputStream; - -import org.codehaus.jackson.io.JsonStringEncoder; -import org.codehaus.jackson.map.ObjectMapper; -import org.springframework.util.Assert; - -/** - * A Jackson 1.x codec for encoding and decoding SockJS messages. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class JacksonSockJsMessageCodec extends AbstractSockJsMessageCodec { - - private final ObjectMapper objectMapper; - - - public JacksonSockJsMessageCodec() { - this.objectMapper = new ObjectMapper(); - } - - public JacksonSockJsMessageCodec(ObjectMapper objectMapper) { - Assert.notNull(objectMapper, "ObjectMapper must not be null"); - this.objectMapper = objectMapper; - } - - @Override - public String[] decode(String content) throws IOException { - return this.objectMapper.readValue(content, String[].class); - } - - @Override - public String[] decodeInputStream(InputStream content) throws IOException { - return this.objectMapper.readValue(content, String[].class); - } - - @Override - protected char[] applyJsonQuoting(String content) { - return JsonStringEncoder.getInstance().quoteAsString(content); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/SockJsServiceConfig.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsServiceConfig.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/SockJsServiceConfig.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsServiceConfig.java index a0d44e9a2a..5093b1315d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/SockJsServiceConfig.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsServiceConfig.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs.transport.session; +package org.springframework.web.socket.sockjs.transport; import org.springframework.scheduling.TaskScheduler; import org.springframework.web.socket.sockjs.SockJsService; -import org.springframework.web.socket.sockjs.support.AbstractSockJsService; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; /** * Provides transport handling code with access to the {@link SockJsService} configuration @@ -30,6 +29,11 @@ import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; */ public interface SockJsServiceConfig { + /** + * A scheduler instance to use for scheduling heart-beat messages. + */ + TaskScheduler getTaskScheduler(); + /** * Streaming transports save responses on the client side and don't free * memory used by delivered messages. Such transports need to recycle the @@ -51,9 +55,16 @@ public interface SockJsServiceConfig { long getHeartbeatTime(); /** - * A scheduler instance to use for scheduling heart-beat messages. + * The number of server-to-client messages that a session can cache while waiting for + * the next HTTP polling request from the client. All HTTP transports use this + * property since even streaming transports recycle HTTP requests periodically. + *

The amount of time between HTTP requests should be relatively brief and will not + * exceed the allows disconnect delay (see + * {@link org.springframework.web.socket.sockjs.support.AbstractSockJsService#setDisconnectDelay(long)}, + * 5 seconds by default. + *

The default size is 100. */ - TaskScheduler getTaskScheduler(); + int getHttpMessageCacheSize(); /** * The codec to use for encoding and decoding SockJS messages. @@ -61,15 +72,4 @@ public interface SockJsServiceConfig { */ SockJsMessageCodec getMessageCodec(); - /** - * The number of server-to-client messages that a session can cache while waiting for - * the next HTTP polling request from the client. All HTTP transports use this - * property since even streaming transports recycle HTTP requests periodically. - *

The amount of time between HTTP requests should be relatively brief and will not - * exceed the allows disconnect delay (see - * {@link AbstractSockJsService#setDisconnectDelay(long)}, 5 seconds by default. - *

The default size is 100. - */ - int getHttpMessageCacheSize(); - } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSession.java new file mode 100644 index 0000000000..216216c008 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSession.java @@ -0,0 +1,36 @@ +/* + * 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.sockjs.transport; + +import org.springframework.web.socket.WebSocketSession; + +/** + * SockJS extension of Spring's standard {@link WebSocketSession}. + * + * @author Juergen Hoeller + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsSession extends WebSocketSession { + + /** + * Return the time (in ms) since the session was last active, or otherwise + * if the session is new, then the time since the session was created. + */ + long getTimeSinceLastActive(); + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSessionFactory.java similarity index 73% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsSessionFactory.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSessionFactory.java index b4d2c9748b..e1fbc79bcc 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsSessionFactory.java @@ -14,13 +14,11 @@ * limitations under the License. */ -package org.springframework.web.socket.sockjs.transport.handler; +package org.springframework.web.socket.sockjs.transport; import java.util.Map; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.transport.TransportHandler; -import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession; /** * A factory for creating a SockJS session. {@link TransportHandler}s typically also serve @@ -34,10 +32,10 @@ public interface SockJsSessionFactory { /** * Create a new SockJS session. * @param sessionId the ID of the session - * @param wsHandler the underlying {@link WebSocketHandler} + * @param handler the underlying {@link WebSocketHandler} * @param attributes handshake request specific attributes * @return a new session, never {@code null} */ - AbstractSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, Map attributes); + SockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandler.java index 069c519955..48fa86e485 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandler.java @@ -19,7 +19,6 @@ package org.springframework.web.socket.sockjs.transport; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsService; @@ -27,28 +26,34 @@ import org.springframework.web.socket.sockjs.SockJsService; * Handle a SockJS session URL, i.e. transport-specific request. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.0 */ public interface TransportHandler { /** - * @return the transport type supported by this handler + * Initialize this handler with the given configuration. + * @param serviceConfig the configuration as defined by the containing + * {@link org.springframework.web.socket.sockjs.SockJsService} + */ + void initialize(SockJsServiceConfig serviceConfig); + + /** + * Return the transport type supported by this handler. */ TransportType getTransportType(); /** * Handle the given request and delegate messages to the provided * {@link WebSocketHandler}. - * * @param request the current request * @param response the current response - * @param handler the target WebSocketHandler, never {@code null} - * @param session the SockJS session, never {@code null} - * - * @throws SockJsException raised when request processing fails as explained in - * {@link SockJsService} + * @param handler the target WebSocketHandler (never {@code null}) + * @param session the SockJS session (never {@code null}) + * @throws SockJsException raised when request processing fails as + * explained in {@link SockJsService} */ void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler handler, WebSocketSession session) throws SockJsException; + WebSocketHandler handler, SockJsSession session) throws SockJsException; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java new file mode 100644 index 0000000000..2a5b8b383e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlingSockJsService.java @@ -0,0 +1,323 @@ +/* + * 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.sockjs.transport; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeFailureException; +import org.springframework.web.socket.server.HandshakeHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; +import org.springframework.web.socket.server.support.HandshakeInterceptorChain; +import org.springframework.web.socket.sockjs.SockJsException; +import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.support.AbstractSockJsService; + +/** + * A basic implementation of {@link org.springframework.web.socket.sockjs.SockJsService} + * with support for SPI-based transport handling and session management. + * + *

Based on the {@link TransportHandler} SPI. {@link TransportHandler}s may additionally + * implement the {@link SockJsSessionFactory} and {@link HandshakeHandler} interfaces. + * + *

See the {@link AbstractSockJsService} base class for important details on request mapping. + * + * @author Rossen Stoyanchev + * @author Juergen Hoeller + * @since 4.0 + */ +public class TransportHandlingSockJsService extends AbstractSockJsService implements SockJsServiceConfig { + + private static final boolean jackson2Present = ClassUtils.isPresent( + "com.fasterxml.jackson.databind.ObjectMapper", TransportHandlingSockJsService.class.getClassLoader()); + + + private final Map handlers = new HashMap(); + + private SockJsMessageCodec messageCodec; + + private final List interceptors = new ArrayList(); + + private final Map sessions = new ConcurrentHashMap(); + + private ScheduledFuture sessionCleanupTask; + + + /** + * Create a TransportHandlingSockJsService with given {@link TransportHandler handler} types. + * @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions; + * the provided TaskScheduler should be declared as a Spring bean to ensure it gets + * initialized at start-up and shuts down when the application stops + * @param handlers one or more {@link TransportHandler} implementations to use + */ + public TransportHandlingSockJsService(TaskScheduler scheduler, TransportHandler... handlers) { + this(scheduler, Arrays.asList(handlers)); + } + + /** + * Create a TransportHandlingSockJsService with given {@link TransportHandler handler} types. + * @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions; + * the provided TaskScheduler should be declared as a Spring bean to ensure it gets + * initialized at start-up and shuts down when the application stops + * @param handlers one or more {@link TransportHandler} implementations to use + */ + public TransportHandlingSockJsService(TaskScheduler scheduler, Collection handlers) { + super(scheduler); + + if (CollectionUtils.isEmpty(handlers)) { + logger.warn("No transport handlers specified for TransportHandlingSockJsService"); + } + else { + for (TransportHandler handler : handlers) { + handler.initialize(this); + this.handlers.put(handler.getTransportType(), handler); + } + } + + if (jackson2Present) { + this.messageCodec = new Jackson2SockJsMessageCodec(); + } + } + + + /** + * Return the registered handlers per transport type. + */ + public Map getTransportHandlers() { + return Collections.unmodifiableMap(this.handlers); + } + + /** + * The codec to use for encoding and decoding SockJS messages. + */ + public void setMessageCodec(SockJsMessageCodec messageCodec) { + this.messageCodec = messageCodec; + } + + public SockJsMessageCodec getMessageCodec() { + Assert.state(this.messageCodec != null, "A SockJsMessageCodec is required but not available: " + + "Add Jackson 2 to the classpath, or configure a custom SockJsMessageCodec."); + return this.messageCodec; + } + + /** + * Configure one or more WebSocket handshake request interceptors. + */ + public void setHandshakeInterceptors(List interceptors) { + this.interceptors.clear(); + if (interceptors != null) { + this.interceptors.addAll(interceptors); + } + } + + /** + * Return the configured WebSocket handshake request interceptors. + */ + public List getHandshakeInterceptors() { + return Collections.unmodifiableList(this.interceptors); + } + + + @Override + protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler handler) throws IOException { + + TransportHandler transportHandler = this.handlers.get(TransportType.WEBSOCKET); + if (!(transportHandler instanceof HandshakeHandler)) { + logger.warn("No handler for raw WebSocket messages"); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + HandshakeInterceptorChain chain = new HandshakeInterceptorChain(this.interceptors, handler); + HandshakeFailureException failure = null; + + try { + Map attributes = new HashMap(); + if (!chain.applyBeforeHandshake(request, response, attributes)) { + return; + } + ((HandshakeHandler) transportHandler).doHandshake(request, response, handler, attributes); + chain.applyAfterHandshake(request, response, null); + } + catch (HandshakeFailureException ex) { + failure = ex; + } + catch (Throwable ex) { + failure = new HandshakeFailureException("Uncaught failure for request " + request.getURI(), ex); + } + finally { + if (failure != null) { + chain.applyAfterHandshake(request, response, failure); + throw failure; + } + } + } + + @Override + protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler handler, String sessionId, String transport) throws SockJsException { + + TransportType transportType = TransportType.fromValue(transport); + if (transportType == null) { + if (logger.isDebugEnabled()) { + logger.debug("Unknown transport type: " + transportType); + } + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + TransportHandler transportHandler = this.handlers.get(transportType); + if (transportHandler == null) { + logger.debug("Transport handler not found"); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + HttpMethod supportedMethod = transportType.getHttpMethod(); + if (!supportedMethod.equals(request.getMethod())) { + if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.supportsCors()) { + response.setStatusCode(HttpStatus.NO_CONTENT); + addCorsHeaders(request, response, HttpMethod.OPTIONS, supportedMethod); + addCacheHeaders(response); + } + else if (transportType.supportsCors()) { + sendMethodNotAllowed(response, supportedMethod, HttpMethod.OPTIONS); + } + else { + sendMethodNotAllowed(response, supportedMethod); + } + return; + } + + HandshakeInterceptorChain chain = new HandshakeInterceptorChain(this.interceptors, handler); + SockJsException failure = null; + + try { + SockJsSession session = this.sessions.get(sessionId); + if (session == null) { + if (transportHandler instanceof SockJsSessionFactory) { + Map attributes = new HashMap(); + if (!chain.applyBeforeHandshake(request, response, attributes)) { + return; + } + SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; + session = createSockJsSession(sessionId, sessionFactory, handler, attributes); + } + else { + response.setStatusCode(HttpStatus.NOT_FOUND); + logger.warn("Session not found"); + return; + } + } + + if (transportType.sendsNoCacheInstruction()) { + addNoCacheHeaders(response); + } + + if (transportType.supportsCors()) { + addCorsHeaders(request, response); + } + + transportHandler.handleRequest(request, response, handler, session); + chain.applyAfterHandshake(request, response, null); + } + catch (SockJsException ex) { + failure = ex; + } + catch (Throwable ex) { + failure = new SockJsException("Uncaught failure for request " + request.getURI(), sessionId, ex); + } + finally { + if (failure != null) { + chain.applyAfterHandshake(request, response, failure); + throw failure; + } + } + } + + private SockJsSession createSockJsSession(String sessionId, SockJsSessionFactory sessionFactory, + WebSocketHandler handler, Map handshakeAttributes) { + + synchronized (this.sessions) { + SockJsSession session = this.sessions.get(sessionId); + if (session != null) { + return session; + } + if (this.sessionCleanupTask == null) { + scheduleSessionTask(); + } + + if (logger.isDebugEnabled()) { + logger.debug("Creating new session with session id \"" + sessionId + "\""); + } + session = sessionFactory.createSession(sessionId, handler, handshakeAttributes); + this.sessions.put(sessionId, session); + return session; + } + } + + private void scheduleSessionTask() { + this.sessionCleanupTask = getTaskScheduler().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + int count = sessions.size(); + if (logger.isTraceEnabled() && (count != 0)) { + logger.trace("Checking " + count + " session(s) for timeouts [" + getName() + "]"); + } + for (SockJsSession session : sessions.values()) { + if (session.getTimeSinceLastActive() > getDisconnectDelay()) { + if (logger.isTraceEnabled()) { + logger.trace("Removing " + session + " for [" + getName() + "]"); + } + session.close(); + sessions.remove(session.getId()); + } + } + if (logger.isTraceEnabled() && count > 0) { + logger.trace(sessions.size() + " remaining session(s) [" + getName() + "]"); + } + } + catch (Throwable ex) { + if (logger.isErrorEnabled()) { + logger.error("Failed to complete session timeout checks for [" + getName() + "]", ex); + } + } + } + }, getDisconnectDelay()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpReceivingTransportHandler.java index 3634f59976..da451f4a25 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpReceivingTransportHandler.java @@ -17,34 +17,31 @@ package org.springframework.web.socket.sockjs.transport.handler; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Arrays; +import com.fasterxml.jackson.databind.JsonMappingException; + import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.sockjs.SockJsException; -import org.springframework.web.socket.sockjs.transport.TransportHandler; +import org.springframework.web.socket.sockjs.transport.SockJsSession; import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; -import com.fasterxml.jackson.databind.JsonMappingException; - /** * Base class for HTTP transport handlers that receive messages via HTTP POST. * * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractHttpReceivingTransportHandler - extends TransportHandlerSupport implements TransportHandler { +public abstract class AbstractHttpReceivingTransportHandler extends AbstractTransportHandler { @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException { + WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException { Assert.notNull(wsSession, "No session"); AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession; @@ -55,22 +52,22 @@ public abstract class AbstractHttpReceivingTransportHandler protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException { - String[] messages = null; + String[] messages; try { messages = readMessages(request); } catch (JsonMappingException ex) { - logger.error("Failed to read message: " + ex.getMessage()); + logger.error("Failed to read message", ex); handleReadError(response, "Payload expected.", sockJsSession.getId()); return; } catch (IOException ex) { - logger.error("Failed to read message: " + ex.getMessage()); + logger.error("Failed to read message", ex); handleReadError(response, "Broken JSON encoding.", sockJsSession.getId()); return; } - catch (Throwable t) { - logger.error("Failed to read message: " + t.getMessage()); + catch (Throwable ex) { + logger.error("Failed to read message", ex); handleReadError(response, "Failed to read message(s)", sockJsSession.getId()); return; } @@ -85,7 +82,7 @@ public abstract class AbstractHttpReceivingTransportHandler } response.setStatusCode(getResponseStatus()); - response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); + response.getHeaders().setContentType(new MediaType("text", "plain", UTF8_CHARSET)); sockJsSession.delegateMessages(messages); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java index 02094d75c5..6e373b4599 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractHttpSendingTransportHandler.java @@ -25,11 +25,11 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.sockjs.SockJsException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; -import org.springframework.web.socket.sockjs.transport.TransportHandler; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.transport.SockJsSession; +import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory; import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; @@ -40,13 +40,12 @@ import org.springframework.web.util.UriUtils; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractHttpSendingTransportHandler extends TransportHandlerSupport - implements TransportHandler, SockJsSessionFactory { - +public abstract class AbstractHttpSendingTransportHandler extends AbstractTransportHandler + implements SockJsSessionFactory { @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException { + WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException { AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession; @@ -95,7 +94,7 @@ public abstract class AbstractHttpSendingTransportHandler extends TransportHandl protected abstract MediaType getContentType(); - protected abstract FrameFormat getFrameFormat(ServerHttpRequest request); + protected abstract SockJsFrameFormat getFrameFormat(ServerHttpRequest request); protected final String getCallbackParam(ServerHttpRequest request) { String query = request.getURI().getQuery(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/TransportHandlerSupport.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractTransportHandler.java similarity index 61% rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/TransportHandlerSupport.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractTransportHandler.java index 1ccb2f9059..652ec69191 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/TransportHandlerSupport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/AbstractTransportHandler.java @@ -16,27 +16,34 @@ package org.springframework.web.socket.sockjs.transport.handler; +import java.nio.charset.Charset; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig; + +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; +import org.springframework.web.socket.sockjs.transport.TransportHandler; /** * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class TransportHandlerSupport { +public abstract class AbstractTransportHandler implements TransportHandler { + + protected static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); protected final Log logger = LogFactory.getLog(this.getClass()); - private SockJsServiceConfig sockJsServiceConfig; + private SockJsServiceConfig serviceConfig; - public void setSockJsServiceConfiguration(SockJsServiceConfig sockJsConfig) { - this.sockJsServiceConfig = sockJsConfig; + @Override + public void initialize(SockJsServiceConfig serviceConfig) { + this.serviceConfig = serviceConfig; } - public SockJsServiceConfig getSockJsServiceConfig() { - return this.sockJsServiceConfig; + public SockJsServiceConfig getServiceConfig() { + return this.serviceConfig; } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsService.java index d487daad45..1d3f263ed4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsService.java @@ -16,397 +16,87 @@ package org.springframework.web.socket.sockjs.transport.handler; -import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.LinkedHashSet; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.scheduling.TaskScheduler; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.server.HandshakeFailureException; -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.HandshakeInterceptorChain; -import org.springframework.web.socket.sockjs.SockJsException; -import org.springframework.web.socket.sockjs.SockJsService; -import org.springframework.web.socket.sockjs.support.AbstractSockJsService; -import org.springframework.web.socket.sockjs.support.frame.Jackson2SockJsMessageCodec; -import org.springframework.web.socket.sockjs.support.frame.JacksonSockJsMessageCodec; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; import org.springframework.web.socket.sockjs.transport.TransportHandler; -import org.springframework.web.socket.sockjs.transport.TransportType; -import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession; -import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig; +import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService; /** - * A default implementation of {@link SockJsService} adding support for transport handling - * and session management. See {@link AbstractSockJsService} base class for important - * details on request mapping. + * A default implementation of {@link org.springframework.web.socket.sockjs.SockJsService} + * with all default {@link TransportHandler} implementations pre-registered. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 4.0 */ -public class DefaultSockJsService extends AbstractSockJsService { - - private static final boolean jackson2Present = ClassUtils.isPresent( - "com.fasterxml.jackson.databind.ObjectMapper", DefaultSockJsService.class.getClassLoader()); - - private static final boolean jacksonPresent = ClassUtils.isPresent( - "org.codehaus.jackson.map.ObjectMapper", DefaultSockJsService.class.getClassLoader()); - - - private final Map transportHandlers = new HashMap(); - - private SockJsMessageCodec messageCodec; - - private final List interceptors = new ArrayList(); - - private final Map sessions = new ConcurrentHashMap(); - - private ScheduledFuture sessionCleanupTask; - +public class DefaultSockJsService extends TransportHandlingSockJsService { /** - * Create an instance with default {@link TransportHandler transport handler} types. - * @param taskScheduler a task scheduler for heart-beat messages and removing + * Create a DefaultSockJsService with default {@link TransportHandler handler} types. + * @param scheduler a task scheduler for heart-beat messages and removing * timed-out sessions; the provided TaskScheduler should be declared as a * Spring bean to ensure it is initialized at start up and shut down when the * application stops. */ - public DefaultSockJsService(TaskScheduler taskScheduler) { - this(taskScheduler, null); + public DefaultSockJsService(TaskScheduler scheduler) { + this(scheduler, getDefaultTransportHandlers(null)); } /** - * Create an instance by overriding or replacing completely the default - * {@link TransportHandler transport handler} types. - * @param taskScheduler a task scheduler for heart-beat messages and removing - * timed-out sessions; the provided TaskScheduler should be declared as a - * Spring bean to ensure it is initialized at start up and shut down when the - * application stops. - * @param transportHandlers the transport handlers to use (replaces the default ones); - * can be {@code null} if you don't want to install the default ones. - * @param transportHandlerOverrides zero or more overrides to the default transport - * handler types. + * Create a DefaultSockJsService with overridden {@link TransportHandler handler} types + * replacing the corresponding default handler implementation. + * @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions; + * the provided TaskScheduler should be declared as a Spring bean to ensure it gets + * initialized at start-up and shuts down when the application stops + * @param handlerOverrides zero or more overrides to the default transport handler types */ - public DefaultSockJsService(TaskScheduler taskScheduler, Collection transportHandlers, - TransportHandler... transportHandlerOverrides) { - - super(taskScheduler); - - initMessageCodec(); - - if (CollectionUtils.isEmpty(transportHandlers)) { - addTransportHandlers(getDefaultTransportHandlers()); - } - else { - addTransportHandlers(transportHandlers); - } - - if (!ObjectUtils.isEmpty(transportHandlerOverrides)) { - addTransportHandlers(Arrays.asList(transportHandlerOverrides)); - } - - if (this.transportHandlers.isEmpty()) { - logger.warn("No transport handlers"); - } + public DefaultSockJsService(TaskScheduler scheduler, TransportHandler... handlerOverrides) { + this(scheduler, Arrays.asList(handlerOverrides)); } - private void initMessageCodec() { - if (jackson2Present) { - this.messageCodec = new Jackson2SockJsMessageCodec(); - } - else if (jacksonPresent) { - this.messageCodec = new JacksonSockJsMessageCodec(); - } + /** + * Create a DefaultSockJsService with overridden {@link TransportHandler handler} types + * replacing the corresponding default handler implementation. + * @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions; + * the provided TaskScheduler should be declared as a Spring bean to ensure it gets + * initialized at start-up and shuts down when the application stops + * @param handlerOverrides zero or more overrides to the default transport handler types + */ + public DefaultSockJsService(TaskScheduler scheduler, Collection handlerOverrides) { + super(scheduler, getDefaultTransportHandlers(handlerOverrides)); } - protected final Set getDefaultTransportHandlers() { - Set result = new HashSet(); + + private static Set getDefaultTransportHandlers(Collection overrides) { + Set result = new LinkedHashSet(8); result.add(new XhrPollingTransportHandler()); result.add(new XhrReceivingTransportHandler()); + result.add(new XhrStreamingTransportHandler()); result.add(new JsonpPollingTransportHandler()); result.add(new JsonpReceivingTransportHandler()); - result.add(new XhrStreamingTransportHandler()); result.add(new EventSourceTransportHandler()); result.add(new HtmlFileTransportHandler()); try { result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler())); } catch (Exception ex) { + Log logger = LogFactory.getLog(DefaultSockJsService.class); if (logger.isWarnEnabled()) { logger.warn("Failed to create default WebSocketTransportHandler", ex); } } + if (overrides != null) { + result.addAll(overrides); + } return result; } - protected void addTransportHandlers(Collection handlers) { - for (TransportHandler handler : handlers) { - if (handler instanceof TransportHandlerSupport) { - ((TransportHandlerSupport) handler).setSockJsServiceConfiguration(this.sockJsServiceConfig); - } - this.transportHandlers.put(handler.getTransportType(), handler); - } - } - - - /** - * Configure one or more WebSocket handshake request interceptors. - */ - public void setHandshakeInterceptors(List interceptors) { - this.interceptors.clear(); - if (interceptors != null) { - this.interceptors.addAll(interceptors); - } - } - - /** - * Return the configured WebSocket handshake request interceptors. - */ - public List getHandshakeInterceptors() { - return this.interceptors; - } - - /** - * The codec to use for encoding and decoding SockJS messages. - * @exception IllegalStateException if no {@link SockJsMessageCodec} is available - */ - public void setMessageCodec(SockJsMessageCodec messageCodec) { - this.messageCodec = messageCodec; - } - - public SockJsMessageCodec getMessageCodec() { - return this.messageCodec; - } - - public Map getTransportHandlers() { - return Collections.unmodifiableMap(this.transportHandlers); - } - - @Override - protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler) throws IOException { - - if (!isWebSocketEnabled()) { - return; - } - - TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); - if ((transportHandler == null) || !(transportHandler instanceof HandshakeHandler)) { - logger.warn("No handler for raw WebSocket messages"); - response.setStatusCode(HttpStatus.NOT_FOUND); - return; - } - - HandshakeInterceptorChain chain = new HandshakeInterceptorChain(this.interceptors, wsHandler); - HandshakeFailureException failure = null; - - try { - Map attributes = new HashMap(); - if (!chain.applyBeforeHandshake(request, response, attributes)) { - return; - } - ((HandshakeHandler) transportHandler).doHandshake(request, response, wsHandler, attributes); - chain.applyAfterHandshake(request, response, null); - } - catch (HandshakeFailureException ex) { - failure = ex; - } - catch (Throwable t) { - failure = new HandshakeFailureException("Uncaught failure for request " + request.getURI(), t); - } - finally { - if (failure != null) { - chain.applyAfterHandshake(request, response, failure); - throw failure; - } - } - } - - @Override - protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler, String sessionId, String transport) throws SockJsException { - - TransportType transportType = TransportType.fromValue(transport); - if (transportType == null) { - logger.debug("Unknown transport type: " + transportType); - response.setStatusCode(HttpStatus.NOT_FOUND); - return; - } - - TransportHandler transportHandler = this.transportHandlers.get(transportType); - if (transportHandler == null) { - logger.debug("Transport handler not found"); - response.setStatusCode(HttpStatus.NOT_FOUND); - return; - } - - HttpMethod supportedMethod = transportType.getHttpMethod(); - if (!supportedMethod.equals(request.getMethod())) { - if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.supportsCors()) { - response.setStatusCode(HttpStatus.NO_CONTENT); - addCorsHeaders(request, response, HttpMethod.OPTIONS, supportedMethod); - addCacheHeaders(response); - } - else { - List supportedMethods = Arrays.asList(supportedMethod); - if (transportType.supportsCors()) { - supportedMethods.add(HttpMethod.OPTIONS); - } - sendMethodNotAllowed(response, supportedMethods); - } - return; - } - - HandshakeInterceptorChain chain = new HandshakeInterceptorChain(this.interceptors, wsHandler); - SockJsException failure = null; - - try { - WebSocketSession session = this.sessions.get(sessionId); - if (session == null) { - if (transportHandler instanceof SockJsSessionFactory) { - Map attributes = new HashMap(); - if (!chain.applyBeforeHandshake(request, response, attributes)) { - return; - } - SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; - session = createSockJsSession(sessionId, sessionFactory, wsHandler, attributes, request, response); - } - else { - response.setStatusCode(HttpStatus.NOT_FOUND); - logger.warn("Session not found"); - return; - } - } - - if (transportType.sendsNoCacheInstruction()) { - addNoCacheHeaders(response); - } - - if (transportType.supportsCors()) { - addCorsHeaders(request, response); - } - - transportHandler.handleRequest(request, response, wsHandler, session); - chain.applyAfterHandshake(request, response, null); - } - catch (SockJsException ex) { - failure = ex; - } - catch (Throwable t) { - failure = new SockJsException("Uncaught failure for request " + request.getURI(), sessionId, t); - } - finally { - if (failure != null) { - chain.applyAfterHandshake(request, response, failure); - throw failure; - } - } - } - - private WebSocketSession createSockJsSession(String sessionId, SockJsSessionFactory sessionFactory, - WebSocketHandler wsHandler, Map handshakeAttributes, - ServerHttpRequest request, ServerHttpResponse response) { - - synchronized (this.sessions) { - AbstractSockJsSession session = this.sessions.get(sessionId); - if (session != null) { - return session; - } - if (this.sessionCleanupTask == null) { - scheduleSessionTask(); - } - - logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = sessionFactory.createSession(sessionId, wsHandler, handshakeAttributes); - this.sessions.put(sessionId, session); - return session; - } - } - - @Override - protected boolean isValidTransportType(String lastSegment) { - return TransportType.fromValue(lastSegment) != null; - } - - private void scheduleSessionTask() { - this.sessionCleanupTask = getTaskScheduler().scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - int count = sessions.size(); - if (logger.isTraceEnabled() && (count != 0)) { - logger.trace("Checking " + count + " session(s) for timeouts [" + getName() + "]"); - } - for (AbstractSockJsSession session : sessions.values()) { - if (session.getTimeSinceLastActive() > getDisconnectDelay()) { - if (logger.isTraceEnabled()) { - logger.trace("Removing " + session + " for [" + getName() + "]"); - } - session.close(); - sessions.remove(session.getId()); - } - } - if (logger.isTraceEnabled() && (count != 0)) { - logger.trace(sessions.size() + " remaining session(s) [" + getName() + "]"); - } - } - catch (Throwable t) { - logger.error("Failed to complete session timeout checks for [" + getName() + "]", t); - } - } - }, getDisconnectDelay()); - } - - - private final SockJsServiceConfig sockJsServiceConfig = new SockJsServiceConfig() { - - @Override - public int getStreamBytesLimit() { - return DefaultSockJsService.this.getStreamBytesLimit(); - } - - @Override - public long getHeartbeatTime() { - return DefaultSockJsService.this.getHeartbeatTime(); - } - - @Override - public TaskScheduler getTaskScheduler() { - return DefaultSockJsService.this.getTaskScheduler(); - } - - @Override - public SockJsMessageCodec getMessageCodec() { - Assert.state(DefaultSockJsService.this.getMessageCodec() != null, - "A SockJsMessageCodec is required but not available." - + " Either add Jackson 2 or Jackson 1.x to the classpath, or configure a SockJsMessageCode"); - return DefaultSockJsService.this.getMessageCodec(); - } - - @Override - public int getHttpMessageCacheSize() { - return DefaultSockJsService.this.getHttpMessageCacheSize(); - } - }; - } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/EventSourceTransportHandler.java index f13b3f8ba4..46c2a43026 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/EventSourceTransportHandler.java @@ -17,16 +17,15 @@ package org.springframework.web.socket.sockjs.transport.handler; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Map; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.TransportType; -import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession; /** @@ -38,7 +37,6 @@ import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSe */ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.EVENT_SOURCE; @@ -46,19 +44,19 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan @Override protected MediaType getContentType() { - return new MediaType("text", "event-stream", Charset.forName("UTF-8")); + return new MediaType("text", "event-stream", UTF8_CHARSET); } @Override - public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, + public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes) { - return new EventSourceStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); + return new EventSourceStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes); } @Override - protected FrameFormat getFrameFormat(ServerHttpRequest request) { - return new DefaultFrameFormat("data: %s\r\n\r\n"); + protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultSockJsFrameFormat("data: %s\r\n\r\n"); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/HtmlFileTransportHandler.java index 3b6fa97130..d9d3e9dbe3 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/HtmlFileTransportHandler.java @@ -17,7 +17,6 @@ package org.springframework.web.socket.sockjs.transport.handler; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Map; import org.springframework.http.HttpStatus; @@ -28,12 +27,12 @@ import org.springframework.util.StringUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; -import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession; import org.springframework.web.util.JavaScriptUtils; @@ -84,14 +83,14 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle @Override protected MediaType getContentType() { - return new MediaType("text", "html", Charset.forName("UTF-8")); + return new MediaType("text", "html", UTF8_CHARSET); } @Override - public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, + public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes) { - return new HtmlFileStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); + return new HtmlFileStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes); } @Override @@ -115,8 +114,8 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle } @Override - protected FrameFormat getFrameFormat(ServerHttpRequest request) { - return new DefaultFrameFormat("\r\n") { + protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultSockJsFrameFormat("\r\n") { @Override protected String preProcessContent(String content) { return JavaScriptUtils.javaScriptEscape(content); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpPollingTransportHandler.java index 521ff9c3df..ee445db594 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpPollingTransportHandler.java @@ -16,7 +16,6 @@ package org.springframework.web.socket.sockjs.transport.handler; -import java.nio.charset.Charset; import java.util.Map; import org.springframework.http.HttpStatus; @@ -28,8 +27,8 @@ import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession; @@ -50,14 +49,14 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa @Override protected MediaType getContentType() { - return new MediaType("application", "javascript", Charset.forName("UTF-8")); + return new MediaType("application", "javascript", UTF8_CHARSET); } @Override - public PollingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, + public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes) { - return new PollingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); + return new PollingSockJsSession(sessionId, getServiceConfig(), handler, attributes); } @Override @@ -72,21 +71,20 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa return; } } - catch (Throwable t) { - sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); - throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), t); + catch (Throwable ex) { + sockJsSession.tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR); + throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), ex); } super.handleRequestInternal(request, response, sockJsSession); } @Override - protected FrameFormat getFrameFormat(ServerHttpRequest request) { - - // we already validated the parameter above.. + protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) { + // we already validated the parameter above... String callback = getCallbackParam(request); - return new SockJsFrame.DefaultFrameFormat(callback + "(\"%s\");\r\n") { + return new DefaultSockJsFrameFormat(callback + "(\"%s\");\r\n") { @Override protected String preProcessContent(String content) { return JavaScriptUtils.javaScriptEscape(content); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpReceivingTransportHandler.java index d124830e81..e7d1afb35c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/JsonpReceivingTransportHandler.java @@ -27,7 +27,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsException; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; @@ -62,9 +62,7 @@ public class JsonpReceivingTransportHandler extends AbstractHttpReceivingTranspo @Override protected String[] readMessages(ServerHttpRequest request) throws IOException { - - SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec(); - + SockJsMessageCodec messageCodec = getServiceConfig().getMessageCodec(); MediaType contentType = request.getHeaders().getContentType(); if ((contentType != null) && MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) { MultiValueMap map = this.formConverter.read(null, request); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsWebSocketHandler.java index 42c9c01d32..c30c0b4139 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/SockJsWebSocketHandler.java @@ -24,7 +24,7 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; -import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java index 3ae0e6e859..a37c9a3874 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java @@ -24,11 +24,12 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.server.HandshakeFailureException; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; +import org.springframework.web.socket.sockjs.transport.SockJsSession; +import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory; import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession; @@ -44,8 +45,8 @@ import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSo * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketTransportHandler extends TransportHandlerSupport - implements TransportHandler, SockJsSessionFactory, HandshakeHandler { +public class WebSocketTransportHandler extends AbstractTransportHandler + implements SockJsSessionFactory, HandshakeHandler { private final HandshakeHandler handshakeHandler; @@ -66,24 +67,24 @@ public class WebSocketTransportHandler extends TransportHandlerSupport } @Override - public AbstractSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, + public AbstractSockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes) { - return new WebSocketServerSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); + return new WebSocketServerSockJsSession(sessionId, getServiceConfig(), handler, attributes); } @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException { + WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException { WebSocketServerSockJsSession sockJsSession = (WebSocketServerSockJsSession) wsSession; try { - wsHandler = new SockJsWebSocketHandler(getSockJsServiceConfig(), wsHandler, sockJsSession); + wsHandler = new SockJsWebSocketHandler(getServiceConfig(), wsHandler, sockJsSession); this.handshakeHandler.doHandshake(request, response, wsHandler, Collections.emptyMap()); } - catch (Throwable t) { - sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); - throw new SockJsTransportFailureException("WebSocket handshake failure", wsSession.getId(), t); + catch (Throwable ex) { + sockJsSession.tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR); + throw new SockJsTransportFailureException("WebSocket handshake failure", wsSession.getId(), ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrPollingTransportHandler.java index 832525dfa2..b813bbaf74 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrPollingTransportHandler.java @@ -16,14 +16,13 @@ package org.springframework.web.socket.sockjs.transport.handler; -import java.nio.charset.Charset; import java.util.Map; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession; @@ -43,19 +42,19 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand @Override protected MediaType getContentType() { - return new MediaType("application", "javascript", Charset.forName("UTF-8")); + return new MediaType("application", "javascript", UTF8_CHARSET); } @Override - protected FrameFormat getFrameFormat(ServerHttpRequest request) { - return new DefaultFrameFormat("%s\n"); + protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultSockJsFrameFormat("%s\n"); } @Override - public PollingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, + public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes) { - return new PollingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); + return new PollingSockJsSession(sessionId, getServiceConfig(), handler, attributes); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrReceivingTransportHandler.java index d3e1300a34..dc40b28b2a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrReceivingTransportHandler.java @@ -27,10 +27,10 @@ import org.springframework.web.socket.sockjs.transport.TransportType; * A {@link TransportHandler} that receives messages over HTTP. * * @author Rossen Stoyanchev + * @since 4.0 */ public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.XHR_SEND; @@ -38,7 +38,7 @@ public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransport @Override protected String[] readMessages(ServerHttpRequest request) throws IOException { - return getSockJsServiceConfig().getMessageCodec().decodeInputStream(request.getBody()); + return getServiceConfig().getMessageCodec().decodeInputStream(request.getBody()); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrStreamingTransportHandler.java index 03da01986f..65f4f657c4 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/XhrStreamingTransportHandler.java @@ -17,17 +17,16 @@ package org.springframework.web.socket.sockjs.transport.handler; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Map; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportType; -import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession; /** @@ -38,7 +37,6 @@ import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSe */ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.XHR_STREAMING; @@ -46,19 +44,19 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa @Override protected MediaType getContentType() { - return new MediaType("application", "javascript", Charset.forName("UTF-8")); + return new MediaType("application", "javascript", UTF8_CHARSET); } @Override - public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, + public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler, Map attributes) { - return new XhrStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); + return new XhrStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes); } @Override - protected FrameFormat getFrameFormat(ServerHttpRequest request) { - return new DefaultFrameFormat("%s\n"); + protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultSockJsFrameFormat("%s\n"); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java index c93074dd84..3d3315dba3 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSession.java @@ -32,12 +32,13 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketExtension; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; -import org.springframework.web.socket.WebSocketExtension; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; /** * An abstract base class for use with HTTP transport based SockJS sessions. @@ -47,8 +48,6 @@ import org.springframework.web.socket.WebSocketExtension; */ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { - private FrameFormat frameFormat; - private final BlockingQueue messageCache; private ServerHttpRequest request; @@ -57,6 +56,8 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { private ServerHttpAsyncRequestControl asyncRequestControl; + private SockJsFrameFormat frameFormat; + private URI uri; private HttpHeaders handshakeHeaders; @@ -88,42 +89,21 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { return this.handshakeHeaders; } - protected void setHandshakeHeaders(HttpHeaders handshakeHeaders) { - this.handshakeHeaders = handshakeHeaders; - } - @Override public Principal getPrincipal() { return this.principal; } - protected void setPrincipal(Principal principal) { - this.principal = principal; - } - @Override public InetSocketAddress getLocalAddress() { return this.localAddress; } - protected void setLocalAddress(InetSocketAddress localAddress) { - this.localAddress = localAddress; - } - @Override public InetSocketAddress getRemoteAddress() { return this.remoteAddress; } - protected void setRemoteAddress(InetSocketAddress remoteAddress) { - this.remoteAddress = remoteAddress; - } - - @Override - public List getExtensions() { - return Collections.emptyList(); - } - /** * Unlike WebSocket where sub-protocol negotiation is part of the * initial handshake, in HTTP transports the same negotiation must @@ -141,17 +121,23 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { return this.acceptedProtocol; } - public synchronized void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response, - FrameFormat frameFormat) throws SockJsException { + @Override + public List getExtensions() { + return Collections.emptyList(); + } - udpateRequest(request, response, frameFormat); + + public synchronized void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsFrameFormat frameFormat) throws SockJsException { + + updateRequest(request, response, frameFormat); try { writePrelude(); writeFrame(SockJsFrame.openFrame()); } - catch (Throwable t) { - tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); - throw new SockJsTransportFailureException("Failed to send \"open\" frame", getId(), t); + catch (Throwable ex) { + tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR); + throw new SockJsTransportFailureException("Failed to send \"open\" frame", getId(), ex); } this.uri = request.getURI(); @@ -163,8 +149,8 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { try { delegateConnectionEstablished(); } - catch (Throwable t) { - throw new SockJsException("Unhandled exception from WebSocketHandler", getId(), t); + catch (Throwable ex) { + throw new SockJsException("Unhandled exception from WebSocketHandler", getId(), ex); } } @@ -172,25 +158,24 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { } public synchronized void startLongPollingRequest(ServerHttpRequest request, - ServerHttpResponse response, FrameFormat frameFormat) throws SockJsException { + ServerHttpResponse response, SockJsFrameFormat frameFormat) throws SockJsException { - udpateRequest(request, response, frameFormat); + updateRequest(request, response, frameFormat); try { this.asyncRequestControl.start(-1); scheduleHeartbeat(); tryFlushCache(); } - catch (Throwable t) { - tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); - throw new SockJsTransportFailureException("Failed to flush messages", getId(), t); + catch (Throwable ex) { + tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR); + throw new SockJsTransportFailureException("Failed to flush messages", getId(), ex); } } - private void udpateRequest(ServerHttpRequest request, ServerHttpResponse response, - FrameFormat frameFormat) { - Assert.notNull(request, "expected request"); - Assert.notNull(response, "expected response"); - Assert.notNull(frameFormat, "expected frameFormat"); + private void updateRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsFrameFormat frameFormat) { + Assert.notNull(request, "Request must not be null"); + Assert.notNull(response, "Response must not be null"); + Assert.notNull(frameFormat, "SockJsFrameFormat must not be null"); this.request = request; this.response = response; this.asyncRequestControl = request.getAsyncRequestControl(response); @@ -203,7 +188,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession { @Override public synchronized boolean isActive() { - return ((this.asyncRequestControl != null) && (!this.asyncRequestControl.isCompleted())); + return (this.asyncRequestControl != null && !this.asyncRequestControl.isCompleted()); } protected BlockingQueue getMessageCache() { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java index 4bda310c71..5d0c943c4f 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java @@ -28,23 +28,25 @@ import java.util.concurrent.ScheduledFuture; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.util.Assert; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; -import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; +import org.springframework.web.socket.sockjs.transport.SockJsSession; /** - * An abstract base class SockJS sessions implementing {@link WebSocketSession}. + * An abstract base class SockJS sessions implementing {@link SockJsSession}. * * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractSockJsSession implements WebSocketSession { +public abstract class AbstractSockJsSession implements SockJsSession { protected final Log logger = LogFactory.getLog(getClass()); @@ -68,21 +70,22 @@ public abstract class AbstractSockJsSession implements WebSocketSession { /** * @param id the session ID * @param config SockJS service configuration options - * @param wsHandler the recipient of SockJS messages + * @param handler the recipient of SockJS messages */ public AbstractSockJsSession(String id, SockJsServiceConfig config, - WebSocketHandler wsHandler, Map handshakeAttributes) { + WebSocketHandler handler, Map handshakeAttributes) { Assert.notNull(id, "SessionId must not be null"); Assert.notNull(config, "SockJsConfig must not be null"); - Assert.notNull(wsHandler, "WebSocketHandler must not be null"); + Assert.notNull(handler, "WebSocketHandler must not be null"); this.id = id; this.config = config; - this.handler = wsHandler; + this.handler = handler; this.handshakeAttributes = handshakeAttributes; } + @Override public String getId() { return this.id; @@ -119,10 +122,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { */ public abstract boolean isActive(); - /** - * Return the time since the session was last active, or otherwise if the - * session is new, the time since the session was created. - */ + @Override public long getTimeSinceLastActive() { if (isNew()) { return (System.currentTimeMillis() - this.timeCreated); @@ -156,8 +156,8 @@ public abstract class AbstractSockJsSession implements WebSocketSession { undelivered.remove(0); } } - catch (Throwable t) { - throw new SockJsMessageDeliveryException(this.id, undelivered, t); + catch (Throwable ex) { + throw new SockJsMessageDeliveryException(this.id, undelivered, ex); } } } @@ -235,8 +235,8 @@ public abstract class AbstractSockJsSession implements WebSocketSession { try { this.handler.afterConnectionClosed(this, status); } - catch (Throwable t) { - logger.error("Unhandled error for " + this, t); + catch (Throwable ex) { + logger.error("Unhandled error for " + this, ex); } } } @@ -302,7 +302,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } protected void scheduleHeartbeat() { - Assert.state(this.config.getTaskScheduler() != null, "heartbeatScheduler not configured"); + Assert.state(this.config.getTaskScheduler() != null, "No TaskScheduler configured for heartbeat"); cancelHeartbeat(); if (!isActive()) { return; @@ -313,7 +313,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { try { sendHeartbeat(); } - catch (Throwable t) { + catch (Throwable ex) { // ignore } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java index 1f1e4668f5..32e9a67abf 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/PollingSockJsSession.java @@ -17,20 +17,22 @@ package org.springframework.web.socket.sockjs.transport.session; import java.util.Map; +import java.util.concurrent.BlockingQueue; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; /** * A SockJS session for use with polling HTTP transports. * * @author Rossen Stoyanchev + * @since 4.0 */ public class PollingSockJsSession extends AbstractHttpSockJsSession { - public PollingSockJsSession(String sessionId, SockJsServiceConfig config, WebSocketHandler wsHandler, Map attributes) { @@ -40,10 +42,10 @@ public class PollingSockJsSession extends AbstractHttpSockJsSession { @Override protected void flushCache() throws SockJsTransportFailureException { - cancelHeartbeat(); - String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]); - getMessageCache().clear(); + BlockingQueue messageCache = getMessageCache(); + String[] messages = messageCache.toArray(new String[messageCache.size()]); + messageCache.clear(); SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec(); SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, messages); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/StreamingSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/StreamingSockJsSession.java index ee824256cc..381142ce6c 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/StreamingSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/StreamingSockJsSession.java @@ -24,14 +24,16 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; /** * A SockJS session for use with streaming HTTP transports. * * @author Rossen Stoyanchev + * @since 4.0 */ public class StreamingSockJsSession extends AbstractHttpSockJsSession { @@ -47,7 +49,7 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession { @Override public synchronized void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response, - FrameFormat frameFormat) throws SockJsException { + SockJsFrameFormat frameFormat) throws SockJsException { super.handleInitialRequest(request, response, frameFormat); @@ -59,7 +61,6 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession { @Override protected void flushCache() throws SockJsTransportFailureException { - cancelHeartbeat(); do { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSession.java index e66af40e7e..6a7b5610ed 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSession.java @@ -33,8 +33,9 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; /** * A SockJS session for use with the WebSocket transport. @@ -42,99 +43,97 @@ import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketServerSockJsSession extends AbstractSockJsSession - implements WebSocketSession, NativeWebSocketSession { +public class WebSocketServerSockJsSession extends AbstractSockJsSession implements NativeWebSocketSession { - private WebSocketSession wsSession; + private WebSocketSession webSocketSession; public WebSocketServerSockJsSession(String id, SockJsServiceConfig config, - WebSocketHandler wsHandler, Map attributes) { + WebSocketHandler handler, Map attributes) { - super(id, config, wsHandler, attributes); + super(id, config, handler, attributes); } @Override public URI getUri() { checkDelegateSessionInitialized(); - return this.wsSession.getUri(); + return this.webSocketSession.getUri(); } @Override public HttpHeaders getHandshakeHeaders() { checkDelegateSessionInitialized(); - return this.wsSession.getHandshakeHeaders(); + return this.webSocketSession.getHandshakeHeaders(); } @Override public Principal getPrincipal() { checkDelegateSessionInitialized(); - return this.wsSession.getPrincipal(); + return this.webSocketSession.getPrincipal(); } @Override public InetSocketAddress getLocalAddress() { checkDelegateSessionInitialized(); - return this.wsSession.getLocalAddress(); + return this.webSocketSession.getLocalAddress(); } @Override public InetSocketAddress getRemoteAddress() { checkDelegateSessionInitialized(); - return this.wsSession.getRemoteAddress(); + return this.webSocketSession.getRemoteAddress(); } @Override public String getAcceptedProtocol() { checkDelegateSessionInitialized(); - return this.wsSession.getAcceptedProtocol(); + return this.webSocketSession.getAcceptedProtocol(); } @Override public List getExtensions() { checkDelegateSessionInitialized(); - return this.wsSession.getExtensions(); + return this.webSocketSession.getExtensions(); } private void checkDelegateSessionInitialized() { - Assert.state(this.wsSession != null, "WebSocketSession not yet initialized"); + Assert.state(this.webSocketSession != null, "WebSocketSession not yet initialized"); } @Override public Object getNativeSession() { - if ((this.wsSession != null) && (this.wsSession instanceof NativeWebSocketSession)) { - return ((NativeWebSocketSession) this.wsSession).getNativeSession(); + if ((this.webSocketSession != null) && (this.webSocketSession instanceof NativeWebSocketSession)) { + return ((NativeWebSocketSession) this.webSocketSession).getNativeSession(); } return null; } @Override public T getNativeSession(Class requiredType) { - if ((this.wsSession != null) && (this.wsSession instanceof NativeWebSocketSession)) { - return ((NativeWebSocketSession) this.wsSession).getNativeSession(requiredType); + if ((this.webSocketSession != null) && (this.webSocketSession instanceof NativeWebSocketSession)) { + return ((NativeWebSocketSession) this.webSocketSession).getNativeSession(requiredType); } return null; } public void initializeDelegateSession(WebSocketSession session) { - this.wsSession = session; + this.webSocketSession = session; try { TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); - this.wsSession.sendMessage(message); + this.webSocketSession.sendMessage(message); scheduleHeartbeat(); delegateConnectionEstablished(); } catch (Exception ex) { tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR); - return; } } @Override public boolean isActive() { - return ((this.wsSession != null) && this.wsSession.isOpen()); + return ((this.webSocketSession != null) && this.webSocketSession.isOpen()); } public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception { @@ -170,13 +169,13 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession logger.trace("Write " + frame); } TextMessage message = new TextMessage(frame.getContent()); - this.wsSession.sendMessage(message); + this.webSocketSession.sendMessage(message); } @Override protected void disconnect(CloseStatus status) throws IOException { - if (this.wsSession != null) { - this.wsSession.close(status); + if (this.webSocketSession != null) { + this.webSocketSession.close(status); } } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java index a311146387..394e0e4ce1 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/HandlersBeanDefinitionParserTests.java @@ -46,8 +46,9 @@ 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.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; import org.springframework.web.socket.sockjs.SockJsService; +import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService; 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; @@ -150,7 +151,7 @@ public class HandlersBeanDefinitionParserTests { } @Test - public void sockJSSupport() { + public void sockJsSupport() { loadBeanDefinitions("websocket-config-handlers-sockjs.xml"); SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class); assertNotNull(handlerMapping); @@ -182,7 +183,7 @@ public class HandlersBeanDefinitionParserTests { } @Test - public void sockJSAttributesSupport() { + public void sockJsAttributesSupport() { loadBeanDefinitions("websocket-config-handlers-sockjs-attributes.xml"); SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class); assertNotNull(handlerMapping); @@ -191,8 +192,8 @@ public class HandlersBeanDefinitionParserTests { checkDelegateHandlerType(handler.getWebSocketHandler(), TestWebSocketHandler.class); SockJsService sockJsService = handler.getSockJsService(); assertNotNull(sockJsService); - assertThat(sockJsService, Matchers.instanceOf(DefaultSockJsService.class)); - DefaultSockJsService defaultSockJsService = (DefaultSockJsService) sockJsService; + assertThat(sockJsService, Matchers.instanceOf(TransportHandlingSockJsService.class)); + TransportHandlingSockJsService defaultSockJsService = (TransportHandlingSockJsService) sockJsService; assertThat(defaultSockJsService.getTaskScheduler(), Matchers.instanceOf(TestTaskScheduler.class)); assertThat(defaultSockJsService.getTransportHandlers().values(), Matchers.containsInAnyOrder( Matchers.instanceOf(XhrPollingTransportHandler.class), diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java index f89cc94da3..b5fc7f4fbb 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/MessageBrokerBeanDefinitionParserTests.java @@ -48,7 +48,7 @@ import org.springframework.web.socket.handler.WebSocketHandlerDecorator; import org.springframework.web.socket.messaging.StompSubProtocolHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler; import static org.junit.Assert.*; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistrationTests.java index 47c07c0f52..6128d10de1 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistrationTests.java @@ -31,7 +31,7 @@ import org.springframework.web.HttpRequestHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.support.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; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/AbstractSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/AbstractSockJsServiceTests.java index adc998cf8e..5dfe104bf6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/AbstractSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/AbstractSockJsServiceTests.java @@ -54,10 +54,10 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests { @Test public void validateRequest() throws Exception { - this.service.setWebSocketsEnabled(false); + this.service.setWebSocketEnabled(false); handleRequest("GET", "/echo/server/session/websocket", HttpStatus.NOT_FOUND); - this.service.setWebSocketsEnabled(true); + this.service.setWebSocketEnabled(true); handleRequest("GET", "/echo/server/session/websocket", HttpStatus.OK); handleRequest("GET", "/echo//", HttpStatus.NOT_FOUND); @@ -86,7 +86,7 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests { body.substring(body.indexOf(','))); this.service.setSessionCookieNeeded(false); - this.service.setWebSocketsEnabled(false); + this.service.setWebSocketEnabled(false); handleRequest("GET", "/echo/info", HttpStatus.OK); body = this.servletResponse.getContentAsString(); @@ -187,11 +187,6 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests { this.transport = transport; this.handler = handler; } - - @Override - protected boolean isValidTransportType(String transportType) { - return TransportType.fromValue(transportType) != null; - } } } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java index 6842b98d31..8cc3a39ba6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/DefaultSockJsServiceTests.java @@ -16,29 +16,31 @@ package org.springframework.web.socket.sockjs.transport.handler; -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.Mock; import org.mockito.MockitoAnnotations; + import org.springframework.scheduling.TaskScheduler; import org.springframework.web.socket.AbstractHttpRequestTests; import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory; 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.session.StubSockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.TestSockJsSession; import static org.junit.Assert.*; -import static org.mockito.Matchers.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; /** - * Test fixture for {@link DefaultSockJsService}. + * Test fixture for {@link org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService}. * * @author Rossen Stoyanchev */ @@ -61,14 +63,12 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { private TestSockJsSession session; - private DefaultSockJsService service; + private TransportHandlingSockJsService service; @Before public void setup() { - super.setUp(); - MockitoAnnotations.initMocks(this); Map attributes = Collections.emptyMap(); @@ -78,13 +78,11 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { when(this.xhrHandler.createSession(sessionId, this.wsHandler, attributes)).thenReturn(this.session); when(this.xhrSendHandler.getTransportType()).thenReturn(TransportType.XHR_SEND); - this.service = new DefaultSockJsService(this.taskScheduler, - Arrays.asList(this.xhrHandler, this.xhrSendHandler)); + this.service = new TransportHandlingSockJsService(this.taskScheduler, this.xhrHandler, this.xhrSendHandler); } @Test public void defaultTransportHandlers() { - DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class)); Map handlers = service.getTransportHandlers(); @@ -101,10 +99,9 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { @Test public void defaultTransportHandlersWithOverride() { - XhrReceivingTransportHandler xhrHandler = new XhrReceivingTransportHandler(); - DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), null, xhrHandler); + DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), xhrHandler); Map handlers = service.getTransportHandlers(); assertEquals(8, handlers.size()); @@ -113,19 +110,15 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { @Test public void customizedTransportHandlerList() { - - List handlers = Arrays.asList( - new XhrPollingTransportHandler(), new XhrReceivingTransportHandler()); - - DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), handlers); + TransportHandlingSockJsService service = new TransportHandlingSockJsService( + mock(TaskScheduler.class), new XhrPollingTransportHandler(), new XhrReceivingTransportHandler()); Map actualHandlers = service.getTransportHandlers(); - assertEquals(handlers.size(), actualHandlers.size()); + assertEquals(2, actualHandlers.size()); } @Test public void handleTransportRequestXhr() throws Exception { - String sockJsPath = sessionUrlPrefix + "xhr"; setRequest("POST", sockJsPrefix + sockJsPath); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); @@ -141,7 +134,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { @Test public void handleTransportRequestXhrOptions() throws Exception { - String sockJsPath = sessionUrlPrefix + "xhr"; setRequest("OPTIONS", sockJsPrefix + sockJsPath); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); @@ -154,7 +146,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { @Test public void handleTransportRequestNoSuitableHandler() throws Exception { - String sockJsPath = sessionUrlPrefix + "eventsource"; setRequest("POST", sockJsPrefix + sockJsPath); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); @@ -164,7 +155,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { @Test public void handleTransportRequestXhrSend() throws Exception { - String sockJsPath = sessionUrlPrefix + "xhr_send"; setRequest("POST", sockJsPrefix + sockJsPath); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpReceivingTransportHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpReceivingTransportHandlerTests.java index ef8396abd1..846a2bbd6c 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpReceivingTransportHandlerTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpReceivingTransportHandlerTests.java @@ -114,7 +114,7 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest try { XhrReceivingTransportHandler transportHandler = new XhrReceivingTransportHandler(); - transportHandler.setSockJsServiceConfiguration(sockJsConfig); + transportHandler.initialize(sockJsConfig); transportHandler.handleRequest(this.request, this.response, wsHandler, session); fail("Expected exception"); } @@ -129,7 +129,7 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest WebSocketHandler wsHandler = mock(WebSocketHandler.class); AbstractSockJsSession session = new TestHttpSockJsSession("1", new StubSockJsServiceConfig(), wsHandler, null); - transportHandler.setSockJsServiceConfiguration(new StubSockJsServiceConfig()); + transportHandler.initialize(new StubSockJsServiceConfig()); transportHandler.handleRequest(this.request, this.response, wsHandler, session); assertEquals("text/plain;charset=UTF-8", this.response.getHeaders().getContentType().toString()); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java index 7e7c9f1b18..9504ae1268 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/handler/HttpSendingTransportHandlerTests.java @@ -20,18 +20,19 @@ import java.sql.Date; import org.junit.Before; import org.junit.Test; + import org.springframework.scheduling.TaskScheduler; import org.springframework.web.socket.AbstractHttpRequestTests; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession; import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession; import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession; import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig; import static org.junit.Assert.*; -import static org.mockito.Matchers.*; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; /** @@ -64,7 +65,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void handleRequestXhr() throws Exception { XhrPollingTransportHandler transportHandler = new XhrPollingTransportHandler(); - transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); + transportHandler.initialize(this.sockJsConfig); AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); @@ -91,7 +92,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void jsonpTransport() throws Exception { JsonpPollingTransportHandler transportHandler = new JsonpPollingTransportHandler(); - transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); + transportHandler.initialize(this.sockJsConfig); PollingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); @@ -113,7 +114,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void handleRequestXhrStreaming() throws Exception { XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler(); - transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); + transportHandler.initialize(this.sockJsConfig); AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); @@ -127,7 +128,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void htmlFileTransport() throws Exception { HtmlFileTransportHandler transportHandler = new HtmlFileTransportHandler(); - transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); + transportHandler.initialize(this.sockJsConfig); StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); @@ -149,7 +150,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests public void eventSourceTransport() throws Exception { EventSourceTransportHandler transportHandler = new EventSourceTransportHandler(); - transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); + transportHandler.initialize(this.sockJsConfig); StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); @@ -167,7 +168,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests SockJsFrame frame = SockJsFrame.openFrame(); - FrameFormat format = new XhrPollingTransportHandler().getFrameFormat(this.request); + SockJsFrameFormat format = new XhrPollingTransportHandler().getFrameFormat(this.request); SockJsFrame formatted = format.format(frame); assertEquals(frame.getContent() + "\n", formatted.getContent()); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSessionTests.java index d270d7e28b..8c8f803349 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractHttpSockJsSessionTests.java @@ -21,6 +21,7 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; @@ -29,9 +30,10 @@ import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; +import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSessionTests.TestAbstractHttpSockJsSession; import static org.junit.Assert.*; @@ -52,7 +54,7 @@ public class AbstractHttpSockJsSessionTests extends BaseAbstractSockJsSessionTes protected MockHttpServletResponse servletResponse; - private FrameFormat frameFormat; + private SockJsFrameFormat frameFormat; @Before @@ -60,7 +62,7 @@ public class AbstractHttpSockJsSessionTests extends BaseAbstractSockJsSessionTes super.setUp(); - this.frameFormat = new DefaultFrameFormat("%s"); + this.frameFormat = new DefaultSockJsFrameFormat("%s"); this.servletResponse = new MockHttpServletResponse(); this.response = new ServletServerHttpResponse(this.servletResponse); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSessionTests.java index 962d09ab25..acffb11365 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSessionTests.java @@ -28,7 +28,7 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator; import static org.junit.Assert.*; diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/StubSockJsServiceConfig.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/StubSockJsServiceConfig.java index 5c6915d6f9..a96f527856 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/StubSockJsServiceConfig.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/StubSockJsServiceConfig.java @@ -18,8 +18,9 @@ package org.springframework.web.socket.sockjs.transport.session; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.web.socket.sockjs.support.frame.Jackson2SockJsMessageCodec; -import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; +import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec; +import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestHttpSockJsSession.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestHttpSockJsSession.java index 6269883e2b..a5ea27c3c6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestHttpSockJsSession.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestHttpSockJsSession.java @@ -23,8 +23,9 @@ import java.util.Map; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestSockJsSession.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestSockJsSession.java index 7f60c86474..b27cf7d3e8 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestSockJsSession.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/TestSockJsSession.java @@ -28,7 +28,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketExtension; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; +import org.springframework.web.socket.sockjs.frame.SockJsFrame; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSessionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSessionTests.java index 5ae1cbb69b..85620c8586 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSessionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/session/WebSocketServerSockJsSessionTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSessionTests.TestWebSocketServerSockJsSession; import org.springframework.web.socket.handler.TestWebSocketSession;