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;
/**
* A WebSocket session abstraction. Allows sending messages over a WebSocket connection
* and closing it.
* A WebSocket session abstraction. Allows sending messages over a WebSocket
* connection and closing it.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface WebSocketSession {
/**
* Return a unique session identifier.
*/

View File

@ -33,9 +33,8 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.socket.config.WebSocketNamespaceUtils;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
/**
* A {@link BeanDefinitionParser} that provides the configuration for the
@ -129,7 +128,6 @@ class HandlersBeanDefinitionParser implements BeanDefinitionParser {
@Override
public ManagedMap<String, Object> createMappings(Element mappingElement, ParserContext parserContext) {
ManagedMap<String, Object> urlMap = new ManagedMap<String, Object>();
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.SubProtocolWebSocketHandler;
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;
import java.util.Collections;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
@ -30,6 +27,7 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.xml.DomUtils;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
/**
@ -63,54 +61,36 @@ class WebSocketNamespaceUtils {
Element sockJsElement = DomUtils.getChildElementByTagName(element, "sockjs");
if (sockJsElement != null) {
ConstructorArgumentValues cavs = new ConstructorArgumentValues();
RootBeanDefinition sockJsServiceDef = new RootBeanDefinition(DefaultSockJsService.class);
sockJsServiceDef.setSource(source);
Object scheduler;
String customTaskSchedulerName = sockJsElement.getAttribute("scheduler");
if (!customTaskSchedulerName.isEmpty()) {
cavs.addIndexedArgumentValue(0, new RuntimeBeanReference(customTaskSchedulerName));
scheduler = new RuntimeBeanReference(customTaskSchedulerName);
}
else {
cavs.addIndexedArgumentValue(0, registerSockJsTaskScheduler(sockJsSchedulerName, parserContext, source));
scheduler = registerSockJsTaskScheduler(sockJsSchedulerName, parserContext, source);
}
sockJsServiceDef.getConstructorArgumentValues().addIndexedArgumentValue(0, scheduler);
Element transportHandlersElement = DomUtils.getChildElementByTagName(sockJsElement, "transport-handlers");
boolean registerDefaults = true;
if (transportHandlersElement != null) {
String registerDefaultsAttribute = transportHandlersElement.getAttribute("register-defaults");
registerDefaults = !registerDefaultsAttribute.equals("false");
if (registerDefaultsAttribute.equals("false")) {
sockJsServiceDef.setBeanClass(TransportHandlingSockJsService.class);
}
ManagedList<?> transportHandlersList = parseBeanSubElements(transportHandlersElement, parserContext);
sockJsServiceDef.getConstructorArgumentValues().addIndexedArgumentValue(1, transportHandlersList);
}
ManagedList<?> transportHandlersList = parseBeanSubElements(transportHandlersElement, parserContext);
if (registerDefaults) {
cavs.addIndexedArgumentValue(1, Collections.emptyList());
if (transportHandlersList.isEmpty()) {
cavs.addIndexedArgumentValue(2, new ConstructorArgumentValues.ValueHolder(null));
}
else {
cavs.addIndexedArgumentValue(2, transportHandlersList);
}
}
else {
if (transportHandlersList.isEmpty()) {
cavs.addIndexedArgumentValue(1, new ConstructorArgumentValues.ValueHolder(null));
}
else {
cavs.addIndexedArgumentValue(1, transportHandlersList);
}
cavs.addIndexedArgumentValue(2, new ConstructorArgumentValues.ValueHolder(null));
}
RootBeanDefinition sockJsServiceDef = new RootBeanDefinition(DefaultSockJsService.class, cavs, null);
sockJsServiceDef.setSource(source);
String attrValue = sockJsElement.getAttribute("name");
if (!attrValue.isEmpty()) {
sockJsServiceDef.getPropertyValues().add("name", attrValue);
}
attrValue = sockJsElement.getAttribute("websocket-enabled");
if (!attrValue.isEmpty()) {
sockJsServiceDef.getPropertyValues().add("webSocketsEnabled", Boolean.valueOf(attrValue));
sockJsServiceDef.getPropertyValues().add("webSocketEnabled", Boolean.valueOf(attrValue));
}
attrValue = sockJsElement.getAttribute("session-cookie-needed");
if (!attrValue.isEmpty()) {

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.HandshakeInterceptor;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsService;
/**

View File

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

View File

@ -24,7 +24,7 @@ import org.springframework.web.HttpRequestHandler;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;

View File

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

View File

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

View File

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

View File

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

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.
*/
package org.springframework.web.socket.sockjs.support.frame;
package org.springframework.web.socket.sockjs.frame;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.util.Assert;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.Assert;
/**
* A Jackson 2 codec for encoding and decoding SockJS messages.
* A Jackson 2.x codec for encoding and decoding SockJS messages.
*
* @author Rossen Stoyanchev
* @since 4.0
@ -44,6 +44,7 @@ public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
this.objectMapper = objectMapper;
}
@Override
public String[] decode(String content) throws IOException {
return this.objectMapper.readValue(content, String[].class);

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.socket.sockjs.support.frame;
package org.springframework.web.socket.sockjs.frame;
import java.nio.charset.Charset;
@ -28,6 +28,8 @@ import org.springframework.util.Assert;
*/
public class SockJsFrame {
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final SockJsFrame openFrame = new SockJsFrame("o");
private static final SockJsFrame heartbeatFrame = new SockJsFrame("h");
@ -37,15 +39,6 @@ public class SockJsFrame {
private static final SockJsFrame closeAnotherConnectionOpenFrame = closeFrame(2010, "Another connection still open");
private final String content;
private SockJsFrame(String content) {
Assert.notNull("Content must not be null");
this.content = content;
}
public static SockJsFrame openFrame() {
return openFrame;
}
@ -72,26 +65,21 @@ public class SockJsFrame {
}
private final String content;
public SockJsFrame(String content) {
Assert.notNull("Content must not be null");
this.content = content;
}
public String getContent() {
return this.content;
}
public byte[] getContentBytes() {
return this.content.getBytes(Charset.forName("UTF-8"));
}
@Override
public String toString() {
String result = this.content;
if (result.length() > 80) {
result = result.substring(0, 80) + "...(truncated)";
}
return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'";
}
@Override
public int hashCode() {
return this.content.hashCode();
return this.content.getBytes(UTF8_CHARSET);
}
@Override
@ -105,34 +93,18 @@ public class SockJsFrame {
return this.content.equals(((SockJsFrame) other).content);
}
public interface FrameFormat {
SockJsFrame format(SockJsFrame frame);
@Override
public int hashCode() {
return this.content.hashCode();
}
public static class DefaultFrameFormat implements FrameFormat {
private final String format;
public DefaultFrameFormat(String format) {
Assert.notNull(format, "format must not be null");
this.format = format;
}
/**
* @param frame the SockJs frame.
* @return new SockJsFrame instance with the formatted content
*/
@Override
public SockJsFrame format(SockJsFrame frame) {
String content = String.format(this.format, preProcessContent(frame.getContent()));
return new SockJsFrame(content);
}
protected String preProcessContent(String content) {
return content;
@Override
public String toString() {
String result = this.content;
if (result.length() > 80) {
result = result.substring(0, 80) + "...(truncated)";
}
return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'";
}
}

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.
*/
package org.springframework.web.socket.sockjs.support.frame;
package org.springframework.web.socket.sockjs.frame;
import java.io.IOException;
import java.io.InputStream;

View File

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

View File

@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
@ -53,10 +54,16 @@ import org.springframework.web.socket.sockjs.SockJsService;
*/
public abstract class AbstractSockJsService implements SockJsService {
protected final Log logger = LogFactory.getLog(getClass());
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final long ONE_YEAR = TimeUnit.DAYS.toSeconds(365);
private static final Random random = new Random();
protected final Log logger = LogFactory.getLog(getClass());
private final TaskScheduler taskScheduler;
private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this);
@ -72,13 +79,11 @@ public abstract class AbstractSockJsService implements SockJsService {
private int httpMessageCacheSize = 100;
private boolean webSocketsEnabled = true;
private final TaskScheduler taskScheduler;
private boolean webSocketEnabled = true;
public AbstractSockJsService(TaskScheduler scheduler) {
Assert.notNull(scheduler, "scheduler must not be null");
Assert.notNull(scheduler, "TaskScheduler must not be null");
this.taskScheduler = scheduler;
}
@ -223,22 +228,23 @@ public abstract class AbstractSockJsService implements SockJsService {
}
/**
* Some load balancers don't support websockets. This option can be used to
* Some load balancers don't support WebSocket. This option can be used to
* disable the WebSocket transport on the server side.
* <p>The default value is "true".
*/
public void setWebSocketsEnabled(boolean webSocketsEnabled) {
this.webSocketsEnabled = webSocketsEnabled;
public void setWebSocketEnabled(boolean webSocketEnabled) {
this.webSocketEnabled = webSocketEnabled;
}
/**
* Whether WebSocket transport is enabled.
* @see #setWebSocketsEnabled(boolean)
* @see #setWebSocketEnabled(boolean)
*/
public boolean isWebSocketEnabled() {
return this.webSocketsEnabled;
return this.webSocketEnabled;
}
/**
* {@inheritDoc}
* <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 {
if (sockJsPath == null) {
logger.warn("No SockJS path provided, URI=\"" + request.getURI());
if (logger.isWarnEnabled()) {
logger.warn("No SockJS path provided, URI=\"" + request.getURI());
}
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
}
logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]");
if (logger.isDebugEnabled()) {
logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]");
}
try {
request.getHeaders();
}
catch (InvalidMediaTypeException ex) {
logger.warn("Invalid media type ignored: " + ex.getMediaType());
if (logger.isWarnEnabled()) {
logger.warn("Invalid media type ignored: " + ex.getMediaType());
}
}
try {
@ -275,12 +287,16 @@ public abstract class AbstractSockJsService implements SockJsService {
this.iframeHandler.handle(request, response);
}
else if (sockJsPath.equals("/websocket")) {
handleRawWebSocketRequest(request, response, wsHandler);
if (isWebSocketEnabled()) {
handleRawWebSocketRequest(request, response, wsHandler);
}
}
else {
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
if (pathSegments.length != 3) {
logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\"");
if (logger.isWarnEnabled()) {
logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\"");
}
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
}
@ -301,27 +317,7 @@ public abstract class AbstractSockJsService implements SockJsService {
}
}
/**
* Validate whether the given transport String extracted from the URL is a valid
* SockJS transport type (regardless of whether a transport handler is configured).
*/
protected abstract boolean isValidTransportType(String transportType);
/**
* Handle request for raw WebSocket communication, i.e. without any SockJS message framing.
*/
protected abstract void handleRawWebSocketRequest(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException;
/**
* Handle a SockJS session URL (i.e. transport-specific request).
*/
protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler webSocketHandler, String sessionId, String transport) throws SockJsException;
protected boolean validateRequest(String serverId, String sessionId, String transport) {
if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) {
logger.warn("Empty server, session, or transport value");
return false;
@ -341,8 +337,21 @@ public abstract class AbstractSockJsService implements SockJsService {
return true;
}
protected void addCorsHeaders(ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) {
/**
* Handle request for raw WebSocket communication, i.e. without any SockJS message framing.
*/
protected abstract void handleRawWebSocketRequest(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException;
/**
* Handle a SockJS session URL (i.e. transport-specific request).
*/
protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler webSocketHandler, String sessionId, String transport) throws SockJsException;
protected void addCorsHeaders(ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) {
String origin = request.getHeaders().getFirst("origin");
origin = ((origin == null) || origin.equals("null")) ? "*" : origin;
@ -371,10 +380,10 @@ public abstract class AbstractSockJsService implements SockJsService {
response.getHeaders().setCacheControl("no-store, no-cache, must-revalidate, max-age=0");
}
protected void sendMethodNotAllowed(ServerHttpResponse response, List<HttpMethod> httpMethods) {
protected void sendMethodNotAllowed(ServerHttpResponse response, HttpMethod... httpMethods) {
logger.debug("Sending Method Not Allowed (405)");
response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
response.getHeaders().setAllow(new HashSet<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 static final String INFO_CONTENT =
@ -393,26 +400,20 @@ public abstract class AbstractSockJsService implements SockJsService {
@Override
public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (HttpMethod.GET.equals(request.getMethod())) {
response.getHeaders().setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
response.getHeaders().setContentType(new MediaType("application", "json", UTF8_CHARSET));
addCorsHeaders(request, response);
addNoCacheHeaders(response);
String content = String.format(INFO_CONTENT, random.nextInt(), isSessionCookieNeeded(), isWebSocketEnabled());
response.getBody().write(content.getBytes());
}
else if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatusCode(HttpStatus.NO_CONTENT);
addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET);
addCacheHeaders(response);
}
else {
sendMethodNotAllowed(response, Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET));
sendMethodNotAllowed(response, HttpMethod.OPTIONS, HttpMethod.GET);
}
}
};
@ -439,14 +440,13 @@ public abstract class AbstractSockJsService implements SockJsService {
@Override
public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (!HttpMethod.GET.equals(request.getMethod())) {
sendMethodNotAllowed(response, Arrays.asList(HttpMethod.GET));
sendMethodNotAllowed(response, HttpMethod.GET);
return;
}
String content = String.format(IFRAME_CONTENT, getSockJsClientLibraryUrl());
byte[] contentBytes = content.getBytes(Charset.forName("UTF-8"));
byte[] contentBytes = content.getBytes(UTF8_CHARSET);
StringBuilder builder = new StringBuilder("\"0");
DigestUtils.appendMd5DigestAsHex(contentBytes, builder);
builder.append('"');
@ -458,7 +458,7 @@ public abstract class AbstractSockJsService implements SockJsService {
return;
}
response.getHeaders().setContentType(new MediaType("text", "html", Charset.forName("UTF-8")));
response.getHeaders().setContentType(new MediaType("text", "html", UTF8_CHARSET));
response.getHeaders().setContentLength(contentBytes.length);
addCacheHeaders(response);

View File

@ -14,10 +14,9 @@
* limitations under the License.
*/
package org.springframework.web.socket.sockjs;
package org.springframework.web.socket.sockjs.support;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -32,6 +31,8 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator;
import org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator;
import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsService;
/**
* An {@link HttpRequestHandler} that allows mapping a {@link SockJsService} to requests
@ -44,19 +45,20 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler {
private final SockJsService sockJsService;
private final WebSocketHandler wsHandler;
private final WebSocketHandler webSocketHandler;
/**
* Create a new {@link SockJsHttpRequestHandler}.
* Create a new SockJsHttpRequestHandler.
* @param sockJsService the SockJS service
* @param wsHandler the websocket handler
* @param webSocketHandler the websocket handler
*/
public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler wsHandler) {
public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler webSocketHandler) {
Assert.notNull(sockJsService, "sockJsService must not be null");
Assert.notNull(wsHandler, "webSocketHandler must not be null");
Assert.notNull(webSocketHandler, "webSocketHandler must not be null");
this.sockJsService = sockJsService;
this.wsHandler = new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(wsHandler));
this.webSocketHandler =
new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(webSocketHandler));
}
@ -71,9 +73,10 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler {
* Return the {@link WebSocketHandler}.
*/
public WebSocketHandler getWebSocketHandler() {
return this.wsHandler;
return this.webSocketHandler;
}
@Override
public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
throws ServletException, IOException {
@ -82,10 +85,10 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler {
ServerHttpResponse response = new ServletServerHttpResponse(servletResponse);
try {
this.sockJsService.handleRequest(request, response, getSockJsPath(servletRequest), this.wsHandler);
this.sockJsService.handleRequest(request, response, getSockJsPath(servletRequest), this.webSocketHandler);
}
catch (Throwable t) {
throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), t);
catch (Throwable ex) {
throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), ex);
}
}

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.
*/
package org.springframework.web.socket.sockjs.transport.session;
package org.springframework.web.socket.sockjs.transport;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.support.AbstractSockJsService;
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
/**
* Provides transport handling code with access to the {@link SockJsService} configuration
@ -30,6 +29,11 @@ import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
*/
public interface SockJsServiceConfig {
/**
* A scheduler instance to use for scheduling heart-beat messages.
*/
TaskScheduler getTaskScheduler();
/**
* Streaming transports save responses on the client side and don't free
* memory used by delivered messages. Such transports need to recycle the
@ -51,9 +55,16 @@ public interface SockJsServiceConfig {
long getHeartbeatTime();
/**
* A scheduler instance to use for scheduling heart-beat messages.
* The number of server-to-client messages that a session can cache while waiting for
* the next HTTP polling request from the client. All HTTP transports use this
* property since even streaming transports recycle HTTP requests periodically.
* <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.
@ -61,15 +72,4 @@ public interface SockJsServiceConfig {
*/
SockJsMessageCodec getMessageCodec();
/**
* The number of server-to-client messages that a session can cache while waiting for
* the next HTTP polling request from the client. All HTTP transports use this
* property since even streaming transports recycle HTTP requests periodically.
* <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.
*/
package org.springframework.web.socket.sockjs.transport.handler;
package org.springframework.web.socket.sockjs.transport;
import java.util.Map;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
/**
* A factory for creating a SockJS session. {@link TransportHandler}s typically also serve
@ -34,10 +32,10 @@ public interface SockJsSessionFactory {
/**
* Create a new SockJS session.
* @param sessionId the ID of the session
* @param wsHandler the underlying {@link WebSocketHandler}
* @param handler the underlying {@link WebSocketHandler}
* @param attributes handshake request specific attributes
* @return a new session, never {@code null}
*/
AbstractSockJsSession createSession(String sessionId, WebSocketHandler wsHandler, Map<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.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsService;
@ -27,28 +26,34 @@ import org.springframework.web.socket.sockjs.SockJsService;
* Handle a SockJS session URL, i.e. transport-specific request.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0
*/
public interface TransportHandler {
/**
* @return the transport type supported by this handler
* Initialize this handler with the given configuration.
* @param serviceConfig the configuration as defined by the containing
* {@link org.springframework.web.socket.sockjs.SockJsService}
*/
void initialize(SockJsServiceConfig serviceConfig);
/**
* Return the transport type supported by this handler.
*/
TransportType getTransportType();
/**
* Handle the given request and delegate messages to the provided
* {@link WebSocketHandler}.
*
* @param request the current request
* @param response the current response
* @param handler the target WebSocketHandler, never {@code null}
* @param session the SockJS session, never {@code null}
*
* @throws SockJsException raised when request processing fails as explained in
* {@link SockJsService}
* @param handler the target WebSocketHandler (never {@code null})
* @param session the SockJS session (never {@code null})
* @throws SockJsException raised when request processing fails as
* explained in {@link SockJsService}
*/
void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, WebSocketSession session) throws SockJsException;
WebSocketHandler handler, SockJsSession session) throws SockJsException;
}

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

View File

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

View File

@ -16,27 +16,34 @@
package org.springframework.web.socket.sockjs.transport.handler;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
/**
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class TransportHandlerSupport {
public abstract class AbstractTransportHandler implements TransportHandler {
protected static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
protected final Log logger = LogFactory.getLog(this.getClass());
private SockJsServiceConfig sockJsServiceConfig;
private SockJsServiceConfig serviceConfig;
public void setSockJsServiceConfiguration(SockJsServiceConfig sockJsConfig) {
this.sockJsServiceConfig = sockJsConfig;
@Override
public void initialize(SockJsServiceConfig serviceConfig) {
this.serviceConfig = serviceConfig;
}
public SockJsServiceConfig getSockJsServiceConfig() {
return this.sockJsServiceConfig;
public SockJsServiceConfig getServiceConfig() {
return this.serviceConfig;
}
}

View File

@ -16,397 +16,87 @@
package org.springframework.web.socket.sockjs.transport.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.server.HandshakeFailureException;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.web.socket.server.support.HandshakeInterceptorChain;
import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.support.AbstractSockJsService;
import org.springframework.web.socket.sockjs.support.frame.Jackson2SockJsMessageCodec;
import org.springframework.web.socket.sockjs.support.frame.JacksonSockJsMessageCodec;
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
/**
* A default implementation of {@link SockJsService} adding support for transport handling
* and session management. See {@link AbstractSockJsService} base class for important
* details on request mapping.
* A default implementation of {@link org.springframework.web.socket.sockjs.SockJsService}
* with all default {@link TransportHandler} implementations pre-registered.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 4.0
*/
public class DefaultSockJsService extends AbstractSockJsService {
private static final boolean jackson2Present = ClassUtils.isPresent(
"com.fasterxml.jackson.databind.ObjectMapper", DefaultSockJsService.class.getClassLoader());
private static final boolean jacksonPresent = ClassUtils.isPresent(
"org.codehaus.jackson.map.ObjectMapper", DefaultSockJsService.class.getClassLoader());
private final Map<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;
public class DefaultSockJsService extends TransportHandlingSockJsService {
/**
* Create an instance with default {@link TransportHandler transport handler} types.
* @param taskScheduler a task scheduler for heart-beat messages and removing
* Create a DefaultSockJsService with default {@link TransportHandler handler} types.
* @param scheduler a task scheduler for heart-beat messages and removing
* timed-out sessions; the provided TaskScheduler should be declared as a
* Spring bean to ensure it is initialized at start up and shut down when the
* application stops.
*/
public DefaultSockJsService(TaskScheduler taskScheduler) {
this(taskScheduler, null);
public DefaultSockJsService(TaskScheduler scheduler) {
this(scheduler, getDefaultTransportHandlers(null));
}
/**
* Create an instance by overriding or replacing completely the default
* {@link TransportHandler transport handler} types.
* @param taskScheduler a task scheduler for heart-beat messages and removing
* timed-out sessions; the provided TaskScheduler should be declared as a
* Spring bean to ensure it is initialized at start up and shut down when the
* application stops.
* @param transportHandlers the transport handlers to use (replaces the default ones);
* can be {@code null} if you don't want to install the default ones.
* @param transportHandlerOverrides zero or more overrides to the default transport
* handler types.
* Create a DefaultSockJsService with overridden {@link TransportHandler handler} types
* replacing the corresponding default handler implementation.
* @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions;
* the provided TaskScheduler should be declared as a Spring bean to ensure it gets
* initialized at start-up and shuts down when the application stops
* @param handlerOverrides zero or more overrides to the default transport handler types
*/
public DefaultSockJsService(TaskScheduler taskScheduler, Collection<TransportHandler> transportHandlers,
TransportHandler... transportHandlerOverrides) {
super(taskScheduler);
initMessageCodec();
if (CollectionUtils.isEmpty(transportHandlers)) {
addTransportHandlers(getDefaultTransportHandlers());
}
else {
addTransportHandlers(transportHandlers);
}
if (!ObjectUtils.isEmpty(transportHandlerOverrides)) {
addTransportHandlers(Arrays.asList(transportHandlerOverrides));
}
if (this.transportHandlers.isEmpty()) {
logger.warn("No transport handlers");
}
public DefaultSockJsService(TaskScheduler scheduler, TransportHandler... handlerOverrides) {
this(scheduler, Arrays.asList(handlerOverrides));
}
private void initMessageCodec() {
if (jackson2Present) {
this.messageCodec = new Jackson2SockJsMessageCodec();
}
else if (jacksonPresent) {
this.messageCodec = new JacksonSockJsMessageCodec();
}
/**
* Create a DefaultSockJsService with overridden {@link TransportHandler handler} types
* replacing the corresponding default handler implementation.
* @param scheduler a task scheduler for heart-beat messages and removing timed-out sessions;
* the provided TaskScheduler should be declared as a Spring bean to ensure it gets
* initialized at start-up and shuts down when the application stops
* @param handlerOverrides zero or more overrides to the default transport handler types
*/
public DefaultSockJsService(TaskScheduler scheduler, Collection<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 XhrReceivingTransportHandler());
result.add(new XhrStreamingTransportHandler());
result.add(new JsonpPollingTransportHandler());
result.add(new JsonpReceivingTransportHandler());
result.add(new XhrStreamingTransportHandler());
result.add(new EventSourceTransportHandler());
result.add(new HtmlFileTransportHandler());
try {
result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler()));
}
catch (Exception ex) {
Log logger = LogFactory.getLog(DefaultSockJsService.class);
if (logger.isWarnEnabled()) {
logger.warn("Failed to create default WebSocketTransportHandler", ex);
}
}
if (overrides != null) {
result.addAll(overrides);
}
return result;
}
protected void addTransportHandlers(Collection<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;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
/**
@ -38,7 +37,6 @@ import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSe
*/
public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler {
@Override
public TransportType getTransportType() {
return TransportType.EVENT_SOURCE;
@ -46,19 +44,19 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan
@Override
protected MediaType getContentType() {
return new MediaType("text", "event-stream", Charset.forName("UTF-8"));
return new MediaType("text", "event-stream", UTF8_CHARSET);
}
@Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler,
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<String, Object> attributes) {
return new EventSourceStreamingSockJsSession(sessionId, getSockJsServiceConfig(), wsHandler, attributes);
return new EventSourceStreamingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
}
@Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultFrameFormat("data: %s\r\n\r\n");
protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultSockJsFrameFormat("data: %s\r\n\r\n");
}

View File

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

View File

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

View File

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

View File

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

View File

@ -16,14 +16,13 @@
package org.springframework.web.socket.sockjs.transport.handler;
import java.nio.charset.Charset;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
@ -43,19 +42,19 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand
@Override
protected MediaType getContentType() {
return new MediaType("application", "javascript", Charset.forName("UTF-8"));
return new MediaType("application", "javascript", UTF8_CHARSET);
}
@Override
protected FrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultFrameFormat("%s\n");
protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
return new DefaultSockJsFrameFormat("%s\n");
}
@Override
public PollingSockJsSession createSession(String sessionId, WebSocketHandler wsHandler,
public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler,
Map<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.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransportHandler {
@Override
public TransportType getTransportType() {
return TransportType.XHR_SEND;
@ -38,7 +38,7 @@ public class XhrReceivingTransportHandler extends AbstractHttpReceivingTransport
@Override
protected String[] readMessages(ServerHttpRequest request) throws IOException {
return getSockJsServiceConfig().getMessageCodec().decodeInputStream(request.getBody());
return getServiceConfig().getMessageCodec().decodeInputStream(request.getBody());
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

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

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.SubProtocolWebSocketHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import static org.junit.Assert.*;

View File

@ -31,7 +31,7 @@ import org.springframework.web.HttpRequestHandler;
import org.springframework.web.socket.messaging.SubProtocolWebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
import org.springframework.web.socket.sockjs.transport.TransportType;
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator;
import static org.junit.Assert.*;

View File

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

View File

@ -23,8 +23,9 @@ import java.util.Map;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
/**
* @author Rossen Stoyanchev

View File

@ -28,7 +28,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
/**
* @author Rossen Stoyanchev

View File

@ -28,6 +28,7 @@ import org.junit.Test;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.transport.SockJsServiceConfig;
import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSessionTests.TestWebSocketServerSockJsSession;
import org.springframework.web.socket.handler.TestWebSocketSession;