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:
parent
fcecd0328a
commit
1f9b833c4d
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
|
@ -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") + "'";
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.*;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.*;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue