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

This commit is contained in:
Juergen Hoeller 2013-12-08 01:25:52 +01:00
parent fcecd0328a
commit 1f9b833c4d
56 changed files with 905 additions and 894 deletions

View File

@ -26,15 +26,14 @@ import java.util.Map;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
/** /**
* A WebSocket session abstraction. Allows sending messages over a WebSocket connection * A WebSocket session abstraction. Allows sending messages over a WebSocket
* and closing it. * connection and closing it.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public interface WebSocketSession { public interface WebSocketSession {
/** /**
* Return a unique session identifier. * Return a unique session identifier.
*/ */

View File

@ -33,9 +33,8 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils; import org.springframework.util.xml.DomUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; 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.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 * A {@link BeanDefinitionParser} that provides the configuration for the
@ -129,7 +128,6 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
@Override @Override
public ManagedMap<String, Object> createMappings(Element mappingElement, ParserContext parserContext) { public ManagedMap<String, Object> createMappings(Element mappingElement, ParserContext parserContext) {
ManagedMap<String, Object> urlMap = new ManagedMap<String, Object>(); ManagedMap<String, Object> urlMap = new ManagedMap<String, Object>();
Object source = parserContext.extractSource(mappingElement); Object source = parserContext.extractSource(mappingElement);

View File

@ -55,7 +55,7 @@ import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.socket.messaging.StompSubProtocolHandler; import org.springframework.web.socket.messaging.StompSubProtocolHandler;
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
/** /**

View File

@ -16,12 +16,9 @@
package org.springframework.web.socket.config; package org.springframework.web.socket.config;
import java.util.Collections;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition; 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.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedList; 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.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.xml.DomUtils; import org.springframework.util.xml.DomUtils;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler; 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; import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
/** /**
@ -63,54 +61,36 @@ class WebSocketNamespaceUtils {
Element sockJsElement = DomUtils.getChildElementByTagName(element, "sockjs"); Element sockJsElement = DomUtils.getChildElementByTagName(element, "sockjs");
if (sockJsElement != null) { if (sockJsElement != null) {
ConstructorArgumentValues cavs = new ConstructorArgumentValues(); RootBeanDefinition sockJsServiceDef = new RootBeanDefinition(DefaultSockJsService.class);
sockJsServiceDef.setSource(source);
Object scheduler;
String customTaskSchedulerName = sockJsElement.getAttribute("scheduler"); String customTaskSchedulerName = sockJsElement.getAttribute("scheduler");
if (!customTaskSchedulerName.isEmpty()) { if (!customTaskSchedulerName.isEmpty()) {
cavs.addIndexedArgumentValue(0, new RuntimeBeanReference(customTaskSchedulerName)); scheduler = new RuntimeBeanReference(customTaskSchedulerName);
} }
else { 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"); Element transportHandlersElement = DomUtils.getChildElementByTagName(sockJsElement, "transport-handlers");
boolean registerDefaults = true;
if (transportHandlersElement != null) { if (transportHandlersElement != null) {
String registerDefaultsAttribute = transportHandlersElement.getAttribute("register-defaults"); 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"); String attrValue = sockJsElement.getAttribute("name");
if (!attrValue.isEmpty()) { if (!attrValue.isEmpty()) {
sockJsServiceDef.getPropertyValues().add("name", attrValue); sockJsServiceDef.getPropertyValues().add("name", attrValue);
} }
attrValue = sockJsElement.getAttribute("websocket-enabled"); attrValue = sockJsElement.getAttribute("websocket-enabled");
if (!attrValue.isEmpty()) { if (!attrValue.isEmpty()) {
sockJsServiceDef.getPropertyValues().add("webSocketsEnabled", Boolean.valueOf(attrValue)); sockJsServiceDef.getPropertyValues().add("webSocketEnabled", Boolean.valueOf(attrValue));
} }
attrValue = sockJsElement.getAttribute("session-cookie-needed"); attrValue = sockJsElement.getAttribute("session-cookie-needed");
if (!attrValue.isEmpty()) { if (!attrValue.isEmpty()) {

View File

@ -27,7 +27,7 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; 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.SockJsService;
/** /**

View File

@ -21,11 +21,13 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.sockjs.SockJsService; import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; 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 * A helper class for configuring SockJS fallback options, typically used indirectly, in
@ -190,7 +192,8 @@ public class SockJsServiceRegistration {
} }
protected SockJsService getSockJsService() { protected SockJsService getSockJsService() {
DefaultSockJsService service = createSockJsService(); TransportHandlingSockJsService service = createSockJsService();
service.setHandshakeInterceptors(this.interceptors);
if (this.clientLibraryUrl != null) { if (this.clientLibraryUrl != null) {
service.setSockJsClientLibraryUrl(this.clientLibraryUrl); service.setSockJsClientLibraryUrl(this.clientLibraryUrl);
} }
@ -204,21 +207,26 @@ public class SockJsServiceRegistration {
service.setHeartbeatTime(this.heartbeatTime); service.setHeartbeatTime(this.heartbeatTime);
} }
if (this.disconnectDelay != null) { if (this.disconnectDelay != null) {
service.setDisconnectDelay(this.heartbeatTime); service.setDisconnectDelay(this.disconnectDelay);
} }
if (this.httpMessageCacheSize != null) { if (this.httpMessageCacheSize != null) {
service.setHttpMessageCacheSize(this.httpMessageCacheSize); service.setHttpMessageCacheSize(this.httpMessageCacheSize);
} }
if (this.webSocketEnabled != null) { if (this.webSocketEnabled != null) {
service.setWebSocketsEnabled(this.webSocketEnabled); service.setWebSocketEnabled(this.webSocketEnabled);
} }
service.setHandshakeInterceptors(this.interceptors);
return service; return service;
} }
private DefaultSockJsService createSockJsService() { private TransportHandlingSockJsService createSockJsService() {
return new DefaultSockJsService(this.taskScheduler, this.transportHandlers, if (!this.transportHandlers.isEmpty()) {
this.transportHandlerOverrides.toArray(new TransportHandler[this.transportHandlerOverrides.size()])); 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);
}
} }
} }

View File

@ -24,7 +24,7 @@ import org.springframework.web.HttpRequestHandler;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; 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.SockJsService;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;

View File

@ -26,11 +26,16 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message; import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageType; 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.user.UserSessionRegistry;
import org.springframework.messaging.simp.stomp.*;
import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
@ -39,8 +44,8 @@ import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
/** /**
* A {@link SubProtocolHandler} for STOMP that supports versions 1.0, 1.1, and 1.2 of the * A {@link SubProtocolHandler} for STOMP that supports versions 1.0, 1.1, and 1.2
* STOMP specification. * of the STOMP specification.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Andy Wilkinson * @author Andy Wilkinson
@ -54,6 +59,8 @@ public class StompSubProtocolHandler implements SubProtocolHandler {
*/ */
public static final String CONNECTED_USER_HEADER = "user-name"; 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); private static final Log logger = LogFactory.getLog(StompSubProtocolHandler.class);
@ -94,22 +101,24 @@ public class StompSubProtocolHandler implements SubProtocolHandler {
try { try {
Assert.isInstanceOf(TextMessage.class, webSocketMessage); Assert.isInstanceOf(TextMessage.class, webSocketMessage);
String payload = ((TextMessage)webSocketMessage).getPayload(); 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); message = this.stompDecoder.decode(byteBuffer);
} }
catch (Throwable error) { catch (Throwable ex) {
logger.error("Failed to parse STOMP frame, WebSocket message payload: ", error); logger.error("Failed to parse STOMP frame, WebSocket message payload", ex);
sendErrorMessage(session, error); sendErrorMessage(session, ex);
return; return;
} }
try { try {
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message); StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
if (SimpMessageType.HEARTBEAT.equals(headers.getMessageType())) { if (logger.isTraceEnabled()) {
logger.trace("Received heartbeat from client session=" + session.getId()); 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()); else {
logger.trace("Received message from client session=" + session.getId());
}
} }
headers.setSessionId(session.getId()); headers.setSessionId(session.getId());
@ -118,9 +127,9 @@ public class StompSubProtocolHandler implements SubProtocolHandler {
message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build(); message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
outputChannel.send(message); outputChannel.send(message);
} }
catch (Throwable t) { catch (Throwable ex) {
logger.error("Terminating STOMP session due to failure to send message: ", t); logger.error("Terminating STOMP session due to failure to send message", ex);
sendErrorMessage(session, t); sendErrorMessage(session, ex);
} }
} }
@ -129,11 +138,11 @@ public class StompSubProtocolHandler implements SubProtocolHandler {
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.ERROR); StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.ERROR);
headers.setMessage(error.getMessage()); headers.setMessage(error.getMessage());
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build(); Message<byte[]> 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 { try {
session.sendMessage(new TextMessage(payload)); session.sendMessage(new TextMessage(payload));
} }
catch (Throwable t) { catch (Throwable ex) {
// ignore // ignore
} }
} }
@ -176,19 +185,19 @@ public class StompSubProtocolHandler implements SubProtocolHandler {
byte[] bytes = this.stompEncoder.encode((Message<byte[]>) message); byte[] bytes = this.stompEncoder.encode((Message<byte[]>) message);
synchronized(session) { 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) { catch (Throwable ex) {
sendErrorMessage(session, t); sendErrorMessage(session, ex);
} }
finally { finally {
if (StompCommand.ERROR.equals(headers.getCommand())) { if (StompCommand.ERROR.equals(headers.getCommand())) {
try { try {
session.close(CloseStatus.PROTOCOL_ERROR); session.close(CloseStatus.PROTOCOL_ERROR);
} }
catch (IOException e) { catch (IOException ex) {
// Ignore // Ignore
} }
} }

View File

@ -24,13 +24,12 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
/** /**
* Interceptor for WebSocket handshake requests. Can be used to inspect the handshake * Interceptor for WebSocket handshake requests. Can be used to inspect the
* request and response as well as to pass attributes to the target * handshake request and response as well as to pass attributes to the target
* {@link WebSocketHandler}. * {@link WebSocketHandler}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*
* @see org.springframework.web.socket.server.support.WebSocketHttpRequestHandler * @see org.springframework.web.socket.server.support.WebSocketHttpRequestHandler
* @see org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService * @see org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService
*/ */

View File

@ -24,15 +24,15 @@ import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator
/** /**
* The main entry point for processing HTTP requests from SockJS clients. * The main entry point for processing HTTP requests from SockJS clients.
* *
* <p>In a Servlet 3+ container, {@link SockJsHttpRequestHandler} can be used to invoke this * <p>In a Servlet 3+ container, {@link org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler}
* service. The processing servlet, as well as all filters involved, must have * can be used to invoke this service. The processing servlet, as well as all filters involved,
* asynchronous support enabled through the ServletContext API or by adding an * must have asynchronous support enabled through the ServletContext API or by adding an
* {@code <async-support>true</async-support>} element to servlet and filter declarations * {@code <async-support>true</async-support>} element to servlet and filter declarations
* in web.xml. * in web.xml.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
* @see SockJsHttpRequestHandler * @see org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler
*/ */
public interface SockJsService { public interface SockJsService {
@ -53,7 +53,7 @@ public interface SockJsService {
* exceptions from the WebSocketHandler can be handled internally or through * exceptions from the WebSocketHandler can be handled internally or through
* {@link ExceptionWebSocketHandlerDecorator} or some alternative decorator. * {@link ExceptionWebSocketHandlerDecorator} or some alternative decorator.
* The former is automatically added when using * 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, void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath,
WebSocketHandler handler) throws SockJsException; WebSocketHandler handler) throws SockJsException;

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.web.socket.sockjs.support.frame; package org.springframework.web.socket.sockjs.frame;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

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

View File

@ -14,18 +14,18 @@
* limitations under the License. * 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.io.JsonStringEncoder; import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.fasterxml.jackson.databind.ObjectMapper; 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 * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
@ -44,6 +44,7 @@ public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
@Override @Override
public String[] decode(String content) throws IOException { public String[] decode(String content) throws IOException {
return this.objectMapper.readValue(content, String[].class); return this.objectMapper.readValue(content, String[].class);

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.web.socket.sockjs.support.frame; package org.springframework.web.socket.sockjs.frame;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -28,6 +28,8 @@ import org.springframework.util.Assert;
*/ */
public class SockJsFrame { 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 openFrame = new SockJsFrame("o");
private static final SockJsFrame heartbeatFrame = new SockJsFrame("h"); 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 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() { public static SockJsFrame openFrame() {
return 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() { public String getContent() {
return this.content; return this.content;
} }
public byte[] getContentBytes() { public byte[] getContentBytes() {
return this.content.getBytes(Charset.forName("UTF-8")); return this.content.getBytes(UTF8_CHARSET);
}
@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();
} }
@Override @Override
@ -105,34 +93,18 @@ public class SockJsFrame {
return this.content.equals(((SockJsFrame) other).content); return this.content.equals(((SockJsFrame) other).content);
} }
@Override
public interface FrameFormat { public int hashCode() {
return this.content.hashCode();
SockJsFrame format(SockJsFrame frame);
} }
public static class DefaultFrameFormat implements FrameFormat { @Override
public String toString() {
private final String format; String result = this.content;
if (result.length() > 80) {
public DefaultFrameFormat(String format) { result = result.substring(0, 80) + "...(truncated)";
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;
} }
return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'";
} }
} }

View File

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

View File

@ -14,7 +14,7 @@
* limitations under the License. * 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.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@ -18,5 +18,5 @@
* Support classes for creating SockJS frames including the encoding and decoding * Support classes for creating SockJS frames including the encoding and decoding
* of SockJS message frames. * of SockJS message frames.
*/ */
package org.springframework.web.socket.sockjs.support.frame; package org.springframework.web.socket.sockjs.frame;

View File

@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.InvalidMediaTypeException;
@ -53,10 +54,16 @@ import org.springframework.web.socket.sockjs.SockJsService;
*/ */
public abstract class AbstractSockJsService implements 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 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); private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this);
@ -72,13 +79,11 @@ public abstract class AbstractSockJsService implements SockJsService {
private int httpMessageCacheSize = 100; private int httpMessageCacheSize = 100;
private boolean webSocketsEnabled = true; private boolean webSocketEnabled = true;
private final TaskScheduler taskScheduler;
public AbstractSockJsService(TaskScheduler scheduler) { public AbstractSockJsService(TaskScheduler scheduler) {
Assert.notNull(scheduler, "scheduler must not be null"); Assert.notNull(scheduler, "TaskScheduler must not be null");
this.taskScheduler = scheduler; 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. * disable the WebSocket transport on the server side.
* <p>The default value is "true". * <p>The default value is "true".
*/ */
public void setWebSocketsEnabled(boolean webSocketsEnabled) { public void setWebSocketEnabled(boolean webSocketEnabled) {
this.webSocketsEnabled = webSocketsEnabled; this.webSocketEnabled = webSocketEnabled;
} }
/** /**
* Whether WebSocket transport is enabled. * Whether WebSocket transport is enabled.
* @see #setWebSocketsEnabled(boolean) * @see #setWebSocketEnabled(boolean)
*/ */
public boolean isWebSocketEnabled() { public boolean isWebSocketEnabled() {
return this.webSocketsEnabled; return this.webSocketEnabled;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* <p>This method determines the SockJS path and handles SockJS static URLs. Session * <p>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 { String sockJsPath, WebSocketHandler wsHandler) throws SockJsException {
if (sockJsPath == null) { 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); response.setStatusCode(HttpStatus.NOT_FOUND);
return; return;
} }
logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]"); if (logger.isDebugEnabled()) {
logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]");
}
try { try {
request.getHeaders(); request.getHeaders();
} }
catch (InvalidMediaTypeException ex) { catch (InvalidMediaTypeException ex) {
logger.warn("Invalid media type ignored: " + ex.getMediaType()); if (logger.isWarnEnabled()) {
logger.warn("Invalid media type ignored: " + ex.getMediaType());
}
} }
try { try {
@ -275,12 +287,16 @@ public abstract class AbstractSockJsService implements SockJsService {
this.iframeHandler.handle(request, response); this.iframeHandler.handle(request, response);
} }
else if (sockJsPath.equals("/websocket")) { else if (sockJsPath.equals("/websocket")) {
handleRawWebSocketRequest(request, response, wsHandler); if (isWebSocketEnabled()) {
handleRawWebSocketRequest(request, response, wsHandler);
}
} }
else { else {
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
if (pathSegments.length != 3) { 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); response.setStatusCode(HttpStatus.NOT_FOUND);
return; 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) { protected boolean validateRequest(String serverId, String sessionId, String transport) {
if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) { if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) {
logger.warn("Empty server, session, or transport value"); logger.warn("Empty server, session, or transport value");
return false; return false;
@ -341,8 +337,21 @@ public abstract class AbstractSockJsService implements SockJsService {
return true; 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"); String origin = request.getHeaders().getFirst("origin");
origin = ((origin == null) || origin.equals("null")) ? "*" : 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"); response.getHeaders().setCacheControl("no-store, no-cache, must-revalidate, max-age=0");
} }
protected void sendMethodNotAllowed(ServerHttpResponse response, List<HttpMethod> httpMethods) { protected void sendMethodNotAllowed(ServerHttpResponse response, HttpMethod... httpMethods) {
logger.debug("Sending Method Not Allowed (405)"); logger.debug("Sending Method Not Allowed (405)");
response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED); response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
response.getHeaders().setAllow(new HashSet<HttpMethod>(httpMethods)); response.getHeaders().setAllow(new HashSet<HttpMethod>(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 final SockJsRequestHandler infoHandler = new SockJsRequestHandler() {
private static final String INFO_CONTENT = private static final String INFO_CONTENT =
@ -393,26 +400,20 @@ public abstract class AbstractSockJsService implements SockJsService {
@Override @Override
public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (HttpMethod.GET.equals(request.getMethod())) { if (HttpMethod.GET.equals(request.getMethod())) {
response.getHeaders().setContentType(new MediaType("application", "json", UTF8_CHARSET));
response.getHeaders().setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
addCorsHeaders(request, response); addCorsHeaders(request, response);
addNoCacheHeaders(response); addNoCacheHeaders(response);
String content = String.format(INFO_CONTENT, random.nextInt(), isSessionCookieNeeded(), isWebSocketEnabled()); String content = String.format(INFO_CONTENT, random.nextInt(), isSessionCookieNeeded(), isWebSocketEnabled());
response.getBody().write(content.getBytes()); response.getBody().write(content.getBytes());
} }
else if (HttpMethod.OPTIONS.equals(request.getMethod())) { else if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatusCode(HttpStatus.NO_CONTENT); response.setStatusCode(HttpStatus.NO_CONTENT);
addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET); addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET);
addCacheHeaders(response); addCacheHeaders(response);
} }
else { 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 @Override
public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (!HttpMethod.GET.equals(request.getMethod())) { if (!HttpMethod.GET.equals(request.getMethod())) {
sendMethodNotAllowed(response, Arrays.asList(HttpMethod.GET)); sendMethodNotAllowed(response, HttpMethod.GET);
return; return;
} }
String content = String.format(IFRAME_CONTENT, getSockJsClientLibraryUrl()); 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"); StringBuilder builder = new StringBuilder("\"0");
DigestUtils.appendMd5DigestAsHex(contentBytes, builder); DigestUtils.appendMd5DigestAsHex(contentBytes, builder);
builder.append('"'); builder.append('"');
@ -458,7 +458,7 @@ public abstract class AbstractSockJsService implements SockJsService {
return; 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); response.getHeaders().setContentLength(contentBytes.length);
addCacheHeaders(response); addCacheHeaders(response);

View File

@ -14,10 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.web.socket.sockjs; package org.springframework.web.socket.sockjs.support;
import java.io.IOException; import java.io.IOException;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; 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.WebSocketHandler;
import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator; import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator;
import org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator; 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 * 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 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 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(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.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}. * Return the {@link WebSocketHandler}.
*/ */
public WebSocketHandler getWebSocketHandler() { public WebSocketHandler getWebSocketHandler() {
return this.wsHandler; return this.webSocketHandler;
} }
@Override @Override
public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws ServletException, IOException { throws ServletException, IOException {
@ -82,10 +85,10 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler {
ServerHttpResponse response = new ServletServerHttpResponse(servletResponse); ServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
try { try {
this.sockJsService.handleRequest(request, response, getSockJsPath(servletRequest), this.wsHandler); this.sockJsService.handleRequest(request, response, getSockJsPath(servletRequest), this.webSocketHandler);
} }
catch (Throwable t) { catch (Throwable ex) {
throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), t); throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), ex);
} }
} }

View File

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

View File

@ -14,12 +14,11 @@
* limitations under the License. * 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.scheduling.TaskScheduler;
import org.springframework.web.socket.sockjs.SockJsService; import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.support.AbstractSockJsService; import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
/** /**
* Provides transport handling code with access to the {@link SockJsService} configuration * 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 { 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 * Streaming transports save responses on the client side and don't free
* memory used by delivered messages. Such transports need to recycle the * memory used by delivered messages. Such transports need to recycle the
@ -51,9 +55,16 @@ public interface SockJsServiceConfig {
long getHeartbeatTime(); 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.
* <p>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.
* <p>The default size is 100.
*/ */
TaskScheduler getTaskScheduler(); int getHttpMessageCacheSize();
/** /**
* The codec to use for encoding and decoding SockJS messages. * The codec to use for encoding and decoding SockJS messages.
@ -61,15 +72,4 @@ public interface SockJsServiceConfig {
*/ */
SockJsMessageCodec getMessageCodec(); 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.
* <p>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.
* <p>The default size is 100.
*/
int getHttpMessageCacheSize();
} }

View File

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

View File

@ -14,13 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport;
import java.util.Map; import java.util.Map;
import org.springframework.web.socket.WebSocketHandler; 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 * 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. * Create a new SockJS session.
* @param sessionId the ID of the 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 * @param attributes handshake request specific attributes
* @return a new session, never {@code null} * @return a new session, never {@code null}
*/ */
AbstractSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, Map<String, Object> attributes); SockJsSession createSession(String sessionId, WebSocketHandler handler, Map<String, Object> attributes);
} }

View File

@ -19,7 +19,6 @@ package org.springframework.web.socket.sockjs.transport;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler; 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.SockJsException;
import org.springframework.web.socket.sockjs.SockJsService; 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. * Handle a SockJS session URL, i.e. transport-specific request.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0 * @since 4.0
*/ */
public interface TransportHandler { 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(); TransportType getTransportType();
/** /**
* Handle the given request and delegate messages to the provided * Handle the given request and delegate messages to the provided
* {@link WebSocketHandler}. * {@link WebSocketHandler}.
*
* @param request the current request * @param request the current request
* @param response the current response * @param response the current response
* @param handler the target WebSocketHandler, never {@code null} * @param handler the target WebSocketHandler (never {@code null})
* @param session the SockJS session, never {@code null} * @param session the SockJS session (never {@code null})
* * @throws SockJsException raised when request processing fails as
* @throws SockJsException raised when request processing fails as explained in * explained in {@link SockJsService}
* {@link SockJsService}
*/ */
void handleRequest(ServerHttpRequest request, ServerHttpResponse response, void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, WebSocketSession session) throws SockJsException; WebSocketHandler handler, SockJsSession session) throws SockJsException;
} }

