Add WebSocketClient and WebSocketConnectionManager

This change adds a WebSocketClient abstraction and enables the use of
WebSocketHandler on the client side.
This commit is contained in:
Rossen Stoyanchev
2013-04-21 10:04:44 -04:00
parent ab5d60d343
commit 2046629945
18 changed files with 797 additions and 235 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.websocket;
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;
@@ -32,14 +33,14 @@ import org.springframework.util.ClassUtils;
*/
public class HandlerProvider<T> implements BeanFactoryAware {
private Log logger = LogFactory.getLog(this.getClass());
private final T handlerBean;
private final Class<? extends T> handlerClass;
private AutowireCapableBeanFactory beanFactory;
private Log logger;
public HandlerProvider(T handlerBean) {
Assert.notNull(handlerBean, "handlerBean is required");

View File

@@ -0,0 +1,48 @@
/*
* 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.websocket;
import java.net.URI;
import org.springframework.http.HttpHeaders;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketHandshakeRequest {
private final URI uri;
private final HttpHeaders headers;
public WebSocketHandshakeRequest(HttpHeaders headers, URI uri) {
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
this.uri = uri;
}
public URI getUri() {
return this.uri;
}
public HttpHeaders getHeaders() {
return this.headers;
}
}

View File

@@ -5,7 +5,7 @@
* 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
* 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,
@@ -13,17 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.websocket.client;
import java.io.IOException;
import java.net.URI;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.SmartLifecycle;
@@ -37,7 +30,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractEndpointConnectionManager implements SmartLifecycle {
public abstract class AbstractWebSocketConnectionManager implements SmartLifecycle {
protected final Log logger = LogFactory.getLog(getClass());
@@ -47,35 +40,15 @@ public abstract class AbstractEndpointConnectionManager implements SmartLifecycl
private int phase = Integer.MAX_VALUE;
private final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
private Session session;
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-");
private final Object lifecycleMonitor = new Object();
public AbstractEndpointConnectionManager(String uriTemplate, Object... uriVariables) {
public AbstractWebSocketConnectionManager(String uriTemplate, Object... uriVariables) {
this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri();
}
public void setAsyncSendTimeout(long timeoutInMillis) {
this.webSocketContainer.setAsyncSendTimeout(timeoutInMillis);
}
public void setMaxSessionIdleTimeout(long timeoutInMillis) {
this.webSocketContainer.setDefaultMaxSessionIdleTimeout(timeoutInMillis);
}
public void setMaxTextMessageBufferSize(int bufferSize) {
this.webSocketContainer.setDefaultMaxTextMessageBufferSize(bufferSize);
}
public void setMaxBinaryMessageBufferSize(Integer bufferSize) {
this.webSocketContainer.setDefaultMaxBinaryMessageBufferSize(bufferSize);
}
/**
* Set whether to auto-connect to the remote endpoint after this connection manager
* has been initialized and the Spring context has been refreshed.
@@ -117,14 +90,11 @@ public abstract class AbstractEndpointConnectionManager implements SmartLifecycl
return this.uri;
}
protected WebSocketContainer getWebSocketContainer() {
return this.webSocketContainer;
}
/**
* Auto-connects to the configured {@link #setDefaultUri(URI) default URI}.
* Connect to the configured {@link #setDefaultUri(URI) default URI}. If already
* connected, the method has no impact.
*/
public void start() {
public final void start() {
synchronized (this.lifecycleMonitor) {
if (!isRunning()) {
this.taskExecutor.execute(new Runnable() {
@@ -132,12 +102,12 @@ public abstract class AbstractEndpointConnectionManager implements SmartLifecycl
public void run() {
synchronized (lifecycleMonitor) {
try {
logger.info("Connecting to endpoint at URI " + uri);
session = connect();
logger.info("Connecting to WebSocket at " + uri);
openConnection();
logger.info("Successfully connected");
}
catch (Throwable ex) {
logger.error("Failed to connect to endpoint at " + uri, ex);
logger.error("Failed to connect", ex);
}
}
}
@@ -146,25 +116,26 @@ public abstract class AbstractEndpointConnectionManager implements SmartLifecycl
}
}
protected abstract Session connect() throws DeploymentException, IOException;
protected abstract void openConnection() throws Exception;
/**
* Deactivates the configured message endpoint.
* Closes the configured message WebSocket connection.
*/
public void stop() {
public final void stop() {
synchronized (this.lifecycleMonitor) {
if (isRunning()) {
try {
this.session.close();
closeConnection();
}
catch (IOException e) {
// ignore
catch (Throwable e) {
logger.error("Failed to stop WebSocket connection", e);
}
}
this.session = null;
}
}
protected abstract void closeConnection() throws Exception;
public void stop(Runnable callback) {
synchronized (this.lifecycleMonitor) {
this.stop();
@@ -177,12 +148,10 @@ public abstract class AbstractEndpointConnectionManager implements SmartLifecycl
*/
public boolean isRunning() {
synchronized (this.lifecycleMonitor) {
if ((this.session != null) && this.session.isOpen()) {
return true;
}
this.session = null;
return false;
return isConnected();
}
}
protected abstract boolean isConnected();
}

View File

@@ -0,0 +1,48 @@
/*
* 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.websocket.client;
import java.net.URI;
import org.springframework.http.HttpHeaders;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
/**
* Contract for starting a WebSocket handshake request.
*
* <p>To automatically start a WebSocket connection when the application starts, see
* {@link WebSocketConnectionManager}.
*
* @author Rossen Stoyanchev
* @since 4.0
*
* @see WebSocketConnectionManager
*/
public interface WebSocketClient {
WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables)
throws WebSocketConnectFailureException;
WebSocketSession doHandshake(WebSocketHandler handler, URI uri)
throws WebSocketConnectFailureException;
WebSocketSession doHandshake(WebSocketHandler handler, HttpHeaders headers, URI uri)
throws WebSocketConnectFailureException;
}

View File

@@ -0,0 +1,38 @@
/*
* 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.websocket.client;
import org.springframework.core.NestedRuntimeException;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
@SuppressWarnings("serial")
public class WebSocketConnectFailureException extends NestedRuntimeException {
public WebSocketConnectFailureException(String msg, Throwable cause) {
super(msg, cause);
}
public WebSocketConnectFailureException(String msg) {
super(msg);
}
}

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.websocket.client;
import java.util.List;
import org.springframework.http.HttpHeaders;
import org.springframework.websocket.HandlerProvider;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketConnectionManager extends AbstractWebSocketConnectionManager {
private final WebSocketClient client;
private final HandlerProvider<WebSocketHandler> webSocketHandlerProvider;
private WebSocketSession webSocketSession;
private List<String> subProtocols;
public WebSocketConnectionManager(WebSocketClient webSocketClient,
WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) {
super(uriTemplate, uriVariables);
this.webSocketHandlerProvider = new HandlerProvider<WebSocketHandler>(webSocketHandler);
this.client = webSocketClient;
}
public void setSubProtocols(List<String> subProtocols) {
this.subProtocols = subProtocols;
}
public List<String> getSubProtocols() {
return this.subProtocols;
}
@Override
protected void openConnection() throws Exception {
WebSocketHandler webSocketHandler = this.webSocketHandlerProvider.getHandler();
HttpHeaders headers = new HttpHeaders();
headers.setSecWebSocketProtocol(this.subProtocols);
this.webSocketSession = this.client.doHandshake(webSocketHandler, headers, getUri());
}
@Override
protected void closeConnection() throws Exception {
this.webSocketSession.close();
}
@Override
protected boolean isConnected() {
return ((this.webSocketSession != null) && (this.webSocketSession.isOpen()));
}
}

View File

@@ -14,15 +14,10 @@
* limitations under the License.
*/
package org.springframework.websocket.client;
package org.springframework.websocket.client.endpoint;
import java.io.IOException;
import javax.websocket.DeploymentException;
import javax.websocket.Session;
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;
@@ -34,24 +29,20 @@ import org.springframework.websocket.HandlerProvider;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class AnnotatedEndpointConnectionManager extends AbstractEndpointConnectionManager
public class AnnotatedEndpointConnectionManager extends EndpointConnectionManagerSupport
implements BeanFactoryAware {
private static Log logger = LogFactory.getLog(AnnotatedEndpointConnectionManager.class);
private final HandlerProvider<Object> endpointProvider;
public AnnotatedEndpointConnectionManager(Class<?> endpointClass, String uriTemplate, Object... uriVariables) {
super(uriTemplate, uriVariables);
this.endpointProvider = new HandlerProvider<Object>(endpointClass);
this.endpointProvider.setLogger(logger);
}
public AnnotatedEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) {
super(uriTemplate, uriVariables);
this.endpointProvider = new HandlerProvider<Object>(endpointBean);
this.endpointProvider.setLogger(logger);
}
@Override
@@ -59,10 +50,12 @@ public class AnnotatedEndpointConnectionManager extends AbstractEndpointConnecti
this.endpointProvider.setBeanFactory(beanFactory);
}
@Override
protected Session connect() throws DeploymentException, IOException {
protected void openConnection() throws Exception {
Object endpoint = this.endpointProvider.getHandler();
return getWebSocketContainer().connectToServer(endpoint, getUri());
Session session = getWebSocketContainer().connectToServer(endpoint, getUri());
updateSession(session);
}
}

View File

@@ -14,23 +14,19 @@
* limitations under the License.
*/
package org.springframework.websocket.client;
package org.springframework.websocket.client.endpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ClientEndpointConfig.Configurator;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.Extension;
import javax.websocket.Session;
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;
@@ -43,27 +39,23 @@ import org.springframework.websocket.HandlerProvider;
* @author Rossen Stoyanchev
* @since 4.0
*/
public class EndpointConnectionManager extends AbstractEndpointConnectionManager implements BeanFactoryAware {
private static Log logger = LogFactory.getLog(EndpointConnectionManager.class);
public class EndpointConnectionManager extends EndpointConnectionManagerSupport implements BeanFactoryAware {
private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create();
private final HandlerProvider<Endpoint> endpointProvider;
public EndpointConnectionManager(Class<? extends Endpoint> endpointClass, String uriTemplate, Object... uriVariables) {
super(uriTemplate, uriVariables);
Assert.notNull(endpointClass, "endpointClass is required");
this.endpointProvider = new HandlerProvider<Endpoint>(endpointClass);
this.endpointProvider.setLogger(logger);
}
public EndpointConnectionManager(Endpoint endpointBean, String uriTemplate, Object... uriVariables) {
super(uriTemplate, uriVariables);
Assert.notNull(endpointBean, "endpointBean is required");
this.endpointProvider = new HandlerProvider<Endpoint>(endpointBean);
this.endpointProvider.setLogger(logger);
}
public EndpointConnectionManager(Class<? extends Endpoint> endpointClass, String uriTemplate, Object... uriVars) {
super(uriTemplate, uriVars);
Assert.notNull(endpointClass, "endpointClass is required");
this.endpointProvider = new HandlerProvider<Endpoint>(endpointClass);
}
public void setSubProtocols(String... subprotocols) {
@@ -92,10 +84,11 @@ public class EndpointConnectionManager extends AbstractEndpointConnectionManager
}
@Override
protected Session connect() throws DeploymentException, IOException {
Endpoint typedEndpoint = this.endpointProvider.getHandler();
protected void openConnection() throws Exception {
Endpoint endpoint = this.endpointProvider.getHandler();
ClientEndpointConfig endpointConfig = this.configBuilder.build();
return getWebSocketContainer().connectToServer(typedEndpoint, endpointConfig, getUri());
Session session = getWebSocketContainer().connectToServer(endpoint, endpointConfig, getUri());
updateSession(session);
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.websocket.client.endpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.springframework.websocket.client.AbstractWebSocketConnectionManager;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class EndpointConnectionManagerSupport extends AbstractWebSocketConnectionManager {
private WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
private Session session;
public EndpointConnectionManagerSupport(String uriTemplate, Object... uriVariables) {
super(uriTemplate, uriVariables);
}
public void setWebSocketContainer(WebSocketContainer webSocketContainer) {
this.webSocketContainer = webSocketContainer;
}
public WebSocketContainer getWebSocketContainer() {
return this.webSocketContainer;
}
protected void updateSession(Session session) {
this.session = session;
}
@Override
protected void closeConnection() throws Exception {
try {
if (isConnected()) {
this.session.close();
}
}
finally {
this.session = null;
}
}
@Override
protected boolean isConnected() {
return ((this.session != null) && this.session.isOpen());
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.websocket.client.endpoint;
import java.net.URI;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.ClientEndpointConfig.Configurator;
import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.springframework.http.HttpHeaders;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.websocket.WebSocketHandler;
import org.springframework.websocket.WebSocketSession;
import org.springframework.websocket.client.WebSocketClient;
import org.springframework.websocket.client.WebSocketConnectFailureException;
import org.springframework.websocket.endpoint.StandardWebSocketSession;
import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class StandardWebSocketClient implements WebSocketClient {
private static final Set<String> EXCLUDED_HEADERS = new HashSet<String>(
Arrays.asList("Sec-WebSocket-Accept", "Sec-WebSocket-Extensions", "Sec-WebSocket-Key",
"Sec-WebSocket-Protocol", "Sec-WebSocket-Version"));
private WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
public void setWebSocketContainer(WebSocketContainer container) {
this.webSocketContainer = container;
}
public WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate,
Object... uriVariables) throws WebSocketConnectFailureException {
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri();
return doHandshake(handler, uri);
}
@Override
public WebSocketSession doHandshake(WebSocketHandler handler, URI uri) throws WebSocketConnectFailureException {
return doHandshake(handler, null, uri);
}
@Override
public WebSocketSession doHandshake(WebSocketHandler handler, final HttpHeaders httpHeaders, URI uri)
throws WebSocketConnectFailureException {
Endpoint endpoint = new WebSocketHandlerEndpoint(handler);
ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create();
if (httpHeaders != null) {
List<String> protocols = httpHeaders.getSecWebSocketProtocol();
if (!protocols.isEmpty()) {
configBuidler.preferredSubprotocols(protocols);
}
configBuidler.configurator(new Configurator() {
@Override
public void beforeRequest(Map<String, List<String>> headers) {
for (String headerName : httpHeaders.keySet()) {
if (!EXCLUDED_HEADERS.contains(headerName)) {
headers.put(headerName, httpHeaders.get(headerName));
}
}
}
});
}
try {
Session session = this.webSocketContainer.connectToServer(endpoint, configBuidler.build(), uri);
return new StandardWebSocketSession(session);
}
catch (Exception e) {
throw new WebSocketConnectFailureException("Failed to connect to " + uri, e);
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.websocket.client.endpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;
import org.springframework.beans.factory.FactoryBean;
/**
* A FactoryBean for creating and configuring a {@link javax.websocket.WebSocketContainer}
* through Spring XML configuration. In Java configuration, ignore this class and use
* {@code ContainerProvider.getWebSocketContainer()} instead.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class WebSocketContainerFactoryBean implements FactoryBean<WebSocketContainer> {
private final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
public void setAsyncSendTimeout(long timeoutInMillis) {
this.webSocketContainer.setAsyncSendTimeout(timeoutInMillis);
}
public long getAsyncSendTimeout() {
return this.webSocketContainer.getDefaultAsyncSendTimeout();
}
public void setMaxSessionIdleTimeout(long timeoutInMillis) {
this.webSocketContainer.setDefaultMaxSessionIdleTimeout(timeoutInMillis);
}
public long getMaxSessionIdleTimeout() {
return this.webSocketContainer.getDefaultMaxSessionIdleTimeout();
}
public void setMaxTextMessageBufferSize(int bufferSize) {
this.webSocketContainer.setDefaultMaxTextMessageBufferSize(bufferSize);
}
public int getMaxTextMessageBufferSize() {
return this.webSocketContainer.getDefaultMaxTextMessageBufferSize();
}
public void setMaxBinaryMessageBufferSize(int bufferSize) {
this.webSocketContainer.setDefaultMaxBinaryMessageBufferSize(bufferSize);
}
public int getMaxBinaryMessageBufferSize() {
return this.webSocketContainer.getDefaultMaxBinaryMessageBufferSize();
}
@Override
public WebSocketContainer getObject() throws Exception {
return this.webSocketContainer;
}
@Override
public Class<?> getObjectType() {
return WebSocketContainer.class;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,8 @@
/**
* Client-side classes for use with standard Java WebSocket endpoints including
* {@link org.springframework.websocket.client.endpoint.EndpointConnectionManager} and
* {@link org.springframework.websocket.client.endpoint.AnnotatedEndpointConnectionManager}
* for connecting to server endpoints using type-based or annotated endpoints respectively.
*/
package org.springframework.websocket.client.endpoint;

View File

@@ -1,6 +1,5 @@
/**
* Client-side support for WebSocket applications.
* Server-side abstractions for WebSocket applications.
*
*/
package org.springframework.websocket.client;

View File

@@ -54,8 +54,7 @@ public class WebSocketHandlerEndpoint extends Endpoint {
@Override
public void onOpen(javax.websocket.Session session, EndpointConfig config) {
if (logger.isDebugEnabled()) {
logger.debug("Client connected, WebSocket session id=" + session.getId()
+ ", path=" + session.getRequestURI().getPath());
logger.debug("Client connected, WebSocket session id=" + session.getId() + ", uri=" + session.getRequestURI());
}
try {
WebSocketSession webSocketSession = new StandardWebSocketSession(session);

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.websocket.server.endpoint;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.websocket.DeploymentException;
@@ -25,13 +29,13 @@ import javax.websocket.server.ServerEndpointConfig;
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.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* BeanPostProcessor that detects beans of type
@@ -43,114 +47,79 @@ import org.springframework.util.ObjectUtils;
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class EndpointExporter implements InitializingBean, BeanPostProcessor, BeanFactoryAware {
public class EndpointExporter implements InitializingBean, BeanPostProcessor, ApplicationContextAware {
private static final boolean isServletApiPresent =
ClassUtils.isPresent("javax.servlet.ServletContext", EndpointExporter.class.getClassLoader());
private static Log logger = LogFactory.getLog(EndpointExporter.class);
private Class<?>[] annotatedEndpointClasses;
private final List<Class<?>> annotatedEndpointClasses = new ArrayList<Class<?>>();
private Long maxSessionIdleTimeout;
private final List<Class<?>> annotatedEndpointBeanTypes = new ArrayList<Class<?>>();
private Integer maxTextMessageBufferSize;
private Integer maxBinaryMessageBufferSize;
private ApplicationContext applicationContext;
private ServerContainer serverContainer;
/**
* TODO
* @param annotatedEndpointClasses
*/
public void setAnnotatedEndpointClasses(Class<?>... annotatedEndpointClasses) {
this.annotatedEndpointClasses = annotatedEndpointClasses;
}
/**
* If this property set it is in turn used to configure
* {@link ServerContainer#setDefaultMaxSessionIdleTimeout(long)}.
*/
public void setMaxSessionIdleTimeout(long maxSessionIdleTimeout) {
this.maxSessionIdleTimeout = maxSessionIdleTimeout;
}
public Long getMaxSessionIdleTimeout() {
return this.maxSessionIdleTimeout;
}
/**
* If this property set it is in turn used to configure
* {@link ServerContainer#setDefaultMaxTextMessageBufferSize(int)}
*/
public void setMaxTextMessageBufferSize(int maxTextMessageBufferSize) {
this.maxTextMessageBufferSize = maxTextMessageBufferSize;
}
public Integer getMaxTextMessageBufferSize() {
return this.maxTextMessageBufferSize;
}
/**
* If this property set it is in turn used to configure
* {@link ServerContainer#setDefaultMaxBinaryMessageBufferSize(int)}.
*/
public void setMaxBinaryMessageBufferSize(int maxBinaryMessageBufferSize) {
this.maxBinaryMessageBufferSize = maxBinaryMessageBufferSize;
}
public Integer getMaxBinaryMessageBufferSize() {
return this.maxBinaryMessageBufferSize;
this.annotatedEndpointClasses.clear();
this.annotatedEndpointClasses.addAll(Arrays.asList(annotatedEndpointClasses));
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) beanFactory;
Map<String, Object> annotatedEndpoints = lbf.getBeansWithAnnotation(ServerEndpoint.class);
for (String beanName : annotatedEndpoints.keySet()) {
Class<?> beanType = lbf.getType(beanName);
try {
if (logger.isInfoEnabled()) {
logger.info("Detected @ServerEndpoint bean '" + beanName + "', registering it as an endpoint by type");
}
getServerContainer().addEndpoint(beanType);
}
catch (DeploymentException e) {
throw new IllegalStateException("Failed to register @ServerEndpoint bean type " + beanName, e);
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.serverContainer = getServerContainer();
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(ServerEndpoint.class);
for (String beanName : beans.keySet()) {
Class<?> beanType = applicationContext.getType(beanName);
if (logger.isInfoEnabled()) {
logger.info("Detected @ServerEndpoint bean '" + beanName + "', registering it as an endpoint by type");
}
this.annotatedEndpointBeanTypes.add(beanType);
}
}
/**
* Return the {@link ServerContainer} instance, a process which is undefined outside
* of standalone containers (section 6.4 of the spec).
*/
protected abstract ServerContainer getServerContainer();
protected ServerContainer getServerContainer() {
if (isServletApiPresent) {
try {
Method getter = ReflectionUtils.findMethod(this.applicationContext.getClass(), "getServletContext");
Object servletContext = getter.invoke(this.applicationContext);
Method attrMethod = ReflectionUtils.findMethod(servletContext.getClass(), "getAttribute", String.class);
return (ServerContainer) attrMethod.invoke(servletContext, "javax.websocket.server.ServerContainer");
}
catch (Exception ex) {
throw new IllegalStateException(
"Failed to get javax.websocket.server.ServerContainer via ServletContext attribute", ex);
}
}
return null;
}
@Override
public void afterPropertiesSet() throws Exception {
ServerContainer serverContainer = getServerContainer();
Assert.notNull(serverContainer, "javax.websocket.server.ServerContainer not available");
if (this.maxSessionIdleTimeout != null) {
serverContainer.setDefaultMaxSessionIdleTimeout(this.maxSessionIdleTimeout);
}
if (this.maxTextMessageBufferSize != null) {
serverContainer.setDefaultMaxTextMessageBufferSize(this.maxTextMessageBufferSize);
}
if (this.maxBinaryMessageBufferSize != null) {
serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize);
}
List<Class<?>> allClasses = new ArrayList<Class<?>>(this.annotatedEndpointClasses);
allClasses.addAll(this.annotatedEndpointBeanTypes);
if (!ObjectUtils.isEmpty(this.annotatedEndpointClasses)) {
for (Class<?> clazz : this.annotatedEndpointClasses) {
try {
logger.info("Registering @ServerEndpoint type " + clazz);
serverContainer.addEndpoint(clazz);
}
catch (DeploymentException e) {
throw new IllegalStateException("Failed to register @ServerEndpoint type " + clazz, e);
}
for (Class<?> clazz : allClasses) {
try {
logger.info("Registering @ServerEndpoint type " + clazz);
this.serverContainer.addEndpoint(clazz);
}
catch (DeploymentException e) {
throw new IllegalStateException("Failed to register @ServerEndpoint type " + clazz, e);
}
}
}

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.websocket.server.endpoint;
import javax.servlet.ServletContext;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerContainerProvider;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
/**
* A sub-class of {@link EndpointExporter} for use with a Servlet container runtime.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class ServletEndpointExporter extends EndpointExporter implements ServletContextAware {
private static final String SERVER_CONTAINER_ATTR_NAME = "javax.websocket.server.ServerContainer";
private ServletContext servletContext;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
protected ServerContainer getServerContainer() {
Assert.notNull(this.servletContext, "A ServletContext is needed to access the WebSocket ServerContainer");
ServerContainer container = (ServerContainer) this.servletContext.getAttribute(SERVER_CONTAINER_ATTR_NAME);
if (container == null) {
// Remove when Tomcat has caught up to http://java.net/jira/browse/WEBSOCKET_SPEC-165
return ServerContainerProvider.getServerContainer();
}
return container;
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.websocket.server.endpoint;
import javax.servlet.ServletContext;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerContainer;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.sockjs.server.SockJsService;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.websocket.server.DefaultHandshakeHandler;
/**
* A FactoryBean for {@link javax.websocket.server.ServerContainer}. Since
* there is only one {@code ServerContainer} instance accessible under a well-known
* {@code javax.servlet.ServletContext} attribute, simply declaring this FactoryBean and
* using its setters allows configuring the {@code ServerContainer} through Spring
* configuration. This is useful even if the ServerContainer is not injected into any
* other bean. For example, an application can configure a {@link DefaultHandshakeHandler}
* , a {@link SockJsService}, or {@link EndpointExporter}, and separately declare this
* FactoryBean in order to customize the properties of the (one and only)
* {@code ServerContainer} instance.
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public class ServletServerContainerFactoryBean
implements FactoryBean<WebSocketContainer>, InitializingBean, ServletContextAware {
private static final String SERVER_CONTAINER_ATTR_NAME = "javax.websocket.server.ServerContainer";
private Long asyncSendTimeout;
private Long maxSessionIdleTimeout;
private Integer maxTextMessageBufferSize;
private Integer maxBinaryMessageBufferSize;
private ServerContainer serverContainer;
public void setAsyncSendTimeout(long timeoutInMillis) {
this.asyncSendTimeout = timeoutInMillis;
}
public long getAsyncSendTimeout() {
return this.asyncSendTimeout;
}
public void setMaxSessionIdleTimeout(long timeoutInMillis) {
this.maxSessionIdleTimeout = timeoutInMillis;
}
public Long getMaxSessionIdleTimeout() {
return this.maxSessionIdleTimeout;
}
public void setMaxTextMessageBufferSize(int bufferSize) {
this.maxTextMessageBufferSize = bufferSize;
}
public Integer getMaxTextMessageBufferSize() {
return this.maxTextMessageBufferSize;
}
public void setMaxBinaryMessageBufferSize(int bufferSize) {
this.maxBinaryMessageBufferSize = bufferSize;
}
public Integer getMaxBinaryMessageBufferSize() {
return this.maxBinaryMessageBufferSize;
}
@Override
public void setServletContext(ServletContext servletContext) {
this.serverContainer = (ServerContainer) servletContext.getAttribute(SERVER_CONTAINER_ATTR_NAME);
}
@Override
public ServerContainer getObject() {
return this.serverContainer;
}
@Override
public Class<?> getObjectType() {
return ServerContainer.class;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.serverContainer,
"A ServletContext is required to access the javax.websocket.server.ServerContainer instance");
if (this.asyncSendTimeout != null) {
this.serverContainer.setAsyncSendTimeout(this.asyncSendTimeout);
}
if (this.maxSessionIdleTimeout != null) {
this.serverContainer.setDefaultMaxSessionIdleTimeout(this.maxSessionIdleTimeout);
}
if (this.maxTextMessageBufferSize != null) {
this.serverContainer.setDefaultMaxTextMessageBufferSize(this.maxTextMessageBufferSize);
}
if (this.maxBinaryMessageBufferSize != null) {
this.serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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.websocket.server.support;
import javax.websocket.WebSocketContainer;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
/**
*
* @author Rossen Stoyanchev
* @since 4.0
*/
public abstract class AbstractEndpointContainerFactoryBean implements FactoryBean<WebSocketContainer>, InitializingBean {
private WebSocketContainer container;
public void setAsyncSendTimeout(long timeoutInMillis) {
this.container.setAsyncSendTimeout(timeoutInMillis);
}
public long getAsyncSendTimeout() {
return this.container.getDefaultAsyncSendTimeout();
}
public void setMaxSessionIdleTimeout(long timeoutInMillis) {
this.container.setDefaultMaxSessionIdleTimeout(timeoutInMillis);
}
public long getMaxSessionIdleTimeout() {
return this.container.getDefaultMaxSessionIdleTimeout();
}
public void setMaxTextMessageBufferSize(int bufferSize) {
this.container.setDefaultMaxTextMessageBufferSize(bufferSize);
}
public int getMaxTextMessageBufferSize() {
return this.container.getDefaultMaxTextMessageBufferSize();
}
public void setMaxBinaryMessageBufferSize(int bufferSize) {
this.container.setDefaultMaxBinaryMessageBufferSize(bufferSize);
}
public int getMaxBinaryMessageBufferSize() {
return this.container.getDefaultMaxBinaryMessageBufferSize();
}
@Override
public void afterPropertiesSet() throws Exception {
this.container = getContainer();
}
protected abstract WebSocketContainer getContainer();
@Override
public WebSocketContainer getObject() throws Exception {
return this.container;
}
@Override
public Class<?> getObjectType() {
return WebSocketContainer.class;
}
@Override
public boolean isSingleton() {
return true;
}
}