Refactor SockJS code

- configure SockJS handler by type (as well as by instance)
- add method to obtain SockJS handler instance via SockJsConfiguration
- detect presense of jsr-356 and use it if available
This commit is contained in:
Rossen Stoyanchev
2013-04-09 09:43:20 -04:00
parent 4ad6091510
commit 6bd6311214
41 changed files with 727 additions and 383 deletions

View File

@@ -33,7 +33,7 @@ public abstract class SockJsSessionSupport implements SockJsSession {
private final String sessionId;
private final SockJsHandler delegate;
private final SockJsHandler sockJsHandler;
private State state = State.NEW;
@@ -45,13 +45,13 @@ public abstract class SockJsSessionSupport implements SockJsSession {
/**
*
* @param sessionId
* @param delegate the recipient of SockJS messages
* @param sockJsHandler the recipient of SockJS messages
*/
public SockJsSessionSupport(String sessionId, SockJsHandler delegate) {
public SockJsSessionSupport(String sessionId, SockJsHandler sockJsHandler) {
Assert.notNull(sessionId, "sessionId is required");
Assert.notNull(delegate, "SockJsHandler is required");
Assert.notNull(sockJsHandler, "SockJsHandler is required");
this.sessionId = sessionId;
this.delegate = delegate;
this.sockJsHandler = sockJsHandler;
}
public String getId() {
@@ -59,7 +59,7 @@ public abstract class SockJsSessionSupport implements SockJsSession {
}
public SockJsHandler getSockJsHandler() {
return this.delegate;
return this.sockJsHandler;
}
public boolean isNew() {
@@ -105,17 +105,22 @@ public abstract class SockJsSessionSupport implements SockJsSession {
public void connectionInitialized() throws Exception {
this.state = State.OPEN;
this.delegate.newSession(this);
this.sockJsHandler.newSession(this);
}
public void delegateMessages(String... messages) throws Exception {
for (String message : messages) {
this.delegate.handleMessage(this, message);
this.sockJsHandler.handleMessage(this, message);
}
}
public void delegateException(Throwable ex) {
this.sockJsHandler.handleException(this, ex);
}
public void close() {
this.state = State.CLOSED;
this.sockJsHandler.sessionClosed(this);
}
public String toString() {

View File

@@ -40,13 +40,17 @@ public abstract class AbstractServerSession extends SockJsSessionSupport {
private ScheduledFuture<?> heartbeatTask;
public AbstractServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) {
super(sessionId, delegate);
Assert.notNull(sockJsConfig, "sockJsConfig is required");
public AbstractServerSession(String sessionId, SockJsConfiguration sockJsConfig) {
super(sessionId, getSockJsHandler(sockJsConfig));
this.sockJsConfig = sockJsConfig;
}
public SockJsConfiguration getSockJsConfig() {
private static SockJsHandler getSockJsHandler(SockJsConfiguration sockJsConfig) {
Assert.notNull(sockJsConfig, "sockJsConfig is required");
return sockJsConfig.getSockJsHandler();
}
protected SockJsConfiguration getSockJsConfig() {
return this.sockJsConfig;
}
@@ -61,18 +65,15 @@ public abstract class AbstractServerSession extends SockJsSessionSupport {
if (!isClosed()) {
logger.debug("Closing session");
// set the status
super.close();
if (isActive()) {
// deliver messages "in flight" before sending close frame
writeFrame(SockJsFrame.closeFrameGoAway());
}
super.close();
cancelHeartbeat();
closeInternal();
getSockJsHandler().sessionClosed(this);
}
}
@@ -90,12 +91,12 @@ public abstract class AbstractServerSession extends SockJsSessionSupport {
writeFrameInternal(frame);
}
catch (EOFException ex) {
logger.warn("Failed to send message due to client disconnect. Terminating connection abruptly");
logger.warn("Client went away. Terminating connection abruptly");
deactivate();
close();
}
catch (Throwable t) {
logger.error("Failed to send message. Terminating connection abruptly", t);
logger.warn("Failed to send message. Terminating connection abruptly: " + t.getMessage());
deactivate();
close();
}

View File

@@ -32,13 +32,11 @@ import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.sockjs.TransportType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.websocket.server.HandshakeRequestHandler;
/**
@@ -70,8 +68,6 @@ public abstract class AbstractSockJsService implements SockJsConfiguration {
private boolean webSocketsEnabled = true;
private HandshakeRequestHandler handshakeRequestHandler;
/**
* Class constructor...
@@ -199,16 +195,6 @@ public abstract class AbstractSockJsService implements SockJsConfiguration {
return this.webSocketsEnabled;
}
/**
* SockJS exposes an entry point at "/websocket" for raw WebSocket
* communication without additional custom framing, e.g. no open frame, no
* heartbeats, only raw WebSocket protocol. This property allows setting a
* handler for requests for raw WebSocket communication.
*/
public void setWebsocketHandler(HandshakeRequestHandler handshakeRequestHandler) {
this.handshakeRequestHandler = handshakeRequestHandler;
}
/**
* TODO
@@ -245,8 +231,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration {
return;
}
else if (sockJsPath.equals("/websocket")) {
Assert.notNull(this.handshakeRequestHandler, "No handler for raw Websockets configured");
this.handshakeRequestHandler.doHandshake(request, response);
handleRawWebSocket(request, response);
return;
}
@@ -270,6 +255,9 @@ public abstract class AbstractSockJsService implements SockJsConfiguration {
}
protected abstract void handleRawWebSocket(ServerHttpRequest request, ServerHttpResponse response)
throws Exception;
protected boolean validateRequest(String serverId, String sessionId, String transport) {
if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) {

View File

@@ -17,6 +17,7 @@ package org.springframework.sockjs.server;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.sockjs.SockJsHandler;
/**
@@ -66,4 +67,11 @@ public interface SockJsConfiguration {
*/
public TaskScheduler getHeartbeatScheduler();
/**
* Provides access to the {@link SockJsHandler} that will handle the request. This
* method should be called once per SockJS session. It may return the same or a
* different instance every time it is called.
*/
SockJsHandler getSockJsHandler();
}

View File

@@ -1,96 +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.sockjs.server;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* An implementation of {@link WebSocketHandler} supporting the SockJS protocol.
* Methods merely delegate to a {@link StandardWebSocketServerSession}.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class SockJsWebSocketHandler implements WebSocketHandler {
private static final Log logger = LogFactory.getLog(SockJsWebSocketHandler.class);
private final StandardWebSocketServerSession sockJsSession;
// TODO: the JSON library used must be configurable
private final ObjectMapper objectMapper = new ObjectMapper();
public SockJsWebSocketHandler(StandardWebSocketServerSession sockJsSession) {
this.sockJsSession = sockJsSession;
}
@Override
public void newSession(WebSocketSession webSocketSession) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("New session: " + webSocketSession);
}
this.sockJsSession.setWebSocketSession(webSocketSession);
}
@Override
public void handleTextMessage(WebSocketSession session, String message) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Received payload " + message + " for " + sockJsSession);
}
if (StringUtils.isEmpty(message)) {
logger.trace("Ignoring empty payload");
return;
}
try {
String[] messages = this.objectMapper.readValue(message, String[].class);
this.sockJsSession.delegateMessages(messages);
}
catch (IOException e) {
logger.error("Broken data received. Terminating WebSocket connection abruptly", e);
session.close();
}
}
@Override
public void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception {
// should not happen
throw new UnsupportedOperationException();
}
@Override
public void handleException(WebSocketSession session, Throwable exception) {
exception.printStackTrace();
}
@Override
public void sessionClosed(WebSocketSession session, int statusCode, String reason) throws Exception {
logger.debug("WebSocket connection closed for " + this.sockJsSession);
this.sockJsSession.close();
}
}

View File

@@ -1,80 +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.sockjs.server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.websocket.WebSocketSession;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class StandardWebSocketServerSession extends AbstractServerSession {
private static Log logger = LogFactory.getLog(StandardWebSocketServerSession.class);
private WebSocketSession webSocketSession;
public StandardWebSocketServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) {
super(sessionId, delegate, sockJsConfig);
}
public void setWebSocketSession(WebSocketSession webSocketSession) throws Exception {
this.webSocketSession = webSocketSession;
webSocketSession.sendText(SockJsFrame.openFrame().getContent());
scheduleHeartbeat();
connectionInitialized();
}
@Override
public boolean isActive() {
return ((this.webSocketSession != null) && this.webSocketSession.isOpen());
}
@Override
public void sendMessageInternal(String message) {
cancelHeartbeat();
writeFrame(SockJsFrame.messageFrame(message));
scheduleHeartbeat();
}
@Override
protected void writeFrameInternal(SockJsFrame frame) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Write " + frame);
}
this.webSocketSession.sendText(frame.getContent());
}
@Override
public void closeInternal() {
this.webSocketSession.close();
this.webSocketSession = null;
updateLastActiveTime();
}
@Override
protected void deactivate() {
this.webSocketSession.close();
}
}

View File

@@ -17,9 +17,7 @@ package org.springframework.sockjs.server;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.TransportType;
/**
@@ -31,7 +29,11 @@ public interface TransportHandler {
TransportType getTransportType();
SockJsSessionSupport createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config);
boolean canCreateSession();
SockJsSessionSupport createSession(String sessionId);
boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response);
void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session)
throws Exception;

View File

@@ -23,6 +23,6 @@ package org.springframework.sockjs.server;
*/
public interface TransportHandlerRegistrar {
void registerTransportHandlers(TransportHandlerRegistry registry);
void registerTransportHandlers(TransportHandlerRegistry registry, SockJsConfiguration config);
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.sockjs;
package org.springframework.sockjs.server;
import org.springframework.http.HttpMethod;
@@ -25,7 +25,7 @@ import org.springframework.http.HttpMethod;
*/
public enum TransportType {
WEBSOCKET("websocket", HttpMethod.GET, false /* CORS ? */),
WEBSOCKET("websocket", HttpMethod.GET, false),
XHR("xhr", HttpMethod.POST, true),
XHR_SEND("xhr_send", HttpMethod.POST, true),
@@ -45,10 +45,10 @@ public enum TransportType {
private final boolean corsSupported;
private TransportType(String value, HttpMethod httpMethod, boolean supportsCors) {
private TransportType(String value, HttpMethod httpMethod, boolean corsSupported) {
this.value = value;
this.httpMethod = httpMethod;
this.corsSupported = supportsCors;
this.corsSupported = corsSupported;
}
public String value() {

View File

@@ -20,9 +20,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.http.Cookie;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -32,12 +35,13 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.AbstractSockJsService;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.sockjs.server.TransportHandlerRegistrar;
import org.springframework.sockjs.server.TransportHandlerRegistry;
import org.springframework.sockjs.server.TransportType;
import org.springframework.util.Assert;
import org.springframework.websocket.server.HandshakeHandler;
/**
@@ -46,10 +50,10 @@ import org.springframework.util.Assert;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class DefaultSockJsService extends AbstractSockJsService implements TransportHandlerRegistry, InitializingBean {
private static final AtomicLong webSocketSessionIdSuffix = new AtomicLong();
public class DefaultSockJsService extends AbstractSockJsService
implements TransportHandlerRegistry, BeanFactoryAware, InitializingBean {
private final Class<? extends SockJsHandler> sockJsHandlerClass;
private final SockJsHandler sockJsHandler;
@@ -59,17 +63,24 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans
private final Map<TransportType, TransportHandler> transportHandlers = new HashMap<TransportType, TransportHandler>();
private AutowireCapableBeanFactory beanFactory;
public DefaultSockJsService(String prefix, Class<? extends SockJsHandler> sockJsHandlerClass) {
this(prefix, sockJsHandlerClass, null);
}
/**
* Class constructor...
*
*/
public DefaultSockJsService(String prefix, SockJsHandler sockJsHandler) {
this(prefix, null, sockJsHandler);
}
private DefaultSockJsService(String prefix, Class<? extends SockJsHandler> handlerClass, SockJsHandler handler) {
super(prefix);
Assert.notNull(sockJsHandler, "sockJsHandler is required");
this.sockJsHandler = sockJsHandler;
Assert.isTrue(((handlerClass != null) || (handler != null)), "A sockJsHandler class or instance is required");
this.sockJsHandlerClass = handlerClass;
this.sockJsHandler = handler;
this.sessionTimeoutScheduler = createScheduler("SockJs-sessionTimeout-");
new DefaultTransportHandlerRegistrar().registerTransportHandlers(this);
new DefaultTransportHandlerRegistrar().registerTransportHandlers(this, this);
}
/**
@@ -95,12 +106,34 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans
public void setTransportHandlerRegistrar(TransportHandlerRegistrar registrar) {
Assert.notNull(registrar, "registrar is required");
this.transportHandlers.clear();
registrar.registerTransportHandlers(this);
registrar.registerTransportHandlers(this, this);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof AutowireCapableBeanFactory) {
this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
}
}
@Override
public SockJsHandler getSockJsHandler() {
return (this.sockJsHandlerClass != null) ?
this.beanFactory.createBean(this.sockJsHandlerClass) : this.sockJsHandler;
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.sockJsHandler != null) {
Assert.notNull(this.beanFactory,
"An AutowirecapableBeanFactory is required to initialize SockJS handler instances per request.");
}
if (this.transportHandlers.get(TransportType.WEBSOCKET) == null) {
logger.warn("No WebSocket transport handler was registered");
}
this.sessionTimeoutScheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
try {
@@ -128,6 +161,19 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans
}, getDisconnectDelay());
}
@Override
protected void handleRawWebSocket(ServerHttpRequest request, ServerHttpResponse response) throws Exception {
TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET);
if ((transportHandler != null) && transportHandler instanceof HandshakeHandler) {
HandshakeHandler handshakeHandler = (HandshakeHandler) transportHandler;
handshakeHandler.doHandshake(request, response);
}
else {
logger.debug("No handler found for raw WebSocket messages");
response.setStatusCode(HttpStatus.NOT_FOUND);
}
}
@Override
protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
String sessionId, TransportType transportType) throws Exception {
@@ -159,8 +205,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans
}
SockJsSessionSupport session = getSockJsSession(sessionId, transportHandler);
if (session == null) {
response.setStatusCode(HttpStatus.NOT_FOUND);
if ((session == null) && !transportHandler.handleNoSession(request, response)) {
return;
}
@@ -184,19 +229,12 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans
public SockJsSessionSupport getSockJsSession(String sessionId, TransportHandler transportHandler) {
TransportType transportType = transportHandler.getTransportType();
// Always create new session for WebSocket requests
sessionId = TransportType.WEBSOCKET.equals(transportType) ?
sessionId + "#" + webSocketSessionIdSuffix.getAndIncrement() : sessionId;
SockJsSessionSupport session = this.sessions.get(sessionId);
if (session != null) {
return session;
}
if (TransportType.XHR_SEND.equals(transportType) || TransportType.JSONP_SEND.equals(transportType)) {
logger.debug(transportType + " did not find session");
if (!transportHandler.canCreateSession()) {
return null;
}
@@ -207,7 +245,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans
}
logger.debug("Creating new session with session id \"" + sessionId + "\"");
session = transportHandler.createSession(sessionId, this.sockJsHandler, this);
session = transportHandler.createSession(sessionId);
this.sessions.put(sessionId, session);
return session;

View File

@@ -15,16 +15,21 @@
*/
package org.springframework.sockjs.server.support;
import java.lang.reflect.Constructor;
import org.springframework.beans.BeanUtils;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.sockjs.server.TransportHandlerRegistrar;
import org.springframework.sockjs.server.TransportHandlerRegistry;
import org.springframework.sockjs.server.transport.EventSourceTransportHandler;
import org.springframework.sockjs.server.transport.HtmlFileTransportHandler;
import org.springframework.sockjs.server.transport.JsonpPollingTransportHandler;
import org.springframework.sockjs.server.transport.JsonpTransportHandler;
import org.springframework.sockjs.server.transport.WebSocketTransportHandler;
import org.springframework.sockjs.server.transport.XhrPollingTransportHandler;
import org.springframework.sockjs.server.transport.XhrStreamingTransportHandler;
import org.springframework.sockjs.server.transport.XhrTransportHandler;
import org.springframework.util.ClassUtils;
/**
@@ -35,19 +40,38 @@ import org.springframework.sockjs.server.transport.XhrTransportHandler;
*/
public class DefaultTransportHandlerRegistrar implements TransportHandlerRegistrar {
public void registerTransportHandlers(TransportHandlerRegistry registry) {
private static final boolean standardWebSocketApiPresent = ClassUtils.isPresent(
"javax.websocket.server.ServerEndpointConfig", DefaultTransportHandlerRegistrar.class.getClassLoader());
registry.registerHandler(new WebSocketTransportHandler());
registry.registerHandler(new XhrPollingTransportHandler());
public void registerTransportHandlers(TransportHandlerRegistry registry, SockJsConfiguration config) {
if (standardWebSocketApiPresent) {
registry.registerHandler(createEndpointWebSocketTransportHandler(config));
}
registry.registerHandler(new XhrPollingTransportHandler(config));
registry.registerHandler(new XhrTransportHandler());
registry.registerHandler(new JsonpPollingTransportHandler());
registry.registerHandler(new JsonpPollingTransportHandler(config));
registry.registerHandler(new JsonpTransportHandler());
registry.registerHandler(new XhrStreamingTransportHandler());
registry.registerHandler(new EventSourceTransportHandler());
registry.registerHandler(new HtmlFileTransportHandler());
registry.registerHandler(new XhrStreamingTransportHandler(config));
registry.registerHandler(new EventSourceTransportHandler(config));
registry.registerHandler(new HtmlFileTransportHandler(config));
}
private TransportHandler createEndpointWebSocketTransportHandler(SockJsConfiguration config) {
try {
String className = "org.springframework.sockjs.server.transport.EndpointWebSocketTransportHandler";
Class<?> clazz = ClassUtils.forName(className, DefaultTransportHandlerRegistrar.class.getClassLoader());
Constructor<?> constructor = clazz.getConstructor(SockJsConfiguration.class);
return (TransportHandler) BeanUtils.instantiateClass(constructor, config);
}
catch (Throwable t) {
throw new IllegalStateException("Failed to instantiate EndpointWebSocketTransportHandler", t);
}
}
}

View File

@@ -25,9 +25,7 @@ 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.sockjs.SockJsHandler;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportHandler;
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -53,8 +51,19 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport
}
@Override
public SockJsSessionSupport createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) {
return null;
public boolean canCreateSession() {
return false;
}
@Override
public SockJsSessionSupport createSession(String sessionId) {
throw new IllegalStateException("Transport handlers receiving messages do not create new sessions");
}
@Override
public boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response) {
response.setStatusCode(HttpStatus.NOT_FOUND);
return false;
}
@Override

View File

@@ -23,9 +23,10 @@ import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
import org.springframework.sockjs.server.TransportHandler;
/**
* TODO
@@ -37,6 +38,26 @@ public abstract class AbstractHttpSendingTransportHandler implements TransportHa
protected final Log logger = LogFactory.getLog(this.getClass());
private final SockJsConfiguration sockJsConfig;
public AbstractHttpSendingTransportHandler(SockJsConfiguration sockJsConfig) {
this.sockJsConfig = sockJsConfig;
}
protected SockJsConfiguration getSockJsConfig() {
return this.sockJsConfig;
}
@Override
public boolean canCreateSession() {
return true;
}
@Override
public boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response) {
return true;
}
@Override
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,

View File

@@ -23,12 +23,11 @@ import java.util.concurrent.BlockingQueue;
import org.springframework.http.server.AsyncServerHttpRequest;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.server.AbstractServerSession;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.util.Assert;
/**
@@ -48,8 +47,8 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession {
private OutputStream outputStream;
public AbstractHttpServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) {
super(sessionId, delegate, sockJsConfig);
public AbstractHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig) {
super(sessionId, sockJsConfig);
}
public void setFrameFormat(FrameFormat frameFormat) {

View File

@@ -0,0 +1,98 @@
/*
* 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.sockjs.server.transport;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractSockJsWebSocketHandler implements WebSocketHandler {
protected final Log logger = LogFactory.getLog(getClass());
private final SockJsConfiguration sockJsConfig;
private final Map<WebSocketSession, SockJsSessionSupport> sessions =
new ConcurrentHashMap<WebSocketSession, SockJsSessionSupport>();
public AbstractSockJsWebSocketHandler(SockJsConfiguration sockJsConfig) {
this.sockJsConfig = sockJsConfig;
}
protected SockJsConfiguration getSockJsConfig() {
return this.sockJsConfig;
}
protected SockJsSessionSupport getSockJsSession(WebSocketSession wsSession) {
return this.sessions.get(wsSession);
}
@Override
public void newSession(WebSocketSession wsSession) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("New session: " + wsSession);
}
SockJsSessionSupport session = createSockJsSession(wsSession);
this.sessions.put(wsSession, session);
session.connectionInitialized();
}
protected abstract SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) throws Exception;
@Override
public void handleTextMessage(WebSocketSession wsSession, String message) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Received payload " + message);
}
SockJsSessionSupport session = getSockJsSession(wsSession);
session.delegateMessages(message);
}
@Override
public void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception {
// should not happen
throw new UnsupportedOperationException();
}
@Override
public void handleException(WebSocketSession webSocketSession, Throwable exception) {
SockJsSessionSupport session = getSockJsSession(webSocketSession);
session.delegateException(exception);
}
@Override
public void sessionClosed(WebSocketSession webSocketSession, int statusCode, String reason) throws Exception {
logger.debug("WebSocket connection closed " + webSocketSession);
SockJsSessionSupport session = this.sessions.remove(webSocketSession);
session.close();
}
}

View File

@@ -19,7 +19,6 @@ import java.io.IOException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.server.SockJsConfiguration;
@@ -32,9 +31,13 @@ import org.springframework.sockjs.server.SockJsConfiguration;
public abstract class AbstractStreamingTransportHandler extends AbstractHttpSendingTransportHandler {
public AbstractStreamingTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
public StreamingHttpServerSession createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) {
return new StreamingHttpServerSession(sessionId, handler, config);
public StreamingHttpServerSession createSession(String sessionId) {
return new StreamingHttpServerSession(sessionId, getSockJsConfig());
}
@Override

View File

@@ -0,0 +1,80 @@
/*
* 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.sockjs.server.transport;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.sockjs.server.TransportType;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.server.HandshakeHandler;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractWebSocketTransportHandler implements TransportHandler, HandshakeHandler {
private final HandshakeHandler sockJsHandshakeHandler;
private final HandshakeHandler handshakeHandler;
public AbstractWebSocketTransportHandler(SockJsConfiguration sockJsConfig) {
this.sockJsHandshakeHandler = createHandshakeHandler(new SockJsWebSocketHandler(sockJsConfig));
this.handshakeHandler = createHandshakeHandler(new WebSocketSockJsHandlerAdapter(sockJsConfig));
}
protected abstract HandshakeHandler createHandshakeHandler(WebSocketHandler webSocketHandler);
@Override
public TransportType getTransportType() {
return TransportType.WEBSOCKET;
}
@Override
public boolean canCreateSession() {
return false;
}
@Override
public SockJsSessionSupport createSession(String sessionId) {
throw new IllegalStateException("WebSocket transport handlers do not create new sessions");
}
@Override
public boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response) {
return true;
}
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
SockJsSessionSupport session) throws Exception {
this.sockJsHandshakeHandler.doHandshake(request, response);
}
@Override
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception {
return this.handshakeHandler.doHandshake(request, response);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.sockjs.server.transport;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.server.HandshakeHandler;
import org.springframework.websocket.server.endpoint.EndpointHandshakeHandler;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class EndpointWebSocketTransportHandler extends AbstractWebSocketTransportHandler {
public EndpointWebSocketTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
protected HandshakeHandler createHandshakeHandler(WebSocketHandler webSocketHandler) {
return new EndpointHandshakeHandler(webSocketHandler);
}
}

View File

@@ -21,7 +21,8 @@ import java.nio.charset.Charset;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportType;
import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
@@ -35,6 +36,10 @@ import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
public class EventSourceTransportHandler extends AbstractStreamingTransportHandler {
public EventSourceTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
public TransportType getTransportType() {
return TransportType.EVENT_SOURCE;

View File

@@ -22,7 +22,8 @@ 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.sockjs.TransportType;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportType;
import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
import org.springframework.util.StringUtils;
@@ -66,6 +67,10 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler
}
public HtmlFileTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
public TransportType getTransportType() {
return TransportType.HTML_FILE;

View File

@@ -21,10 +21,9 @@ 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.sockjs.SockJsHandler;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
import org.springframework.sockjs.server.TransportType;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
import org.springframework.util.StringUtils;
import org.springframework.web.util.JavaScriptUtils;
@@ -39,6 +38,10 @@ import org.springframework.web.util.JavaScriptUtils;
public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHandler {
public JsonpPollingTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
public TransportType getTransportType() {
return TransportType.JSONP;
@@ -50,8 +53,8 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
}
@Override
public PollingHttpServerSession createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) {
return new PollingHttpServerSession(sessionId, handler, config);
public PollingHttpServerSession createSession(String sessionId) {
return new PollingHttpServerSession(sessionId, getSockJsConfig());
}
@Override

View File

@@ -22,7 +22,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.TransportType;
public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler {

View File

@@ -15,15 +15,14 @@
*/
package org.springframework.sockjs.server.transport;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
public class PollingHttpServerSession extends AbstractHttpServerSession {
public PollingHttpServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) {
super(sessionId, delegate, sockJsConfig);
public PollingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig) {
super(sessionId, sockJsConfig);
}
@Override

View File

@@ -0,0 +1,122 @@
/*
* 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.sockjs.server.transport;
import java.io.IOException;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.server.AbstractServerSession;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
import org.springframework.util.StringUtils;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A SockJS implementation of {@link WebSocketHandler}. Delegates messages to and from a
* {@link SockJsHandler} and adds SockJS message framing.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler {
// TODO: JSON library used must be configurable
private final ObjectMapper objectMapper = new ObjectMapper();
public SockJsWebSocketHandler(SockJsConfiguration config) {
super(config);
}
@Override
protected SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) throws Exception {
return new WebSocketServerSession(wsSession, getSockJsConfig());
}
@Override
public void handleTextMessage(WebSocketSession wsSession, String message) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Received payload " + message + " for " + wsSession);
}
if (StringUtils.isEmpty(message)) {
logger.trace("Ignoring empty payload");
return;
}
try {
String[] messages = this.objectMapper.readValue(message, String[].class);
SockJsSessionSupport session = getSockJsSession(wsSession);
session.delegateMessages(messages);
}
catch (IOException e) {
logger.error("Broken data received. Terminating WebSocket connection abruptly", e);
wsSession.close();
}
}
private class WebSocketServerSession extends AbstractServerSession {
private WebSocketSession webSocketSession;
public WebSocketServerSession(WebSocketSession wsSession, SockJsConfiguration config) throws Exception {
super(String.valueOf(wsSession.hashCode()), config);
this.webSocketSession = wsSession;
this.webSocketSession.sendText(SockJsFrame.openFrame().getContent());
scheduleHeartbeat();
connectionInitialized();
}
@Override
public boolean isActive() {
return ((this.webSocketSession != null) && this.webSocketSession.isOpen());
}
@Override
public void sendMessageInternal(String message) {
cancelHeartbeat();
writeFrame(SockJsFrame.messageFrame(message));
scheduleHeartbeat();
}
@Override
protected void writeFrameInternal(SockJsFrame frame) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Write " + frame);
}
this.webSocketSession.sendText(frame.getContent());
}
@Override
public void closeInternal() {
this.webSocketSession.close();
this.webSocketSession = null;
updateLastActiveTime();
}
@Override
protected void deactivate() {
this.webSocketSession.close();
}
}
}

View File

@@ -18,7 +18,6 @@ package org.springframework.sockjs.server.transport;
import java.io.IOException;
import java.io.OutputStream;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.SockJsFrame;
@@ -28,8 +27,8 @@ public class StreamingHttpServerSession extends AbstractHttpServerSession {
private int byteCount;
public StreamingHttpServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) {
super(sessionId, delegate, sockJsConfig);
public StreamingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig) {
super(sessionId, sockJsConfig);
}
protected void flush() {

View File

@@ -0,0 +1,76 @@
/*
* 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.sockjs.server.transport;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
/**
* A {@link WebSocketHandler} that merely delegates to a {@link SockJsHandler} without any
* SockJS message framing. For use with raw WebSocket communication at SockJS path
* "/websocket".
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketSockJsHandlerAdapter extends AbstractSockJsWebSocketHandler {
public WebSocketSockJsHandlerAdapter(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
protected SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) {
return new WebSocketSessionAdapter(wsSession);
}
private class WebSocketSessionAdapter extends SockJsSessionSupport {
private final WebSocketSession wsSession;
public WebSocketSessionAdapter(WebSocketSession wsSession) {
super(String.valueOf(wsSession.hashCode()), getSockJsConfig().getSockJsHandler());
this.wsSession = wsSession;
}
@Override
public boolean isActive() {
return (!isClosed() && this.wsSession.isOpen());
}
@Override
public void sendMessage(String message) throws Exception {
this.wsSession.sendText(message);
}
public void close() {
if (!isClosed()) {
logger.debug("Closing session");
super.close();
this.wsSession.close();
}
}
}
}

View File

@@ -1,60 +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.sockjs.server.transport;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.SockJsSessionSupport;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.StandardWebSocketServerSession;
import org.springframework.sockjs.server.TransportHandler;
import org.springframework.sockjs.server.SockJsWebSocketHandler;
import org.springframework.websocket.server.HandshakeRequestHandler;
import org.springframework.websocket.server.endpoint.EndpointHandshakeRequestHandler;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketTransportHandler implements TransportHandler {
@Override
public TransportType getTransportType() {
return TransportType.WEBSOCKET;
}
@Override
public SockJsSessionSupport createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) {
return new StandardWebSocketServerSession(sessionId, handler, config);
}
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session)
throws Exception {
StandardWebSocketServerSession sockJsSession = (StandardWebSocketServerSession) session;
SockJsWebSocketHandler webSocketHandler = new SockJsWebSocketHandler(sockJsSession);
HandshakeRequestHandler handshakeRequestHandler = new EndpointHandshakeRequestHandler(webSocketHandler);
handshakeRequestHandler.doHandshake(request, response);
}
}

View File

@@ -19,9 +19,8 @@ import java.nio.charset.Charset;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.sockjs.SockJsHandler;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportType;
import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
@@ -35,6 +34,10 @@ import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHandler {
public XhrPollingTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
public TransportType getTransportType() {
return TransportType.XHR;
@@ -50,8 +53,8 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand
return new DefaultFrameFormat("%s\n");
}
public PollingHttpServerSession createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) {
return new PollingHttpServerSession(sessionId, handler, config);
public PollingHttpServerSession createSession(String sessionId) {
return new PollingHttpServerSession(sessionId, getSockJsConfig());
}
}

View File

@@ -21,7 +21,8 @@ import java.nio.charset.Charset;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.SockJsConfiguration;
import org.springframework.sockjs.server.TransportType;
import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat;
import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
@@ -35,6 +36,10 @@ import org.springframework.sockjs.server.SockJsFrame.FrameFormat;
public class XhrStreamingTransportHandler extends AbstractStreamingTransportHandler {
public XhrStreamingTransportHandler(SockJsConfiguration sockJsConfig) {
super(sockJsConfig);
}
@Override
public TransportType getTransportType() {
return TransportType.XHR_STREAMING;

View File

@@ -19,7 +19,7 @@ import java.io.IOException;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.sockjs.TransportType;
import org.springframework.sockjs.server.TransportType;
public class XhrTransportHandler extends AbstractHttpReceivingTransportHandler {

View File

@@ -152,6 +152,8 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo
protected Object getEndpoint() {
if (this.endpointClass != null) {
Assert.notNull(this.applicationContext,
"An ApplicationContext is required to initialize endpoint instances per request.");
return this.applicationContext.getAutowireCapableBeanFactory().createBean(this.endpointClass);
}
else {

View File

@@ -28,11 +28,17 @@ import javax.xml.bind.DatatypeConverter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
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.util.Assert;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.websocket.WebSocketHandler;
/**
@@ -40,14 +46,32 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractHandshakeRequestHandler implements HandshakeRequestHandler {
public abstract class AbstractHandshakeHandler implements HandshakeHandler, BeanFactoryAware {
private static final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
protected Log logger = LogFactory.getLog(getClass());
private final WebSocketHandler webSocketHandler;
private final Class<? extends WebSocketHandler> handlerClass;
private List<String> protocols;
private AutowireCapableBeanFactory beanFactory;
public AbstractHandshakeHandler(WebSocketHandler webSocketHandler) {
Assert.notNull(webSocketHandler, "webSocketHandler is required");
this.webSocketHandler = webSocketHandler;
this.handlerClass = null;
}
public AbstractHandshakeHandler(Class<? extends WebSocketHandler> handlerClass) {
Assert.notNull((handlerClass), "handlerClass is required");
this.webSocketHandler = null;
this.handlerClass = handlerClass;
}
public void setProtocols(String... protocols) {
this.protocols = Arrays.asList(protocols);
@@ -58,7 +82,24 @@ public abstract class AbstractHandshakeRequestHandler implements HandshakeReques
}
@Override
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception {
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof AutowireCapableBeanFactory) {
this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
}
}
protected WebSocketHandler getWebSocketHandler() {
if (this.handlerClass != null) {
Assert.notNull(this.beanFactory, "BeanFactory is required for WebSocketHandler instance per request.");
return this.beanFactory.createBean(this.handlerClass);
}
else {
return this.webSocketHandler;
}
}
@Override
public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception {
logger.debug("Starting handshake for " + request.getURI());
@@ -106,6 +147,9 @@ public abstract class AbstractHandshakeRequestHandler implements HandshakeReques
return true;
}
protected abstract void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response,
String protocol) throws Exception;
protected boolean validateUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (!"WebSocket".equalsIgnoreCase(request.getHeaders().getUpgrade())) {
response.setStatusCode(HttpStatus.BAD_REQUEST);
@@ -168,7 +212,4 @@ public abstract class AbstractHandshakeRequestHandler implements HandshakeReques
return DatatypeConverter.printBase64Binary(bytes);
}
protected abstract void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol)
throws Exception;
}

View File

@@ -25,7 +25,7 @@ import org.springframework.http.server.ServerHttpResponse;
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface HandshakeRequestHandler {
public interface HandshakeHandler {
boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception;

View File

@@ -23,35 +23,36 @@ import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ClassUtils;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint;
import org.springframework.websocket.server.AbstractHandshakeRequestHandler;
import org.springframework.websocket.server.AbstractHandshakeHandler;
import org.springframework.websocket.server.HandshakeHandler;
import org.springframework.websocket.support.WebSocketHandlerEndpoint;
/**
* A {@link HandshakeHandler} for use with standard Java WebSocket.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class EndpointHandshakeRequestHandler extends AbstractHandshakeRequestHandler {
public class EndpointHandshakeHandler extends AbstractHandshakeHandler {
private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent(
"org.apache.tomcat.websocket.server.WsHandshakeRequest", EndpointHandshakeRequestHandler.class.getClassLoader());
"org.apache.tomcat.websocket.server.WsHandshakeRequest", EndpointHandshakeHandler.class.getClassLoader());
private final EndpointRegistration endpointRegistration;
private final EndpointRequestUpgradeStrategy upgradeStrategy;
private final WebSocketRequestUpgradeStrategy upgradeStrategy;
public EndpointHandshakeRequestHandler(WebSocketHandler webSocketHandler) {
this(new WebSocketHandlerEndpoint(webSocketHandler));
}
public EndpointHandshakeRequestHandler(Endpoint endpoint) {
this.endpointRegistration = new EndpointRegistration("/dummy", endpoint);
public EndpointHandshakeHandler(WebSocketHandler webSocketHandler) {
super(webSocketHandler);
this.upgradeStrategy = createRequestUpgradeStrategy();
}
private static EndpointRequestUpgradeStrategy createRequestUpgradeStrategy() {
public EndpointHandshakeHandler(Class<? extends WebSocketHandler> handlerClass) {
super(handlerClass);
this.upgradeStrategy = createRequestUpgradeStrategy();
}
private static WebSocketRequestUpgradeStrategy createRequestUpgradeStrategy() {
String className;
if (tomcatWebSocketPresent) {
className = "org.springframework.websocket.server.endpoint.TomcatRequestUpgradeStrategy";
@@ -60,24 +61,21 @@ public class EndpointHandshakeRequestHandler extends AbstractHandshakeRequestHan
throw new IllegalStateException("No suitable EndpointRequestUpgradeStrategy");
}
try {
Class<?> clazz = ClassUtils.forName(className, EndpointHandshakeRequestHandler.class.getClassLoader());
return (EndpointRequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor());
Class<?> clazz = ClassUtils.forName(className, EndpointHandshakeHandler.class.getClassLoader());
return (WebSocketRequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor());
}
catch (Throwable t) {
throw new IllegalStateException("Failed to instantiate " + className, t);
}
}
public EndpointRegistration getEndpointRegistration() {
return this.endpointRegistration;
}
@Override
public void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol)
throws Exception {
logger.debug("Upgrading HTTP request");
this.upgradeStrategy.upgrade(request, response, protocol, this.endpointRegistration);
Endpoint endpoint = new WebSocketHandlerEndpoint(getWebSocketHandler());
this.upgradeStrategy.upgrade(request, response, protocol, new EndpointRegistration("/dummy", endpoint));
}
}

View File

@@ -36,7 +36,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint;
import org.springframework.websocket.support.WebSocketHandlerEndpoint;
/**

View File

@@ -36,7 +36,7 @@ import org.springframework.util.ReflectionUtils;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class TomcatRequestUpgradeStrategy implements EndpointRequestUpgradeStrategy {
public class TomcatRequestUpgradeStrategy implements WebSocketRequestUpgradeStrategy {
@Override

View File

@@ -21,14 +21,16 @@ import org.springframework.http.server.ServerHttpResponse;
/**
* A strategy for performing the actual request upgrade after the handshake checks have
* passed, encapsulating runtime-specific steps of the handshake.
* A strategy for performing the runtime-specific parts of a WebSocket upgrade request.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface EndpointRequestUpgradeStrategy {
public interface WebSocketRequestUpgradeStrategy {
/**
* Invoked after the handshake checks have been performed and succeeded.
*/
void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol,
EndpointRegistration registration) throws Exception;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.websocket.endpoint;
package org.springframework.websocket.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.websocket.endpoint;
package org.springframework.websocket.support;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -43,7 +43,7 @@ public class WebSocketHandlerEndpoint extends Endpoint {
private final WebSocketHandler webSocketHandler;
private final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<String, WebSocketSession>();
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<String, WebSocketSession>();
public WebSocketHandlerEndpoint(WebSocketHandler webSocketHandler) {
@@ -57,7 +57,7 @@ public class WebSocketHandlerEndpoint extends Endpoint {
}
try {
WebSocketSession webSocketSession = new StandardWebSocketSession(session);
this.sessionMap.put(session.getId(), webSocketSession);
this.sessions.put(session.getId(), webSocketSession);
session.addMessageHandler(new StandardMessageHandler(session.getId()));
this.webSocketHandler.newSession(webSocketSession);
}
@@ -75,7 +75,7 @@ public class WebSocketHandlerEndpoint extends Endpoint {
}
try {
WebSocketSession webSocketSession = getSession(id);
this.sessionMap.remove(id);
this.sessions.remove(id);
int code = closeReason.getCloseCode().getCode();
String reason = closeReason.getReasonPhrase();
this.webSocketHandler.sessionClosed(webSocketSession, code, reason);
@@ -100,7 +100,7 @@ public class WebSocketHandlerEndpoint extends Endpoint {
}
private WebSocketSession getSession(String sourceSessionId) {
WebSocketSession webSocketSession = this.sessionMap.get(sourceSessionId);
WebSocketSession webSocketSession = this.sessions.get(sourceSessionId);
Assert.notNull(webSocketSession, "No session");
return webSocketSession;
}

View File

@@ -4,5 +4,5 @@
* Support for working with standard Java WebSocket Endpoint's.
*
*/
package org.springframework.websocket.endpoint;
package org.springframework.websocket.support;