View File

@ -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.
*
* <p>Based on the {@link TransportHandler} SPI. {@link TransportHandler}s may additionally
* implement the {@link SockJsSessionFactory} and {@link HandshakeHandler} interfaces.
*
* <p>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<TransportType, TransportHandler> handlers = new HashMap<TransportType, TransportHandler>();
private SockJsMessageCodec messageCodec;
private final List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();
private final Map<String, SockJsSession> sessions = new ConcurrentHashMap<String, SockJsSession>();
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<TransportHandler> 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<TransportType, TransportHandler> 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<HandshakeInterceptor> interceptors) {
this.interceptors.clear();
if (interceptors != null) {
this.interceptors.addAll(interceptors);
}
}
/**
* Return the configured WebSocket handshake request interceptors.
*/
public List<HandshakeInterceptor> 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<String, Object> attributes = new HashMap<String, Object>();
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<String, Object> attributes = new HashMap<String, Object>();
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<String, Object> 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());
}
}

View File

@ -17,34 +17,31 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler; 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.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 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. * Base class for HTTP transport handlers that receive messages via HTTP POST.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public abstract class AbstractHttpReceivingTransportHandler public abstract class AbstractHttpReceivingTransportHandler extends AbstractTransportHandler {
extends TransportHandlerSupport implements TransportHandler {
@Override @Override
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, 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"); Assert.notNull(wsSession, "No session");
AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession; AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession;
@ -55,22 +52,22 @@ public abstract class AbstractHttpReceivingTransportHandler
protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException { WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException {
String[] messages = null; String[] messages;
try { try {
messages = readMessages(request); messages = readMessages(request);
} }
catch (JsonMappingException ex) { catch (JsonMappingException ex) {
logger.error("Failed to read message: " + ex.getMessage()); logger.error("Failed to read message", ex);
handleReadError(response, "Payload expected.", sockJsSession.getId()); handleReadError(response, "Payload expected.", sockJsSession.getId());
return; return;
} }
catch (IOException ex) { 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()); handleReadError(response, "Broken JSON encoding.", sockJsSession.getId());
return; return;
} }
catch (Throwable t) { catch (Throwable ex) {
logger.error("Failed to read message: " + t.getMessage()); logger.error("Failed to read message", ex);
handleReadError(response, "Failed to read message(s)", sockJsSession.getId()); handleReadError(response, "Failed to read message(s)", sockJsSession.getId());
return; return;
} }
@ -85,7 +82,7 @@ public abstract class AbstractHttpReceivingTransportHandler
} }
response.setStatusCode(getResponseStatus()); 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); sockJsSession.delegateMessages(messages);
} }

View File

@ -25,11 +25,11 @@ import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler; 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.SockJsException;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; import org.springframework.web.socket.sockjs.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.transport.TransportHandler; 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.socket.sockjs.transport.session.AbstractHttpSockJsSession;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils; import org.springframework.web.util.UriUtils;
@ -40,13 +40,12 @@ import org.springframework.web.util.UriUtils;
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public abstract class AbstractHttpSendingTransportHandler extends TransportHandlerSupport public abstract class AbstractHttpSendingTransportHandler extends AbstractTransportHandler
implements TransportHandler, SockJsSessionFactory { implements SockJsSessionFactory {
@Override @Override
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException { WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException {
AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession; AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession;
@ -95,7 +94,7 @@ public abstract class AbstractHttpSendingTransportHandler extends TransportHandl
protected abstract MediaType getContentType(); protected abstract MediaType getContentType();
protected abstract FrameFormat getFrameFormat(ServerHttpRequest request); protected abstract SockJsFrameFormat getFrameFormat(ServerHttpRequest request);
protected final String getCallbackParam(ServerHttpRequest request) { protected final String getCallbackParam(ServerHttpRequest request) {
String query = request.getURI().getQuery(); String query = request.getURI().getQuery();

View File

@ -16,27 +16,34 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; 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 * @author Rossen Stoyanchev
* @since 4.0 * @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()); protected final Log logger = LogFactory.getLog(this.getClass());
private SockJsServiceConfig sockJsServiceConfig; private SockJsServiceConfig serviceConfig;
public void setSockJsServiceConfiguration(SockJsServiceConfig sockJsConfig) { @Override
this.sockJsServiceConfig = sockJsConfig; public void initialize(SockJsServiceConfig serviceConfig) {
this.serviceConfig = serviceConfig;
} }
public SockJsServiceConfig getSockJsServiceConfig() { public SockJsServiceConfig getServiceConfig() {
return this.sockJsServiceConfig; return this.serviceConfig;
} }
} }

View File

@ -16,397 +16,87 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.LinkedHashSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import org.springframework.http.HttpMethod; import org.apache.commons.logging.Log;
import org.springframework.http.HttpStatus; import org.apache.commons.logging.LogFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.scheduling.TaskScheduler; 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.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.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
/** /**
* A default implementation of {@link SockJsService} adding support for transport handling * A default implementation of {@link org.springframework.web.socket.sockjs.SockJsService}
* and session management. See {@link AbstractSockJsService} base class for important * with all default {@link TransportHandler} implementations pre-registered.
* details on request mapping.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0 * @since 4.0
*/ */
public class DefaultSockJsService extends AbstractSockJsService { public class DefaultSockJsService extends TransportHandlingSockJsService {
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<TransportType, TransportHandler> transportHandlers = new HashMap<TransportType, TransportHandler>();
private SockJsMessageCodec messageCodec;
private final List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();
private final Map<String, AbstractSockJsSession> sessions = new ConcurrentHashMap<String, AbstractSockJsSession>();
private ScheduledFuture<?> sessionCleanupTask;
/** /**
* Create an instance with default {@link TransportHandler transport handler} types. * Create a DefaultSockJsService with default {@link TransportHandler handler} types.
* @param taskScheduler a task scheduler for heart-beat messages and removing * @param scheduler a task scheduler for heart-beat messages and removing
* timed-out sessions; the provided TaskScheduler should be declared as a * 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 * Spring bean to ensure it is initialized at start up and shut down when the
* application stops. * application stops.
*/ */
public DefaultSockJsService(TaskScheduler taskScheduler) { public DefaultSockJsService(TaskScheduler scheduler) {
this(taskScheduler, null); this(scheduler, getDefaultTransportHandlers(null));
} }
/** /**
* Create an instance by overriding or replacing completely the default * Create a DefaultSockJsService with overridden {@link TransportHandler handler} types
* {@link TransportHandler transport handler} types. * replacing the corresponding default handler implementation.
* @param taskScheduler a task scheduler for heart-beat messages and removing * @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions;
* timed-out sessions; the provided TaskScheduler should be declared as a * the provided TaskScheduler should be declared as a Spring bean to ensure it gets
* Spring bean to ensure it is initialized at start up and shut down when the * initialized at start-up and shuts down when the application stops
* application stops. * @param handlerOverrides zero or more overrides to the default transport handler types
* @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.
*/ */
public DefaultSockJsService(TaskScheduler taskScheduler, Collection<TransportHandler> transportHandlers, public DefaultSockJsService(TaskScheduler scheduler, TransportHandler... handlerOverrides) {
TransportHandler... transportHandlerOverrides) { this(scheduler, Arrays.asList(handlerOverrides));
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");
}
} }
private void initMessageCodec() { /**
if (jackson2Present) { * Create a DefaultSockJsService with overridden {@link TransportHandler handler} types
this.messageCodec = new Jackson2SockJsMessageCodec(); * replacing the corresponding default handler implementation.
} * @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions;
else if (jacksonPresent) { * the provided TaskScheduler should be declared as a Spring bean to ensure it gets
this.messageCodec = new JacksonSockJsMessageCodec(); * 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<TransportHandler> handlerOverrides) {
super(scheduler, getDefaultTransportHandlers(handlerOverrides));
} }
protected final Set<TransportHandler> getDefaultTransportHandlers() {
Set<TransportHandler> result = new HashSet<TransportHandler>(); private static Set<TransportHandler> getDefaultTransportHandlers(Collection<TransportHandler> overrides) {
Set<TransportHandler> result = new LinkedHashSet<TransportHandler>(8);
result.add(new XhrPollingTransportHandler()); result.add(new XhrPollingTransportHandler());
result.add(new XhrReceivingTransportHandler()); result.add(new XhrReceivingTransportHandler());
result.add(new XhrStreamingTransportHandler());
result.add(new JsonpPollingTransportHandler()); result.add(new JsonpPollingTransportHandler());
result.add(new JsonpReceivingTransportHandler()); result.add(new JsonpReceivingTransportHandler());
result.add(new XhrStreamingTransportHandler());
result.add(new EventSourceTransportHandler()); result.add(new EventSourceTransportHandler());
result.add(new HtmlFileTransportHandler()); result.add(new HtmlFileTransportHandler());
try { try {
result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler())); result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler()));
} }
catch (Exception ex) { catch (Exception ex) {
Log logger = LogFactory.getLog(DefaultSockJsService.class);
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn("Failed to create default WebSocketTransportHandler", ex); logger.warn("Failed to create default WebSocketTransportHandler", ex);
} }
} }
if (overrides != null) {
result.addAll(overrides);
}
return result; return result;
} }
protected void addTransportHandlers(Collection<TransportHandler> 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<HandshakeInterceptor> interceptors) {
this.interceptors.clear();
if (interceptors != null) {
this.interceptors.addAll(interceptors);
}
}
/**
* Return the configured WebSocket handshake request interceptors.
*/
public List<HandshakeInterceptor> 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<TransportType, TransportHandler> 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<String, Object> attributes = new HashMap<String, Object>();
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<HttpMethod> 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<String, Object> attributes = new HashMap<String, Object>();
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<String, Object> 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();
}
};
} }

View File

@ -17,16 +17,15 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map; import java.util.Map;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; 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.TransportType;
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession; 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 { public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler {
@Override @Override
public TransportType getTransportType() { public TransportType getTransportType() {
return TransportType.EVENT_SOURCE; return TransportType.EVENT_SOURCE;
@ -46,19 +44,19 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan
@Override @Override
protected MediaType getContentType() { protected MediaType getContentType() {
return new MediaType("text", "event-stream", Charset.forName("UTF-8")); return new MediaType("text", "event-stream", UTF8_CHARSET);
} }
@Override @Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) { Map<String, Object> attributes) {
return new EventSourceStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); return new EventSourceStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
} }
@Override @Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) { protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultFrameFormat("data: %s\r\n\r\n"); return new DefaultSockJsFrameFormat("data: %s\r\n\r\n");
} }

View File

@ -17,7 +17,6 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map; import java.util.Map;
import org.springframework.http.HttpStatus; 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.CloseStatus;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; 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.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType; 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.AbstractHttpSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession; import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
import org.springframework.web.util.JavaScriptUtils; import org.springframework.web.util.JavaScriptUtils;
@ -84,14 +83,14 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
@Override @Override
protected MediaType getContentType() { protected MediaType getContentType() {
return new MediaType("text", "html", Charset.forName("UTF-8")); return new MediaType("text", "html", UTF8_CHARSET);
} }
@Override @Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) { Map<String, Object> attributes) {
return new HtmlFileStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); return new HtmlFileStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
} }
@Override @Override
@ -115,8 +114,8 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
} }
@Override @Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) { protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultFrameFormat("<script>\np(\"%s\");\n</script>\r\n") { return new DefaultSockJsFrameFormat("<script>\np(\"%s\");\n</script>\r\n") {
@Override @Override
protected String preProcessContent(String content) { protected String preProcessContent(String content) {
return JavaScriptUtils.javaScriptEscape(content); return JavaScriptUtils.javaScriptEscape(content);

View File

@ -16,7 +16,6 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.nio.charset.Charset;
import java.util.Map; import java.util.Map;
import org.springframework.http.HttpStatus; 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.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.transport.TransportType; 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.AbstractHttpSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession; import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
@ -50,14 +49,14 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
@Override @Override
protected MediaType getContentType() { protected MediaType getContentType() {
return new MediaType("application", "javascript", Charset.forName("UTF-8")); return new MediaType("application", "javascript", UTF8_CHARSET);
} }
@Override @Override
public PollingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) { Map<String, Object> attributes) {
return new PollingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); return new PollingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
} }
@Override @Override
@ -72,21 +71,20 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
return; return;
} }
} }
catch (Throwable t) { catch (Throwable ex) {
sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); sockJsSession.tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), t); throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), ex);
} }
super.handleRequestInternal(request, response, sockJsSession); super.handleRequestInternal(request, response, sockJsSession);
} }
@Override @Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) { protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
// we already validated the parameter above...
// we already validated the parameter above..
String callback = getCallbackParam(request); String callback = getCallbackParam(request);
return new SockJsFrame.DefaultFrameFormat(callback + "(\"%s\");\r\n") { return new DefaultSockJsFrameFormat(callback + "(\"%s\");\r\n") {
@Override @Override
protected String preProcessContent(String content) { protected String preProcessContent(String content) {
return JavaScriptUtils.javaScriptEscape(content); return JavaScriptUtils.javaScriptEscape(content);

View File

@ -27,7 +27,7 @@ import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException; 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.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType; 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.AbstractHttpSockJsSession;
@ -62,9 +62,7 @@ public class JsonpReceivingTransportHandler extends AbstractHttpReceivingTranspo
@Override @Override
protected String[] readMessages(ServerHttpRequest request) throws IOException { protected String[] readMessages(ServerHttpRequest request) throws IOException {
SockJsMessageCodec messageCodec = getServiceConfig().getMessageCodec();
SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec();
MediaType contentType = request.getHeaders().getContentType(); MediaType contentType = request.getHeaders().getContentType();
if ((contentType != null) && MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) { if ((contentType != null) && MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
MultiValueMap<String, String> map = this.formConverter.read(null, request); MultiValueMap<String, String> map = this.formConverter.read(null, request);

View File

@ -24,7 +24,7 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler; 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; import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession;
/** /**

View File

@ -24,11 +24,12 @@ import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler; 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.HandshakeFailureException;
import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType; 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.AbstractSockJsSession;
@ -44,8 +45,8 @@ import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSo
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public class WebSocketTransportHandler extends TransportHandlerSupport public class WebSocketTransportHandler extends AbstractTransportHandler
implements TransportHandler, SockJsSessionFactory, HandshakeHandler { implements SockJsSessionFactory, HandshakeHandler {
private final HandshakeHandler handshakeHandler; private final HandshakeHandler handshakeHandler;
@ -66,24 +67,24 @@ public class WebSocketTransportHandler extends TransportHandlerSupport
} }
@Override @Override
public AbstractSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, public AbstractSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) { Map<String, Object> attributes) {
return new WebSocketServerSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); return new WebSocketServerSockJsSession(sessionId, getServiceConfig(), handler, attributes);
} }
@Override @Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException { WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException {
WebSocketServerSockJsSession sockJsSession = (WebSocketServerSockJsSession) wsSession; WebSocketServerSockJsSession sockJsSession = (WebSocketServerSockJsSession) wsSession;
try { try {
wsHandler = new SockJsWebSocketHandler(getSockJsServiceConfig(), wsHandler, sockJsSession); wsHandler = new SockJsWebSocketHandler(getServiceConfig(), wsHandler, sockJsSession);
this.handshakeHandler.doHandshake(request, response, wsHandler, Collections.<String, Object>emptyMap()); this.handshakeHandler.doHandshake(request, response, wsHandler, Collections.<String, Object>emptyMap());
} }
catch (Throwable t) { catch (Throwable ex) {
sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); sockJsSession.tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
throw new SockJsTransportFailureException("WebSocket handshake failure", wsSession.getId(), t); throw new SockJsTransportFailureException("WebSocket handshake failure", wsSession.getId(), ex);
} }
} }

View File

@ -16,14 +16,13 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.nio.charset.Charset;
import java.util.Map; import java.util.Map;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.transport.TransportHandler; import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType; import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession; import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
@ -43,19 +42,19 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand
@Override @Override
protected MediaType getContentType() { protected MediaType getContentType() {
return new MediaType("application", "javascript", Charset.forName("UTF-8")); return new MediaType("application", "javascript", UTF8_CHARSET);
} }
@Override @Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) { protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultFrameFormat("%s\n"); return new DefaultSockJsFrameFormat("%s\n");
} }
@Override @Override
public PollingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) { Map<String, Object> attributes) {
return new PollingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); return new PollingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
} }
} }

View File

@ -27,10 +27,10 @@ import org.springframework.web.socket.sockjs.transport.TransportType;
* A {@link TransportHandler} that receives messages over HTTP. * A {@link TransportHandler} that receives messages over HTTP.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0
*/ */
public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransportHandler { public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransportHandler {
@Override @Override
public TransportType getTransportType() { public TransportType getTransportType() {
return TransportType.XHR_SEND; return TransportType.XHR_SEND;
@ -38,7 +38,7 @@ public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransport
@Override @Override
protected String[] readMessages(ServerHttpRequest request) throws IOException { protected String[] readMessages(ServerHttpRequest request) throws IOException {
return getSockJsServiceConfig().getMessageCodec().decodeInputStream(request.getBody()); return getServiceConfig().getMessageCodec().decodeInputStream(request.getBody());
} }
@Override @Override

View File

@ -17,17 +17,16 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map; import java.util.Map;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; 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.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType; 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; 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 { public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHandler {
@Override @Override
public TransportType getTransportType() { public TransportType getTransportType() {
return TransportType.XHR_STREAMING; return TransportType.XHR_STREAMING;
@ -46,19 +44,19 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa
@Override @Override
protected MediaType getContentType() { protected MediaType getContentType() {
return new MediaType("application", "javascript", Charset.forName("UTF-8")); return new MediaType("application", "javascript", UTF8_CHARSET);
} }
@Override @Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) { Map<String, Object> attributes) {
return new XhrStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes); return new XhrStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
} }
@Override @Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) { protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultFrameFormat("%s\n"); return new DefaultSockJsFrameFormat("%s\n");
} }

View File

@ -32,12 +32,13 @@ import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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.support.frame.SockJsFrame.FrameFormat; import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.WebSocketExtension; import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
/** /**
* An abstract base class for use with HTTP transport based SockJS sessions. * 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 { public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
private FrameFormat frameFormat;
private final BlockingQueue<String> messageCache; private final BlockingQueue<String> messageCache;
private ServerHttpRequest request; private ServerHttpRequest request;
@ -57,6 +56,8 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
private ServerHttpAsyncRequestControl asyncRequestControl; private ServerHttpAsyncRequestControl asyncRequestControl;
private SockJsFrameFormat frameFormat;
private URI uri; private URI uri;
private HttpHeaders handshakeHeaders; private HttpHeaders handshakeHeaders;
@ -88,42 +89,21 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
return this.handshakeHeaders; return this.handshakeHeaders;
} }
protected void setHandshakeHeaders(HttpHeaders handshakeHeaders) {
this.handshakeHeaders = handshakeHeaders;
}
@Override @Override
public Principal getPrincipal() { public Principal getPrincipal() {
return this.principal; return this.principal;
} }
protected void setPrincipal(Principal principal) {
this.principal = principal;
}
@Override @Override
public InetSocketAddress getLocalAddress() { public InetSocketAddress getLocalAddress() {
return this.localAddress; return this.localAddress;
} }
protected void setLocalAddress(InetSocketAddress localAddress) {
this.localAddress = localAddress;
}
@Override @Override
public InetSocketAddress getRemoteAddress() { public InetSocketAddress getRemoteAddress() {
return this.remoteAddress; return this.remoteAddress;
} }
protected void setRemoteAddress(InetSocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
@Override
public List<WebSocketExtension> getExtensions() {
return Collections.emptyList();
}
/** /**
* Unlike WebSocket where sub-protocol negotiation is part of the * Unlike WebSocket where sub-protocol negotiation is part of the
* initial handshake, in HTTP transports the same negotiation must * initial handshake, in HTTP transports the same negotiation must
@ -141,17 +121,23 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
return this.acceptedProtocol; return this.acceptedProtocol;
} }
public synchronized void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response, @Override
FrameFormat frameFormat) throws SockJsException { public List<WebSocketExtension> 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 { try {
writePrelude(); writePrelude();
writeFrame(SockJsFrame.openFrame()); writeFrame(SockJsFrame.openFrame());
} }
catch (Throwable t) { catch (Throwable ex) {
tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
throw new SockJsTransportFailureException("Failed to send \"open\" frame", getId(), t); throw new SockJsTransportFailureException("Failed to send \"open\" frame", getId(), ex);
} }
this.uri = request.getURI(); this.uri = request.getURI();
@ -163,8 +149,8 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
try { try {
delegateConnectionEstablished(); delegateConnectionEstablished();
} }
catch (Throwable t) { catch (Throwable ex) {
throw new SockJsException("Unhandled exception from WebSocketHandler", getId(), t); 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, 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 { try {
this.asyncRequestControl.start(-1); this.asyncRequestControl.start(-1);
scheduleHeartbeat(); scheduleHeartbeat();
tryFlushCache(); tryFlushCache();
} }
catch (Throwable t) { catch (Throwable ex) {
tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR); tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
throw new SockJsTransportFailureException("Failed to flush messages", getId(), t); throw new SockJsTransportFailureException("Failed to flush messages", getId(), ex);
} }
} }
private void udpateRequest(ServerHttpRequest request, ServerHttpResponse response, private void updateRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsFrameFormat frameFormat) {
FrameFormat frameFormat) { Assert.notNull(request, "Request must not be null");
Assert.notNull(request, "expected request"); Assert.notNull(response, "Response must not be null");
Assert.notNull(response, "expected response"); Assert.notNull(frameFormat, "SockJsFrameFormat must not be null");
Assert.notNull(frameFormat, "expected frameFormat");
this.request = request; this.request = request;
this.response = response; this.response = response;
this.asyncRequestControl = request.getAsyncRequestControl(response); this.asyncRequestControl = request.getAsyncRequestControl(response);
@ -203,7 +188,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
@Override @Override
public synchronized boolean isActive() { public synchronized boolean isActive() {
return ((this.asyncRequestControl != null) && (!this.asyncRequestControl.isCompleted())); return (this.asyncRequestControl != null && !this.asyncRequestControl.isCompleted());
} }
protected BlockingQueue<String> getMessageCache() { protected BlockingQueue<String> getMessageCache() {

View File

@ -28,23 +28,25 @@ import java.util.concurrent.ScheduledFuture;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage; 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.SockJsMessageDeliveryException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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 * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public abstract class AbstractSockJsSession implements WebSocketSession { public abstract class AbstractSockJsSession implements SockJsSession {
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
@ -68,21 +70,22 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
/** /**
* @param id the session ID * @param id the session ID
* @param config SockJS service configuration options * @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, public AbstractSockJsSession(String id, SockJsServiceConfig config,
WebSocketHandler wsHandler, Map<String, Object> handshakeAttributes) { WebSocketHandler handler, Map<String, Object> handshakeAttributes) {
Assert.notNull(id, "SessionId must not be null"); Assert.notNull(id, "SessionId must not be null");
Assert.notNull(config, "SockJsConfig 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.id = id;
this.config = config; this.config = config;
this.handler = wsHandler; this.handler = handler;
this.handshakeAttributes = handshakeAttributes; this.handshakeAttributes = handshakeAttributes;
} }
@Override @Override
public String getId() { public String getId() {
return this.id; return this.id;
@ -119,10 +122,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
*/ */
public abstract boolean isActive(); public abstract boolean isActive();
/** @Override
* Return the time since the session was last active, or otherwise if the
* session is new, the time since the session was created.
*/
public long getTimeSinceLastActive() { public long getTimeSinceLastActive() {
if (isNew()) { if (isNew()) {
return (System.currentTimeMillis() - this.timeCreated); return (System.currentTimeMillis() - this.timeCreated);
@ -156,8 +156,8 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
undelivered.remove(0); undelivered.remove(0);
} }
} }
catch (Throwable t) { catch (Throwable ex) {
throw new SockJsMessageDeliveryException(this.id, undelivered, t); throw new SockJsMessageDeliveryException(this.id, undelivered, ex);
} }
} }
} }
@ -235,8 +235,8 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
try { try {
this.handler.afterConnectionClosed(this, status); this.handler.afterConnectionClosed(this, status);
} }
catch (Throwable t) { catch (Throwable ex) {
logger.error("Unhandled error for " + this, t); logger.error("Unhandled error for " + this, ex);
} }
} }
} }
@ -302,7 +302,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
} }
protected void scheduleHeartbeat() { protected void scheduleHeartbeat() {
Assert.state(this.config.getTaskScheduler() != null, "heartbeatScheduler not configured"); Assert.state(this.config.getTaskScheduler() != null, "No TaskScheduler configured for heartbeat");
cancelHeartbeat(); cancelHeartbeat();
if (!isActive()) { if (!isActive()) {
return; return;
@ -313,7 +313,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession {
try { try {
sendHeartbeat(); sendHeartbeat();
} }
catch (Throwable t) { catch (Throwable ex) {
// ignore // ignore
} }
} }

View File

@ -17,20 +17,22 @@
package org.springframework.web.socket.sockjs.transport.session; package org.springframework.web.socket.sockjs.transport.session;
import java.util.Map; import java.util.Map;
import java.util.concurrent.BlockingQueue;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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.support.frame.SockJsMessageCodec; 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. * A SockJS session for use with polling HTTP transports.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0
*/ */
public class PollingSockJsSession extends AbstractHttpSockJsSession { public class PollingSockJsSession extends AbstractHttpSockJsSession {
public PollingSockJsSession(String sessionId, SockJsServiceConfig config, public PollingSockJsSession(String sessionId, SockJsServiceConfig config,
WebSocketHandler wsHandler, Map<String, Object> attributes) { WebSocketHandler wsHandler, Map<String, Object> attributes) {
@ -40,10 +42,10 @@ public class PollingSockJsSession extends AbstractHttpSockJsSession {
@Override @Override
protected void flushCache() throws SockJsTransportFailureException { protected void flushCache() throws SockJsTransportFailureException {
cancelHeartbeat(); cancelHeartbeat();
String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]); BlockingQueue<String> messageCache = getMessageCache();
getMessageCache().clear(); String[] messages = messageCache.toArray(new String[messageCache.size()]);
messageCache.clear();
SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec(); SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec();
SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, messages); SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, messages);

View File

@ -24,14 +24,16 @@ import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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.support.frame.SockJsFrame.FrameFormat; import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; 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. * A SockJS session for use with streaming HTTP transports.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 4.0
*/ */
public class StreamingSockJsSession extends AbstractHttpSockJsSession { public class StreamingSockJsSession extends AbstractHttpSockJsSession {
@ -47,7 +49,7 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession {
@Override @Override
public synchronized void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response, public synchronized void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response,
FrameFormat frameFormat) throws SockJsException { SockJsFrameFormat frameFormat) throws SockJsException {
super.handleInitialRequest(request, response, frameFormat); super.handleInitialRequest(request, response, frameFormat);
@ -59,7 +61,6 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession {
@Override @Override
protected void flushCache() throws SockJsTransportFailureException { protected void flushCache() throws SockJsTransportFailureException {
cancelHeartbeat(); cancelHeartbeat();
do { do {

View File

@ -33,8 +33,9 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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.support.frame.SockJsMessageCodec; 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. * 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 * @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public class WebSocketServerSockJsSession extends AbstractSockJsSession public class WebSocketServerSockJsSession extends AbstractSockJsSession implements NativeWebSocketSession {
implements WebSocketSession, NativeWebSocketSession {
private WebSocketSession wsSession; private WebSocketSession webSocketSession;
public WebSocketServerSockJsSession(String id, SockJsServiceConfig config, public WebSocketServerSockJsSession(String id, SockJsServiceConfig config,
WebSocketHandler wsHandler, Map<String, Object> attributes) { WebSocketHandler handler, Map<String, Object> attributes) {
super(id, config, wsHandler, attributes); super(id, config, handler, attributes);
} }
@Override @Override
public URI getUri() { public URI getUri() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getUri(); return this.webSocketSession.getUri();
} }
@Override @Override
public HttpHeaders getHandshakeHeaders() { public HttpHeaders getHandshakeHeaders() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getHandshakeHeaders(); return this.webSocketSession.getHandshakeHeaders();
} }
@Override @Override
public Principal getPrincipal() { public Principal getPrincipal() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getPrincipal(); return this.webSocketSession.getPrincipal();
} }
@Override @Override
public InetSocketAddress getLocalAddress() { public InetSocketAddress getLocalAddress() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getLocalAddress(); return this.webSocketSession.getLocalAddress();
} }
@Override @Override
public InetSocketAddress getRemoteAddress() { public InetSocketAddress getRemoteAddress() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getRemoteAddress(); return this.webSocketSession.getRemoteAddress();
} }
@Override @Override
public String getAcceptedProtocol() { public String getAcceptedProtocol() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getAcceptedProtocol(); return this.webSocketSession.getAcceptedProtocol();
} }
@Override @Override
public List<WebSocketExtension> getExtensions() { public List<WebSocketExtension> getExtensions() {
checkDelegateSessionInitialized(); checkDelegateSessionInitialized();
return this.wsSession.getExtensions(); return this.webSocketSession.getExtensions();
} }
private void checkDelegateSessionInitialized() { private void checkDelegateSessionInitialized() {
Assert.state(this.wsSession != null, "WebSocketSession not yet initialized"); Assert.state(this.webSocketSession != null, "WebSocketSession not yet initialized");
} }
@Override @Override
public Object getNativeSession() { public Object getNativeSession() {
if ((this.wsSession != null) && (this.wsSession instanceof NativeWebSocketSession)) { if ((this.webSocketSession != null) && (this.webSocketSession instanceof NativeWebSocketSession)) {
return ((NativeWebSocketSession) this.wsSession).getNativeSession(); return ((NativeWebSocketSession) this.webSocketSession).getNativeSession();
} }
return null; return null;
} }
@Override @Override
public <T> T getNativeSession(Class<T> requiredType) { public <T> T getNativeSession(Class<T> requiredType) {
if ((this.wsSession != null) && (this.wsSession instanceof NativeWebSocketSession)) { if ((this.webSocketSession != null) && (this.webSocketSession instanceof NativeWebSocketSession)) {
return ((NativeWebSocketSession) this.wsSession).getNativeSession(requiredType); return ((NativeWebSocketSession) this.webSocketSession).getNativeSession(requiredType);
} }
return null; return null;
} }
public void initializeDelegateSession(WebSocketSession session) { public void initializeDelegateSession(WebSocketSession session) {
this.wsSession = session; this.webSocketSession = session;
try { try {
TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent());
this.wsSession.sendMessage(message); this.webSocketSession.sendMessage(message);
scheduleHeartbeat(); scheduleHeartbeat();
delegateConnectionEstablished(); delegateConnectionEstablished();
} }
catch (Exception ex) { catch (Exception ex) {
tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR); tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
return;
} }
} }
@Override @Override
public boolean isActive() { 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 { public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception {
@ -170,13 +169,13 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession
logger.trace("Write " + frame); logger.trace("Write " + frame);
} }
TextMessage message = new TextMessage(frame.getContent()); TextMessage message = new TextMessage(frame.getContent());
this.wsSession.sendMessage(message); this.webSocketSession.sendMessage(message);
} }
@Override @Override
protected void disconnect(CloseStatus status) throws IOException { protected void disconnect(CloseStatus status) throws IOException {
if (this.wsSession != null) { if (this.webSocketSession != null) {
this.wsSession.close(status); this.webSocketSession.close(status);
} }
} }

View File

@ -46,8 +46,9 @@ import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.HandshakeInterceptor; import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; 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.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.DefaultSockJsService;
import org.springframework.web.socket.sockjs.transport.handler.EventSourceTransportHandler; import org.springframework.web.socket.sockjs.transport.handler.EventSourceTransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.HtmlFileTransportHandler; import org.springframework.web.socket.sockjs.transport.handler.HtmlFileTransportHandler;
@ -150,7 +151,7 @@ public class HandlersBeanDefinitionParserTests {
} }
@Test @Test
public void sockJSSupport() { public void sockJsSupport() {
loadBeanDefinitions("websocket-config-handlers-sockjs.xml"); loadBeanDefinitions("websocket-config-handlers-sockjs.xml");
SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(handlerMapping); assertNotNull(handlerMapping);
@ -182,7 +183,7 @@ public class HandlersBeanDefinitionParserTests {
} }
@Test @Test
public void sockJSAttributesSupport() { public void sockJsAttributesSupport() {
loadBeanDefinitions("websocket-config-handlers-sockjs-attributes.xml"); loadBeanDefinitions("websocket-config-handlers-sockjs-attributes.xml");
SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping handlerMapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(handlerMapping); assertNotNull(handlerMapping);
@ -191,8 +192,8 @@ public class HandlersBeanDefinitionParserTests {
checkDelegateHandlerType(handler.getWebSocketHandler(), TestWebSocketHandler.class); checkDelegateHandlerType(handler.getWebSocketHandler(), TestWebSocketHandler.class);
SockJsService sockJsService = handler.getSockJsService(); SockJsService sockJsService = handler.getSockJsService();
assertNotNull(sockJsService); assertNotNull(sockJsService);
assertThat(sockJsService, Matchers.instanceOf(DefaultSockJsService.class)); assertThat(sockJsService, Matchers.instanceOf(TransportHandlingSockJsService.class));
DefaultSockJsService defaultSockJsService = (DefaultSockJsService) sockJsService; TransportHandlingSockJsService defaultSockJsService = (TransportHandlingSockJsService) sockJsService;
assertThat(defaultSockJsService.getTaskScheduler(), Matchers.instanceOf(TestTaskScheduler.class)); assertThat(defaultSockJsService.getTaskScheduler(), Matchers.instanceOf(TestTaskScheduler.class));
assertThat(defaultSockJsService.getTransportHandlers().values(), Matchers.containsInAnyOrder( assertThat(defaultSockJsService.getTransportHandlers().values(), Matchers.containsInAnyOrder(
Matchers.instanceOf(XhrPollingTransportHandler.class), Matchers.instanceOf(XhrPollingTransportHandler.class),

View File

@ -48,7 +48,7 @@ import org.springframework.web.socket.handler.WebSocketHandlerDecorator;
import org.springframework.web.socket.messaging.StompSubProtocolHandler; import org.springframework.web.socket.messaging.StompSubProtocolHandler;
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; 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.*; import static org.junit.Assert.*;

View File

@ -31,7 +31,7 @@ import org.springframework.web.HttpRequestHandler;
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler; import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; 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.TransportType;
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler; import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;

View File

@ -54,10 +54,10 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
@Test @Test
public void validateRequest() throws Exception { public void validateRequest() throws Exception {
this.service.setWebSocketsEnabled(false); this.service.setWebSocketEnabled(false);
handleRequest("GET", "/echo/server/session/websocket", HttpStatus.NOT_FOUND); 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/server/session/websocket", HttpStatus.OK);
handleRequest("GET", "/echo//", HttpStatus.NOT_FOUND); handleRequest("GET", "/echo//", HttpStatus.NOT_FOUND);
@ -86,7 +86,7 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
body.substring(body.indexOf(','))); body.substring(body.indexOf(',')));
this.service.setSessionCookieNeeded(false); this.service.setSessionCookieNeeded(false);
this.service.setWebSocketsEnabled(false); this.service.setWebSocketEnabled(false);
handleRequest("GET", "/echo/info", HttpStatus.OK); handleRequest("GET", "/echo/info", HttpStatus.OK);
body = this.servletResponse.getContentAsString(); body = this.servletResponse.getContentAsString();
@ -187,11 +187,6 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
this.transport = transport; this.transport = transport;
this.handler = handler; this.handler = handler;
} }
@Override
protected boolean isValidTransportType(String transportType) {
return TransportType.fromValue(transportType) != null;
}
} }
} }

View File

@ -16,29 +16,31 @@
package org.springframework.web.socket.sockjs.transport.handler; package org.springframework.web.socket.sockjs.transport.handler;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests; import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler; 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.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
import org.springframework.web.socket.sockjs.transport.TransportType; 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.StubSockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.session.TestSockJsSession; import org.springframework.web.socket.sockjs.transport.session.TestSockJsSession;
import static org.junit.Assert.*; 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.*; 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 * @author Rossen Stoyanchev
*/ */
@ -61,14 +63,12 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
private TestSockJsSession session; private TestSockJsSession session;
private DefaultSockJsService service; private TransportHandlingSockJsService service;
@Before @Before
public void setup() { public void setup() {
super.setUp(); super.setUp();
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
Map<String, Object> attributes = Collections.emptyMap(); Map<String, Object> 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.xhrHandler.createSession(sessionId, this.wsHandler, attributes)).thenReturn(this.session);
when(this.xhrSendHandler.getTransportType()).thenReturn(TransportType.XHR_SEND); when(this.xhrSendHandler.getTransportType()).thenReturn(TransportType.XHR_SEND);
this.service = new DefaultSockJsService(this.taskScheduler, this.service = new TransportHandlingSockJsService(this.taskScheduler, this.xhrHandler, this.xhrSendHandler);
Arrays.<TransportHandler>asList(this.xhrHandler, this.xhrSendHandler));
} }
@Test @Test
public void defaultTransportHandlers() { public void defaultTransportHandlers() {
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class)); DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class));
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers(); Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
@ -101,10 +99,9 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test @Test
public void defaultTransportHandlersWithOverride() { public void defaultTransportHandlersWithOverride() {
XhrReceivingTransportHandler xhrHandler = new XhrReceivingTransportHandler(); XhrReceivingTransportHandler xhrHandler = new XhrReceivingTransportHandler();
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), null, xhrHandler); DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), xhrHandler);
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers(); Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
assertEquals(8, handlers.size()); assertEquals(8, handlers.size());
@ -113,19 +110,15 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test @Test
public void customizedTransportHandlerList() { public void customizedTransportHandlerList() {
TransportHandlingSockJsService service = new TransportHandlingSockJsService(
List<TransportHandler> handlers = Arrays.<TransportHandler>asList( mock(TaskScheduler.class), new XhrPollingTransportHandler(), new XhrReceivingTransportHandler());
new XhrPollingTransportHandler(), new XhrReceivingTransportHandler());
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), handlers);
Map<TransportType, TransportHandler> actualHandlers = service.getTransportHandlers(); Map<TransportType, TransportHandler> actualHandlers = service.getTransportHandlers();
assertEquals(handlers.size(), actualHandlers.size()); assertEquals(2, actualHandlers.size());
} }
@Test @Test
public void handleTransportRequestXhr() throws Exception { public void handleTransportRequestXhr() throws Exception {
String sockJsPath = sessionUrlPrefix + "xhr"; String sockJsPath = sessionUrlPrefix + "xhr";
setRequest("POST", sockJsPrefix + sockJsPath); setRequest("POST", sockJsPrefix + sockJsPath);
this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
@ -141,7 +134,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test @Test
public void handleTransportRequestXhrOptions() throws Exception { public void handleTransportRequestXhrOptions() throws Exception {
String sockJsPath = sessionUrlPrefix + "xhr"; String sockJsPath = sessionUrlPrefix + "xhr";
setRequest("OPTIONS", sockJsPrefix + sockJsPath); setRequest("OPTIONS", sockJsPrefix + sockJsPath);
this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
@ -154,7 +146,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test @Test
public void handleTransportRequestNoSuitableHandler() throws Exception { public void handleTransportRequestNoSuitableHandler() throws Exception {
String sockJsPath = sessionUrlPrefix + "eventsource"; String sockJsPath = sessionUrlPrefix + "eventsource";
setRequest("POST", sockJsPrefix + sockJsPath); setRequest("POST", sockJsPrefix + sockJsPath);
this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
@ -164,7 +155,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
@Test @Test
public void handleTransportRequestXhrSend() throws Exception { public void handleTransportRequestXhrSend() throws Exception {
String sockJsPath = sessionUrlPrefix + "xhr_send"; String sockJsPath = sessionUrlPrefix + "xhr_send";
setRequest("POST", sockJsPrefix + sockJsPath); setRequest("POST", sockJsPrefix + sockJsPath);
this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler); this.service.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);

View File

@ -114,7 +114,7 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest
try { try {
XhrReceivingTransportHandler transportHandler = new XhrReceivingTransportHandler(); XhrReceivingTransportHandler transportHandler = new XhrReceivingTransportHandler();
transportHandler.setSockJsServiceConfiguration(sockJsConfig); transportHandler.initialize(sockJsConfig);
transportHandler.handleRequest(this.request, this.response, wsHandler, session); transportHandler.handleRequest(this.request, this.response, wsHandler, session);
fail("Expected exception"); fail("Expected exception");
} }
@ -129,7 +129,7 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest
WebSocketHandler wsHandler = mock(WebSocketHandler.class); WebSocketHandler wsHandler = mock(WebSocketHandler.class);
AbstractSockJsSession session = new TestHttpSockJsSession("1", new StubSockJsServiceConfig(), wsHandler, null); 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); transportHandler.handleRequest(this.request, this.response, wsHandler, session);
assertEquals("text/plain;charset=UTF-8", this.response.getHeaders().getContentType().toString()); assertEquals("text/plain;charset=UTF-8", this.response.getHeaders().getContentType().toString());

View File

@ -20,18 +20,19 @@ import java.sql.Date;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests; import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; 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.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession; 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.StreamingSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig; import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Matchers.*; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
/** /**
@ -64,7 +65,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
public void handleRequestXhr() throws Exception { public void handleRequestXhr() throws Exception {
XhrPollingTransportHandler transportHandler = new XhrPollingTransportHandler(); XhrPollingTransportHandler transportHandler = new XhrPollingTransportHandler();
transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); transportHandler.initialize(this.sockJsConfig);
AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
@ -91,7 +92,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
public void jsonpTransport() throws Exception { public void jsonpTransport() throws Exception {
JsonpPollingTransportHandler transportHandler = new JsonpPollingTransportHandler(); JsonpPollingTransportHandler transportHandler = new JsonpPollingTransportHandler();
transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); transportHandler.initialize(this.sockJsConfig);
PollingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); PollingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
@ -113,7 +114,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
public void handleRequestXhrStreaming() throws Exception { public void handleRequestXhrStreaming() throws Exception {
XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler(); XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler();
transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); transportHandler.initialize(this.sockJsConfig);
AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); AbstractSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
@ -127,7 +128,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
public void htmlFileTransport() throws Exception { public void htmlFileTransport() throws Exception {
HtmlFileTransportHandler transportHandler = new HtmlFileTransportHandler(); HtmlFileTransportHandler transportHandler = new HtmlFileTransportHandler();
transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); transportHandler.initialize(this.sockJsConfig);
StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
@ -149,7 +150,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
public void eventSourceTransport() throws Exception { public void eventSourceTransport() throws Exception {
EventSourceTransportHandler transportHandler = new EventSourceTransportHandler(); EventSourceTransportHandler transportHandler = new EventSourceTransportHandler();
transportHandler.setSockJsServiceConfiguration(this.sockJsConfig); transportHandler.initialize(this.sockJsConfig);
StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null); StreamingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session); transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
@ -167,7 +168,7 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
SockJsFrame frame = SockJsFrame.openFrame(); SockJsFrame frame = SockJsFrame.openFrame();
FrameFormat format = new XhrPollingTransportHandler().getFrameFormat(this.request); SockJsFrameFormat format = new XhrPollingTransportHandler().getFrameFormat(this.request);
SockJsFrame formatted = format.format(frame); SockJsFrame formatted = format.format(frame);
assertEquals(frame.getContent() + "\n", formatted.getContent()); assertEquals(frame.getContent() + "\n", formatted.getContent());

View File

@ -21,6 +21,7 @@ import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest; 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.mock.web.test.MockHttpServletResponse;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat; import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat; 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 org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSessionTests.TestAbstractHttpSockJsSession;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -52,7 +54,7 @@ public class AbstractHttpSockJsSessionTests extends BaseAbstractSockJsSessionTes
protected MockHttpServletResponse servletResponse; protected MockHttpServletResponse servletResponse;
private FrameFormat frameFormat; private SockJsFrameFormat frameFormat;
@Before @Before
@ -60,7 +62,7 @@ public class AbstractHttpSockJsSessionTests extends BaseAbstractSockJsSessionTes
super.setUp(); super.setUp();
this.frameFormat = new DefaultFrameFormat("%s"); this.frameFormat = new DefaultSockJsFrameFormat("%s");
this.servletResponse = new MockHttpServletResponse(); this.servletResponse = new MockHttpServletResponse();
this.response = new ServletServerHttpResponse(this.servletResponse); this.response = new ServletServerHttpResponse(this.servletResponse);

View File

@ -28,7 +28,7 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException; import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; 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 org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator;
import static org.junit.Assert.*; import static org.junit.Assert.*;

View File

@ -18,8 +18,9 @@ package org.springframework.web.socket.sockjs.transport.session;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.sockjs.support.frame.Jackson2SockJsMessageCodec; import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec; import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec;
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
/** /**
* @author Rossen Stoyanchev * @author Rossen Stoyanchev

View File

@ -23,8 +23,9 @@ import java.util.Map;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler; 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.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame; import org.springframework.web.socket.sockjs.frame.SockJsFrame;
/** /**
* @author Rossen Stoyanchev * @author Rossen Stoyanchev

View File

@ -28,7 +28,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketExtension; import org.springframework.web.socket.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler; 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 * @author Rossen Stoyanchev

View File

@ -28,6 +28,7 @@ import org.junit.Test;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler; 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.sockjs.transport.session.WebSocketServerSockJsSessionTests.TestWebSocketServerSockJsSession;
import org.springframework.web.socket.handler.TestWebSocketSession; import org.springframework.web.socket.handler.TestWebSocketSession;