From 4e67f809fbc1957e40fc787686b63254eaa8d7fa Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 10 Mar 2013 09:54:11 -0400 Subject: [PATCH 01/51] Add spring-websocket module --- build.gradle | 16 ++++++++++ settings.gradle | 1 + .../websocket/HandshakeRequestHandler.java | 32 +++++++++++++++++++ .../src/main/resources/.gitignore | 0 spring-websocket/src/test/java/.gitignore | 0 5 files changed, 49 insertions(+) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java create mode 100644 spring-websocket/src/main/resources/.gitignore create mode 100644 spring-websocket/src/test/java/.gitignore diff --git a/build.gradle b/build.gradle index 4a6261f4ec..e56d3060d1 100644 --- a/build.gradle +++ b/build.gradle @@ -509,6 +509,22 @@ project("spring-orm-hibernate4") { } } +project("spring-websocket") { + description = "Spring WebSocket support" + dependencies { + compile(project(":spring-core")) + compile(project(":spring-context")) + compile(project(":spring-web")) + optional("javax.websocket:javax.websocket-api:1.0-b14") + } + + repositories { + maven { url "http://repo.springsource.org/libs-release" } + maven { url "https://repository.apache.org" } // tomcat-websocket snapshot + maven { url "https://maven.java.net/content/groups/public/" } // javax.websocket-* + } +} + project("spring-webmvc") { description = "Spring Web MVC" dependencies { diff --git a/settings.gradle b/settings.gradle index 71b5e8408d..3511d2a86f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,6 +21,7 @@ include "spring-web" include "spring-webmvc" include "spring-webmvc-portlet" include "spring-webmvc-tiles3" +include "spring-websocket" // Exposes gradle buildSrc for IDE support include "buildSrc" diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java new file mode 100644 index 0000000000..35324cfd6a --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java @@ -0,0 +1,32 @@ +/* + * 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 org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; + + +/** + * + * @author Rossen Stoyanchev + */ +public interface HandshakeRequestHandler { + + + boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response); + +} diff --git a/spring-websocket/src/main/resources/.gitignore b/spring-websocket/src/main/resources/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-websocket/src/test/java/.gitignore b/spring-websocket/src/test/java/.gitignore new file mode 100644 index 0000000000..e69de29bb2 From cdd7d7bd8864b826c9ddd8cb7edb384e25083e6d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 13 Mar 2013 12:14:51 -0400 Subject: [PATCH 02/51] Add javax.websocket.Endpoint configuration support --- build.gradle | 15 +- .../org/springframework/http/HttpHeaders.java | 199 ++++++++++++++++- ...dshakeRequestHandler.java => Session.java} | 7 +- .../websocket/WebSocketHandler.java | 38 ++++ .../websocket/WebSocketHandlerAdapter.java | 47 ++++ .../support/ServerEndpointPostProcessor.java | 141 ++++++++++++ .../support/ServerEndpointRegistration.java | 204 ++++++++++++++++++ .../support/StandardSessionAdapter.java | 50 +++++ .../StandardWebSocketHandlerAdapter.java | 131 +++++++++++ 9 files changed, 819 insertions(+), 13 deletions(-) rename spring-websocket/src/main/java/org/springframework/websocket/{HandshakeRequestHandler.java => Session.java} (74%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java diff --git a/build.gradle b/build.gradle index e56d3060d1..3bb431d2ac 100644 --- a/build.gradle +++ b/build.gradle @@ -515,13 +515,24 @@ project("spring-websocket") { compile(project(":spring-core")) compile(project(":spring-context")) compile(project(":spring-web")) - optional("javax.websocket:javax.websocket-api:1.0-b14") + + optional("org.apache.tomcat:tomcat-servlet-api:8.0-SNAPSHOT") // TODO: replace with "javax.servlet:javax.servlet-api" + optional("org.apache.tomcat:tomcat-websocket-api:8.0-SNAPSHOT") // TODO: replace with "javax.websocket:javax.websocket-api" + + optional("org.apache.tomcat:tomcat-websocket:8.0-SNAPSHOT") { + exclude group: "org.apache.tomcat", module: "tomcat-websocket-api" + exclude group: "org.apache.tomcat", module: "tomcat-servlet-api" + } + + optional("org.eclipse.jetty:jetty-websocket:8.1.10.v20130312") + optional("org.glassfish.tyrus:tyrus-websocket-core:1.0-SNAPSHOT") } repositories { maven { url "http://repo.springsource.org/libs-release" } - maven { url "https://repository.apache.org" } // tomcat-websocket snapshot maven { url "https://maven.java.net/content/groups/public/" } // javax.websocket-* + maven { url "https://repository.apache.org/content/repositories/snapshots" } // tomcat-websocket snapshots + maven { url "https://maven.java.net/content/repositories/snapshots" } // tyrus/glassfish snapshots } } diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 5d480d5e4b..e972709572 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -17,14 +17,10 @@ package org.springframework.http; import java.io.Serializable; - import java.net.URI; - import java.nio.charset.Charset; - import java.text.ParseException; import java.text.SimpleDateFormat; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -40,6 +36,7 @@ import java.util.Set; import java.util.TimeZone; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -71,6 +68,8 @@ public class HttpHeaders implements MultiValueMap, Serializable private static final String CACHE_CONTROL = "Cache-Control"; + private static final String CONNECTION = "Connection"; + private static final String CONTENT_DISPOSITION = "Content-Disposition"; private static final String CONTENT_LENGTH = "Content-Length"; @@ -91,8 +90,22 @@ public class HttpHeaders implements MultiValueMap, Serializable private static final String LOCATION = "Location"; + private static final String ORIGIN = "Origin"; + + private static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; + + private static final String SEC_WEBSOCKET_EXTENSIONS = "Sec-WebSocket-Extensions"; + + private static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; + + private static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + + private static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; + private static final String PRAGMA = "Pragma"; + private static final String UPGARDE = "Upgrade"; + private static final String[] DATE_FORMATS = new String[] { "EEE, dd MMM yyyy HH:mm:ss zzz", @@ -251,6 +264,30 @@ public class HttpHeaders implements MultiValueMap, Serializable return getFirst(CACHE_CONTROL); } + /** + * Sets the (new) value of the {@code Connection} header. + * @param connection the value of the header + */ + public void setConnection(String connection) { + set(CONNECTION, connection); + } + + /** + * Sets the (new) value of the {@code Connection} header. + * @param connection the value of the header + */ + public void setConnection(List connection) { + set(CONNECTION, toCommaDelimitedString(connection)); + } + + /** + * Returns the value of the {@code Connection} header. + * @return the value of the header + */ + public List getConnection() { + return getFirstValueAsList(CONNECTION); + } + /** * Sets the (new) value of the {@code Content-Disposition} header for {@code form-data}. * @param name the control name @@ -393,15 +430,19 @@ public class HttpHeaders implements MultiValueMap, Serializable * @param ifNoneMatchList the new value of the header */ public void setIfNoneMatch(List ifNoneMatchList) { + set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList)); + } + + private String toCommaDelimitedString(List list) { StringBuilder builder = new StringBuilder(); - for (Iterator iterator = ifNoneMatchList.iterator(); iterator.hasNext();) { + for (Iterator iterator = list.iterator(); iterator.hasNext();) { String ifNoneMatch = iterator.next(); builder.append(ifNoneMatch); if (iterator.hasNext()) { builder.append(", "); } } - set(IF_NONE_MATCH, builder.toString()); + return builder.toString(); } /** @@ -409,9 +450,13 @@ public class HttpHeaders implements MultiValueMap, Serializable * @return the header value */ public List getIfNoneMatch() { + return getFirstValueAsList(IF_NONE_MATCH); + } + + private List getFirstValueAsList(String header) { List result = new ArrayList(); - String value = getFirst(IF_NONE_MATCH); + String value = getFirst(header); if (value != null) { String[] tokens = value.split(",\\s*"); for (String token : tokens) { @@ -457,6 +502,130 @@ public class HttpHeaders implements MultiValueMap, Serializable return (value != null ? URI.create(value) : null); } + /** + * Sets the (new) value of the {@code Origin} header. + * @param origin the value of the header + */ + public void setOrigin(String origin) { + set(ORIGIN, origin); + } + + /** + * Returns the value of the {@code Origin} header. + * @return the value of the header + */ + public String getOrigin() { + return getFirst(ORIGIN); + } + + /** + * Sets the (new) value of the {@code Sec-WebSocket-Accept} header. + * @param secWebSocketAccept the value of the header + */ + public void setSecWebSocketAccept(String secWebSocketAccept) { + set(SEC_WEBSOCKET_ACCEPT, secWebSocketAccept); + } + + /** + * Returns the value of the {@code Sec-WebSocket-Accept} header. + * @return the value of the header + */ + public String getSecWebSocketAccept() { + return getFirst(SEC_WEBSOCKET_ACCEPT); + } + + /** + * Returns the value of the {@code Sec-WebSocket-Extensions} header. + * @return the value of the header + */ + public List getSecWebSocketExtensions() { + List values = get(SEC_WEBSOCKET_EXTENSIONS); + if (CollectionUtils.isEmpty(values)) { + return Collections.emptyList(); + } + else if (values.size() == 1) { + return getFirstValueAsList(SEC_WEBSOCKET_EXTENSIONS); + } + else { + return values; + } + } + + /** + * Sets the (new) value of the {@code Sec-WebSocket-Extensions} header. + * @param secWebSocketExtensions the value of the header + */ + public void setSecWebSocketExtensions(List secWebSocketExtensions) { + set(SEC_WEBSOCKET_EXTENSIONS, toCommaDelimitedString(secWebSocketExtensions)); + } + + /** + * Sets the (new) value of the {@code Sec-WebSocket-Key} header. + * @param secWebSocketKey the value of the header + */ + public void setSecWebSocketKey(String secWebSocketKey) { + set(SEC_WEBSOCKET_KEY, secWebSocketKey); + } + + /** + * Returns the value of the {@code Sec-WebSocket-Key} header. + * @return the value of the header + */ + public String getSecWebSocketKey() { + return getFirst(SEC_WEBSOCKET_KEY); + } + + /** + * Sets the (new) value of the {@code Sec-WebSocket-Protocol} header. + * @param secWebSocketProtocol the value of the header + */ + public void setSecWebSocketProtocol(String secWebSocketProtocol) { + if (secWebSocketProtocol != null) { + set(SEC_WEBSOCKET_PROTOCOL, secWebSocketProtocol); + } + } + + /** + * Sets the (new) value of the {@code Sec-WebSocket-Protocol} header. + * @param secWebSocketProtocols the value of the header + */ + public void setSecWebSocketProtocol(List secWebSocketProtocols) { + set(SEC_WEBSOCKET_PROTOCOL, toCommaDelimitedString(secWebSocketProtocols)); + } + + /** + * Returns the value of the {@code Sec-WebSocket-Key} header. + * @return the value of the header + */ + public List getSecWebSocketProtocol() { + List values = get(SEC_WEBSOCKET_PROTOCOL); + if (CollectionUtils.isEmpty(values)) { + return Collections.emptyList(); + } + else if (values.size() == 1) { + return getFirstValueAsList(SEC_WEBSOCKET_PROTOCOL); + } + else { + return values; + } + } + + /** + * Sets the (new) value of the {@code Sec-WebSocket-Version} header. + * @param secWebSocketKey the value of the header + */ + public void setSecWebSocketVersion(String secWebSocketVersion) { + set(SEC_WEBSOCKET_VERSION, secWebSocketVersion); + } + + /** + * Returns the value of the {@code Sec-WebSocket-Version} header. + * @return the value of the header + */ + public String getSecWebSocketVersion() { + return getFirst(SEC_WEBSOCKET_VERSION); + } + /** * Sets the (new) value of the {@code Pragma} header. * @param pragma the value of the header @@ -473,6 +642,22 @@ public class HttpHeaders implements MultiValueMap, Serializable return getFirst(PRAGMA); } + /** + * Sets the (new) value of the {@code Upgrade} header. + * @param upgrade the value of the header + */ + public void setUpgrade(String upgrade) { + set(UPGARDE, upgrade); + } + + /** + * Returns the value of the {@code Upgrade} header. + * @return the value of the header + */ + public String getUpgrade() { + return getFirst(UPGARDE); + } + // Utility methods private long getFirstDate(String headerName) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/Session.java similarity index 74% rename from spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/Session.java index 35324cfd6a..c7ccb12650 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/Session.java @@ -16,17 +16,16 @@ package org.springframework.websocket; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; /** * * @author Rossen Stoyanchev */ -public interface HandshakeRequestHandler { +public interface Session { + void sendText(String text) throws Exception; - boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response); + void close(int code, String reason) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java new file mode 100644 index 0000000000..b4bfb6c1e4 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -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; + +import java.io.InputStream; + + +/** + * + * @author Rossen Stoyanchev + */ +public interface WebSocketHandler { + + void newSession(Session session) throws Exception; + + void handleTextMessage(Session session, String message) throws Exception; + + void handleBinaryMessage(Session session, InputStream message) throws Exception; + + void handleException(Session session, Throwable exception); + + void sessionClosed(Session session, int statusCode, String reason) throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java new file mode 100644 index 0000000000..a1f4154dc3 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -0,0 +1,47 @@ +/* + * 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.io.InputStream; + +/** + * + * @author Rossen Stoyanchev + */ +public class WebSocketHandlerAdapter implements WebSocketHandler { + + @Override + public void newSession(Session session) throws Exception { + } + + @Override + public void handleTextMessage(Session session, String message) throws Exception { + } + + @Override + public void handleBinaryMessage(Session session, InputStream message) throws Exception { + } + + @Override + public void handleException(Session session, Throwable exception) { + } + + @Override + public void sessionClosed(Session session, int statusCode, String reason) throws Exception { + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java new file mode 100644 index 0000000000..fc08356b07 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java @@ -0,0 +1,141 @@ +/* + * 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.support; + +import javax.servlet.ServletContext; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerContainerProvider; +import javax.websocket.server.ServerEndpointConfig; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.tomcat.websocket.server.WsServerContainer; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.util.Assert; +import org.springframework.web.context.ServletContextAware; + +/** + * BeanPostProcessor that registers {@link javax.websocket.server.ServerEndpointConfig} + * beans with a standard Java WebSocket runtime and also configures the underlying + * {@link javax.websocket.server.ServerContainer}. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class ServerEndpointPostProcessor implements ServletContextAware, BeanPostProcessor, InitializingBean { + + private static Log logger = LogFactory.getLog(ServerEndpointPostProcessor.class); + + private Long maxSessionIdleTimeout; + + private Integer maxTextMessageBufferSize; + + private Integer maxBinaryMessageBufferSize; + + private ServletContext servletContext; + + + /** + * 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; + } + + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public void afterPropertiesSet() throws Exception { + + ServerContainer serverContainer = ServerContainerProvider.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); + } + + // TODO: this is necessary but only done on Tomcat + WsServerContainer sc = WsServerContainer.getServerContainer(); + sc.setServletContext(this.servletContext); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ServerEndpointConfig) { + ServerEndpointConfig sec = (ServerEndpointConfig) bean; + ServerContainer serverContainer = ServerContainerProvider.getServerContainer(); + try { + logger.debug("Registering javax.websocket.Endpoint for path " + sec.getPath()); + serverContainer.addEndpoint(sec); + } + catch (DeploymentException e) { + throw new IllegalStateException("Failed to deploy Endpoint " + bean, e); + } + } + return bean; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java new file mode 100644 index 0000000000..22ee961ea6 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java @@ -0,0 +1,204 @@ +/* + * 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.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.websocket.Decoder; +import javax.websocket.Encoder; +import javax.websocket.Endpoint; +import javax.websocket.Extension; +import javax.websocket.HandshakeResponse; +import javax.websocket.server.HandshakeRequest; +import javax.websocket.server.ServerEndpointConfig; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.websocket.WebSocketHandler; + + +/** + * An implementation of {@link javax.websocket.server.ServerEndpointConfig} that also + * holds the target {@link javax.websocket.Endpoint} as a reference or a bean name. + * The target can also be {@link org.springframework.websocket.WebSocketHandler}, in + * which case it will be adapted via {@link StandardWebSocketHandlerAdapter}. + * + *

+ * Beans of this type are detected by {@link ServerEndpointPostProcessor} and + * registered with a Java WebSocket runtime at startup. + * + * @author Rossen Stoyanchev + */ +public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFactoryAware { + + private final String path; + + private final Object bean; + + private List subprotocols = new ArrayList(); + + private List extensions = new ArrayList(); + + private Map userProperties = new HashMap(); + + private BeanFactory beanFactory; + + private final Configurator configurator = new Configurator() {}; + + + public ServerEndpointRegistration(String path, String beanName) { + Assert.hasText(path, "path must not be empty"); + Assert.notNull(beanName, "beanName is required"); + this.path = path; + this.bean = beanName; + } + + public ServerEndpointRegistration(String path, Object bean) { + Assert.hasText(path, "path must not be empty"); + Assert.notNull(bean, "bean is required"); + this.path = path; + this.bean = bean; + } + + @Override + public String getPath() { + return this.path; + } + + @SuppressWarnings("unchecked") + @Override + public Class getEndpointClass() { + Class beanClass = this.bean.getClass(); + if (beanClass.equals(String.class)) { + beanClass = this.beanFactory.getType((String) this.bean); + } + beanClass = ClassUtils.getUserClass(beanClass); + if (WebSocketHandler.class.isAssignableFrom(beanClass)) { + return StandardWebSocketHandlerAdapter.class; + } + else { + return (Class) beanClass; + } + } + + protected Endpoint getEndpoint() { + Object bean = this.bean; + if (this.bean instanceof String) { + bean = this.beanFactory.getBean((String) this.bean); + } + if (bean instanceof WebSocketHandler) { + return new StandardWebSocketHandlerAdapter((WebSocketHandler) bean); + } + else { + return (Endpoint) bean; + } + } + + @Override + public List getSubprotocols() { + return this.subprotocols; + } + + public void setSubprotocols(List subprotocols) { + this.subprotocols = subprotocols; + } + + @Override + public List getExtensions() { + return this.extensions; + } + + public void setExtensions(List extensions) { + // TODO: verify against ServerContainer.getInstalledExtensions() + this.extensions = extensions; + } + + @Override + public Map getUserProperties() { + return this.userProperties; + } + + public void setUserProperties(Map userProperties) { + this.userProperties = userProperties; + } + + @Override + public List> getEncoders() { + return Collections.emptyList(); + } + + @Override + public List> getDecoders() { + return Collections.emptyList(); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + public Configurator getConfigurator() { + return new Configurator() { + @SuppressWarnings("unchecked") + @Override + public T getEndpointInstance(Class clazz) throws InstantiationException { + return (T) ServerEndpointRegistration.this.getEndpoint(); + } + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { + ServerEndpointRegistration.this.modifyHandshake(request, response); + } + @Override + public boolean checkOrigin(String originHeaderValue) { + return ServerEndpointRegistration.this.checkOrigin(originHeaderValue); + } + @Override + public String getNegotiatedSubprotocol(List supported, List requested) { + return ServerEndpointRegistration.this.selectSubProtocol(requested); + } + @Override + public List getNegotiatedExtensions(List installed, List requested) { + return ServerEndpointRegistration.this.selectExtensions(requested); + } + }; + } + + protected void modifyHandshake(HandshakeRequest request, HandshakeResponse response) { + this.configurator.modifyHandshake(this, request, response); + } + + protected boolean checkOrigin(String originHeaderValue) { + return this.configurator.checkOrigin(originHeaderValue); + } + + protected String selectSubProtocol(List requested) { + return this.configurator.getNegotiatedSubprotocol(getSubprotocols(), requested); + } + + protected List selectExtensions(List requested) { + return this.configurator.getNegotiatedExtensions(getExtensions(), requested); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java new file mode 100644 index 0000000000..e4f8d64501 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.websocket.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.websocket.Session; + + +/** + * + * @author Rossen Stoyanchev + */ +public class StandardSessionAdapter implements Session { + + private static Log logger = LogFactory.getLog(StandardSessionAdapter.class); + + private javax.websocket.Session sourceSession; + + + public StandardSessionAdapter(javax.websocket.Session sourceSession) { + this.sourceSession = sourceSession; + } + + @Override + public void sendText(String text) throws Exception { + logger.trace("Sending text message: " + text); + this.sourceSession.getBasicRemote().sendText(text); + } + + @Override + public void close(int code, String reason) throws Exception { + this.sourceSession = null; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java new file mode 100644 index 0000000000..0e1c99eabc --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java @@ -0,0 +1,131 @@ +/* + * 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.support; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.websocket.Session; +import org.springframework.websocket.WebSocketHandler; + + +/** + * + * @author Rossen Stoyanchev + */ +public class StandardWebSocketHandlerAdapter extends Endpoint { + + private static Log logger = LogFactory.getLog(StandardWebSocketHandlerAdapter.class); + + private final WebSocketHandler webSocketHandler; + + private final Map sessionMap = new ConcurrentHashMap(); + + + public StandardWebSocketHandlerAdapter(WebSocketHandler webSocketHandler) { + this.webSocketHandler = webSocketHandler; + } + + @Override + public void onOpen(javax.websocket.Session sourceSession, EndpointConfig config) { + logger.debug("New WebSocket session: " + sourceSession); + try { + Session session = new StandardSessionAdapter(sourceSession); + this.sessionMap.put(sourceSession.getId(), session); + sourceSession.addMessageHandler(new StandardMessageHandler(sourceSession.getId())); + this.webSocketHandler.newSession(session); + } + catch (Throwable ex) { + // TODO + logger.error("Error while processing new session", ex); + } + } + + @Override + public void onClose(javax.websocket.Session sourceSession, CloseReason closeReason) { + String id = sourceSession.getId(); + if (logger.isDebugEnabled()) { + logger.debug("Closing session: " + sourceSession + ", " + closeReason); + } + try { + Session session = getSession(id); + this.sessionMap.remove(id); + int code = closeReason.getCloseCode().getCode(); + String reason = closeReason.getReasonPhrase(); + session.close(code, reason); + this.webSocketHandler.sessionClosed(session, code, reason); + } + catch (Throwable ex) { + // TODO + logger.error("Error while processing session closing", ex); + } + } + + @Override + public void onError(javax.websocket.Session sourceSession, Throwable exception) { + logger.error("Error for WebSocket session: " + sourceSession.getId(), exception); + try { + Session session = getSession(sourceSession.getId()); + this.webSocketHandler.handleException(session, exception); + } + catch (Throwable ex) { + // TODO + logger.error("Failed to handle error", ex); + } + } + + private Session getSession(String sourceSessionId) { + Session session = this.sessionMap.get(sourceSessionId); + Assert.notNull(session, "No session"); + return session; + } + + + private class StandardMessageHandler implements MessageHandler.Whole { + + private final String sourceSessionId; + + public StandardMessageHandler(String sourceSessionId) { + this.sourceSessionId = sourceSessionId; + } + + @Override + public void onMessage(String message) { + if (logger.isTraceEnabled()) { + logger.trace("Message for session [" + this.sourceSessionId + "]: " + message); + } + try { + Session session = getSession(this.sourceSessionId); + StandardWebSocketHandlerAdapter.this.webSocketHandler.handleTextMessage(session, message); + } + catch (Throwable ex) { + // TODO + logger.error("Error while processing message", ex); + } + } + + } + +} From 715018fe75ccfe6c8088f25c4d7c237003e3dcc4 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 27 Mar 2013 14:17:47 -0400 Subject: [PATCH 03/51] Add handshake request handler abstraction --- .../AbstractHandshakeRequestHandler.java | 174 ++++++++++++++++++ .../websocket/HandshakeRequestHandler.java | 33 ++++ .../springframework/websocket/Session.java | 1 + .../websocket/WebSocketHandler.java | 1 + .../websocket/WebSocketHandlerAdapter.java | 1 + .../ServletServerEndpointPostProcessor.java | 54 ++++++ .../TomcatHandshakeRequestHandler.java | 81 ++++++++ .../support/ServerEndpointPostProcessor.java | 21 +-- .../support/ServerEndpointRegistration.java | 1 + .../support/StandardSessionAdapter.java | 1 + .../StandardWebSocketHandlerAdapter.java | 1 + 11 files changed, 349 insertions(+), 20 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java diff --git a/spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java new file mode 100644 index 0000000000..696b0bf7ce --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java @@ -0,0 +1,174 @@ +/* + * 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.io.IOException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.xml.bind.DatatypeConverter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.util.UriComponentsBuilder; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractHandshakeRequestHandler implements HandshakeRequestHandler { + + private static final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + protected Log logger = LogFactory.getLog(getClass()); + + private List protocols; + + + public void setProtocols(String... protocols) { + this.protocols = Arrays.asList(protocols); + } + + public String[] getProtocols() { + return this.protocols.toArray(new String[this.protocols.size()]); + } + + @Override + public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception { + + logger.debug("Starting handshake for " + request.getURI()); + + if (!HttpMethod.GET.equals(request.getMethod())) { + response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED); + response.getHeaders().setAllow(Collections.singleton(HttpMethod.GET)); + logger.debug("Only HTTP GET is allowed, current method is " + request.getMethod()); + return false; + } + if (!validateUpgradeHeader(request, response)) { + return false; + } + if (!validateConnectHeader(request, response)) { + return false; + } + if (!validateWebSocketVersion(request, response)) { + return false; + } + if (!validateOrigin(request, response)) { + return false; + } + String wsKey = request.getHeaders().getSecWebSocketKey(); + if (wsKey == null) { + logger.debug("Missing \"Sec-WebSocket-Key\" header"); + response.setStatusCode(HttpStatus.BAD_REQUEST); + return false; + } + String protocol = selectProtocol(request.getHeaders().getSecWebSocketProtocol()); + // TODO: request.getHeaders().getSecWebSocketExtensions()) + + response.setStatusCode(HttpStatus.SWITCHING_PROTOCOLS); + response.getHeaders().setUpgrade("WebSocket"); + response.getHeaders().setConnection("Upgrade"); + response.getHeaders().setSecWebSocketProtocol(protocol); + response.getHeaders().setSecWebSocketAccept(getWebSocketKeyHash(wsKey)); + // TODO: response.getHeaders().setSecWebSocketExtensions(extensions); + + logger.debug("Successfully negotiated WebSocket handshake"); + + // TODO: surely there is a better way to flush the headers + response.getBody(); + + doHandshakeInternal(request, response, protocol); + + return true; + } + + protected boolean validateUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + if (!"WebSocket".equalsIgnoreCase(request.getHeaders().getUpgrade())) { + response.setStatusCode(HttpStatus.BAD_REQUEST); + response.getBody().write("Can \"Upgrade\" only to \"websocket\".".getBytes("UTF-8")); + logger.debug("Invalid Upgrade header " + request.getHeaders().getUpgrade()); + return false; + } + return true; + } + + protected boolean validateConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + if (!request.getHeaders().getConnection().contains("Upgrade")) { + response.setStatusCode(HttpStatus.BAD_REQUEST); + response.getBody().write("\"Connection\" must be \"upgrade\".".getBytes("UTF-8")); + logger.debug("Invalid Connection header " + request.getHeaders().getConnection()); + return false; + } + return true; + } + + protected boolean validateWebSocketVersion(ServerHttpRequest request, ServerHttpResponse response) { + if (!"13".equals(request.getHeaders().getSecWebSocketVersion())) { + response.setStatusCode(HttpStatus.UPGRADE_REQUIRED); + response.getHeaders().set("Sec-WebSocket-Version", "13"); + logger.debug("WebSocket version not supported " + request.getHeaders().get("Sec-WebSocket-Version")); + return false; + } + return true; + } + + protected boolean validateOrigin(ServerHttpRequest request, ServerHttpResponse response) { + String origin = request.getHeaders().getOrigin(); + if (origin != null) { + UriComponentsBuilder originUriBuilder = UriComponentsBuilder.fromHttpUrl(origin); + + // TODO + // Check scheme, port, and host against list of configured origins (allow wild cards in the host?) + // Another strategy might be to match current request's scheme/port/host + + // response.setStatusCode(HttpStatus.FORBIDDEN); + // return false; + } + return true; + } + + protected String selectProtocol(List requestedProtocols) { + if (requestedProtocols != null) { + for (String p : requestedProtocols) { + if (this.protocols.contains(p)) { + return p; + } + } + } + return null; + } + + private String getWebSocketKeyHash(String key) throws NoSuchAlgorithmException { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + byte[] bytes = digest.digest((key + GUID).getBytes(Charset.forName("ISO-8859-1"))); + return DatatypeConverter.printBase64Binary(bytes); + } + + protected abstract void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol) + throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java new file mode 100644 index 0000000000..e40aa72c3b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java @@ -0,0 +1,33 @@ +/* + * 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 org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface HandshakeRequestHandler { + + + boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/Session.java b/spring-websocket/src/main/java/org/springframework/websocket/Session.java index c7ccb12650..a6104614ee 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/Session.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/Session.java @@ -21,6 +21,7 @@ package org.springframework.websocket; /** * * @author Rossen Stoyanchev + * @since 4.0 */ public interface Session { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index b4bfb6c1e4..7a71ab595b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -22,6 +22,7 @@ import java.io.InputStream; /** * * @author Rossen Stoyanchev + * @since 4.0 */ public interface WebSocketHandler { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index a1f4154dc3..032abc6b46 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -21,6 +21,7 @@ import java.io.InputStream; /** * * @author Rossen Stoyanchev + * @since 4.0 */ public class WebSocketHandlerAdapter implements WebSocketHandler { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java b/spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java new file mode 100644 index 0000000000..bcf288f9cf --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java @@ -0,0 +1,54 @@ +/* + * 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.servlet; + +import javax.servlet.ServletContext; + +import org.apache.tomcat.websocket.server.WsServerContainer; +import org.springframework.web.context.ServletContextAware; +import org.springframework.websocket.support.ServerEndpointPostProcessor; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class ServletServerEndpointPostProcessor extends ServerEndpointPostProcessor implements ServletContextAware { + + private ServletContext servletContext; + + + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public void afterPropertiesSet() throws Exception { + // TODO: remove hard dependency on Tomcat (see Tomcat's WsListener) + WsServerContainer sc = WsServerContainer.getServerContainer(); + sc.setServletContext(this.servletContext); + + super.afterPropertiesSet(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java new file mode 100644 index 0000000000..5dabaf0f6b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java @@ -0,0 +1,81 @@ +/* + * 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.servlet; + +import java.lang.reflect.Method; +import java.util.Collections; + +import javax.servlet.http.HttpServletRequest; +import javax.websocket.Endpoint; +import javax.websocket.server.ServerEndpointConfig; + +import org.apache.tomcat.websocket.server.WsHandshakeRequest; +import org.apache.tomcat.websocket.server.WsHttpUpgradeHandler; +import org.apache.tomcat.websocket.server.WsServerContainer; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.websocket.AbstractHandshakeRequestHandler; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.support.ServerEndpointRegistration; +import org.springframework.websocket.support.StandardWebSocketHandlerAdapter; + + +/** +* +* @author Rossen Stoyanchev + * @since 4.0 +*/ +public class TomcatHandshakeRequestHandler extends AbstractHandshakeRequestHandler { + + private final Endpoint endpoint; + + private final ServerEndpointConfig endpointConfig; + + + public TomcatHandshakeRequestHandler(WebSocketHandler webSocketHandler) { + this.endpoint = new StandardWebSocketHandlerAdapter(webSocketHandler); + this.endpointConfig = new ServerEndpointRegistration("/shouldnotmatter", this.endpoint); + } + + public TomcatHandshakeRequestHandler(Endpoint endpoint) { + this.endpoint = endpoint; + this.endpointConfig = new ServerEndpointRegistration("/shouldnotmatter", this.endpoint); + } + + @Override + public void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol) throws Exception { + + logger.debug("Upgrading HTTP request"); + + Assert.isTrue(request instanceof ServletServerHttpRequest); + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + + WsHandshakeRequest wsRequest = new WsHandshakeRequest(servletRequest); + Method method = ReflectionUtils.findMethod(WsHandshakeRequest.class, "finished"); + ReflectionUtils.makeAccessible(method); + method.invoke(wsRequest); + + WsHttpUpgradeHandler wsHandler = servletRequest.upgrade(WsHttpUpgradeHandler.class); + + wsHandler.preInit(this.endpoint, this.endpointConfig, WsServerContainer.getServerContainer(), + wsRequest, protocol, Collections. emptyMap(), servletRequest.isSecure()); + } + +} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java index fc08356b07..adaf3b69e8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java @@ -15,7 +15,6 @@ */ package org.springframework.websocket.support; -import javax.servlet.ServletContext; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerContainerProvider; @@ -23,12 +22,10 @@ import javax.websocket.server.ServerEndpointConfig; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.tomcat.websocket.server.WsServerContainer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.util.Assert; -import org.springframework.web.context.ServletContextAware; /** * BeanPostProcessor that registers {@link javax.websocket.server.ServerEndpointConfig} @@ -38,7 +35,7 @@ import org.springframework.web.context.ServletContextAware; * @author Rossen Stoyanchev * @since 4.0 */ -public class ServerEndpointPostProcessor implements ServletContextAware, BeanPostProcessor, InitializingBean { +public class ServerEndpointPostProcessor implements BeanPostProcessor, InitializingBean { private static Log logger = LogFactory.getLog(ServerEndpointPostProcessor.class); @@ -48,8 +45,6 @@ public class ServerEndpointPostProcessor implements ServletContextAware, BeanPos private Integer maxBinaryMessageBufferSize; - private ServletContext servletContext; - /** * If this property set it is in turn used to configure @@ -87,18 +82,8 @@ public class ServerEndpointPostProcessor implements ServletContextAware, BeanPos return this.maxBinaryMessageBufferSize; } - @Override - public void setServletContext(ServletContext servletContext) { - this.servletContext = servletContext; - } - - public ServletContext getServletContext() { - return servletContext; - } - @Override public void afterPropertiesSet() throws Exception { - ServerContainer serverContainer = ServerContainerProvider.getServerContainer(); Assert.notNull(serverContainer, "javax.websocket.server.ServerContainer not available"); @@ -111,10 +96,6 @@ public class ServerEndpointPostProcessor implements ServletContextAware, BeanPos if (this.maxBinaryMessageBufferSize != null) { serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize); } - - // TODO: this is necessary but only done on Tomcat - WsServerContainer sc = WsServerContainer.getServerContainer(); - sc.setServletContext(this.servletContext); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java index 22ee961ea6..22d0386771 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java @@ -49,6 +49,7 @@ import org.springframework.websocket.WebSocketHandler; * registered with a Java WebSocket runtime at startup. * * @author Rossen Stoyanchev + * @since 4.0 */ public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFactoryAware { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java index e4f8d64501..32f04f74be 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java @@ -24,6 +24,7 @@ import org.springframework.websocket.Session; /** * * @author Rossen Stoyanchev + * @since 4.0 */ public class StandardSessionAdapter implements Session { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java index 0e1c99eabc..7667487022 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java @@ -34,6 +34,7 @@ import org.springframework.websocket.WebSocketHandler; /** * * @author Rossen Stoyanchev + * @since 4.0 */ public class StandardWebSocketHandlerAdapter extends Endpoint { From 741927664c456bd2ab5f9710ef4758092754457d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 28 Mar 2013 09:45:33 -0400 Subject: [PATCH 04/51] Update package structure --- .../StandardSessionAdapter.java | 2 +- .../StandardWebSocketHandlerAdapter.java | 2 +- .../websocket/endpoint/package-info.java | 8 ++++++++ .../AbstractHandshakeRequestHandler.java | 2 +- .../{ => server}/HandshakeRequestHandler.java | 2 +- .../endpoint/ServerEndpointExporter.java} | 11 ++++++----- .../endpoint}/ServerEndpointRegistration.java | 5 +++-- .../ServletServerEndpointExporter.java} | 5 ++--- .../TomcatHandshakeRequestHandler.java | 18 ++++++++---------- .../server/endpoint/package-info.java | 8 ++++++++ 10 files changed, 39 insertions(+), 24 deletions(-) rename spring-websocket/src/main/java/org/springframework/websocket/{support => endpoint}/StandardSessionAdapter.java (96%) rename spring-websocket/src/main/java/org/springframework/websocket/{support => endpoint}/StandardWebSocketHandlerAdapter.java (98%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java rename spring-websocket/src/main/java/org/springframework/websocket/{ => server}/AbstractHandshakeRequestHandler.java (99%) rename spring-websocket/src/main/java/org/springframework/websocket/{ => server}/HandshakeRequestHandler.java (95%) rename spring-websocket/src/main/java/org/springframework/websocket/{support/ServerEndpointPostProcessor.java => server/endpoint/ServerEndpointExporter.java} (90%) rename spring-websocket/src/main/java/org/springframework/websocket/{support => server/endpoint}/ServerEndpointRegistration.java (96%) rename spring-websocket/src/main/java/org/springframework/websocket/{servlet/ServletServerEndpointPostProcessor.java => server/endpoint/ServletServerEndpointExporter.java} (85%) rename spring-websocket/src/main/java/org/springframework/websocket/{servlet => server/endpoint}/TomcatHandshakeRequestHandler.java (82%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardSessionAdapter.java similarity index 96% rename from spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardSessionAdapter.java index 32f04f74be..4366ec026b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardSessionAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.websocket.endpoint; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java index 7667487022..608be8f932 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.websocket.endpoint; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java new file mode 100644 index 0000000000..1e5962b911 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java @@ -0,0 +1,8 @@ + +/** + * + * Support for working with standard Java WebSocket Endpoint's. + * + */ +package org.springframework.websocket.endpoint; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeRequestHandler.java similarity index 99% rename from spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeRequestHandler.java index 696b0bf7ce..0f8de38855 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/AbstractHandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeRequestHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.websocket.server; import java.io.IOException; import java.nio.charset.Charset; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeRequestHandler.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeRequestHandler.java index e40aa72c3b..9e2f9f1962 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/HandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeRequestHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.websocket.server; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointExporter.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointExporter.java index adaf3b69e8..bb82114484 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointPostProcessor.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointExporter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.websocket.server.endpoint; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; @@ -28,16 +28,17 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.util.Assert; /** - * BeanPostProcessor that registers {@link javax.websocket.server.ServerEndpointConfig} - * beans with a standard Java WebSocket runtime and also configures the underlying + * BeanPostProcessor that detects beans of type + * {@link javax.websocket.server.ServerEndpointConfig} and registers them with a standard + * Java WebSocket runtime and also configures the underlying * {@link javax.websocket.server.ServerContainer}. * * @author Rossen Stoyanchev * @since 4.0 */ -public class ServerEndpointPostProcessor implements BeanPostProcessor, InitializingBean { +public class ServerEndpointExporter implements BeanPostProcessor, InitializingBean { - private static Log logger = LogFactory.getLog(ServerEndpointPostProcessor.class); + private static Log logger = LogFactory.getLog(ServerEndpointExporter.class); private Long maxSessionIdleTimeout; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointRegistration.java similarity index 96% rename from spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointRegistration.java index 22d0386771..8179253e6b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/ServerEndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointRegistration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.websocket.server.endpoint; import java.util.ArrayList; import java.util.Collections; @@ -36,6 +36,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; /** @@ -45,7 +46,7 @@ import org.springframework.websocket.WebSocketHandler; * which case it will be adapted via {@link StandardWebSocketHandlerAdapter}. * *

- * Beans of this type are detected by {@link ServerEndpointPostProcessor} and + * Beans of this type are detected by {@link ServerEndpointExporter} and * registered with a Java WebSocket runtime at startup. * * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerEndpointExporter.java similarity index 85% rename from spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerEndpointExporter.java index bcf288f9cf..2c7ca73808 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/servlet/ServletServerEndpointPostProcessor.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerEndpointExporter.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package org.springframework.websocket.servlet; +package org.springframework.websocket.server.endpoint; import javax.servlet.ServletContext; import org.apache.tomcat.websocket.server.WsServerContainer; import org.springframework.web.context.ServletContextAware; -import org.springframework.websocket.support.ServerEndpointPostProcessor; /** @@ -28,7 +27,7 @@ import org.springframework.websocket.support.ServerEndpointPostProcessor; * @author Rossen Stoyanchev * @since 4.0 */ -public class ServletServerEndpointPostProcessor extends ServerEndpointPostProcessor implements ServletContextAware { +public class ServletServerEndpointExporter extends ServerEndpointExporter implements ServletContextAware { private ServletContext servletContext; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java similarity index 82% rename from spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java index 5dabaf0f6b..a185f67ad3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/servlet/TomcatHandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.servlet; +package org.springframework.websocket.server.endpoint; import java.lang.reflect.Method; import java.util.Collections; @@ -31,17 +31,16 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; -import org.springframework.websocket.AbstractHandshakeRequestHandler; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.ServerEndpointRegistration; -import org.springframework.websocket.support.StandardWebSocketHandlerAdapter; +import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; +import org.springframework.websocket.server.AbstractHandshakeRequestHandler; /** -* -* @author Rossen Stoyanchev + * + * @author Rossen Stoyanchev * @since 4.0 -*/ + */ public class TomcatHandshakeRequestHandler extends AbstractHandshakeRequestHandler { private final Endpoint endpoint; @@ -50,13 +49,12 @@ public class TomcatHandshakeRequestHandler extends AbstractHandshakeRequestHandl public TomcatHandshakeRequestHandler(WebSocketHandler webSocketHandler) { - this.endpoint = new StandardWebSocketHandlerAdapter(webSocketHandler); - this.endpointConfig = new ServerEndpointRegistration("/shouldnotmatter", this.endpoint); + this(new StandardWebSocketHandlerAdapter(webSocketHandler)); } public TomcatHandshakeRequestHandler(Endpoint endpoint) { this.endpoint = endpoint; - this.endpointConfig = new ServerEndpointRegistration("/shouldnotmatter", this.endpoint); + this.endpointConfig = new ServerEndpointRegistration("/dummy", this.endpoint); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java new file mode 100644 index 0000000000..a5f66584f8 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java @@ -0,0 +1,8 @@ + +/** + * + * Server-specific support for working with standard Java WebSocket Endpoint's. + * + */ +package org.springframework.websocket.server.endpoint; + From 30ab5953f973e56bbcdc971c700657da5cc2e446 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 28 Mar 2013 12:03:33 -0400 Subject: [PATCH 05/51] Add EndpointRequestUpgradeStrategy Now there is just one EndpointHandshakeRequestHandler that works on different runtimes. --- ...intExporter.java => EndpointExporter.java} | 6 +- .../EndpointHandshakeRequestHandler.java | 83 +++++++++++++++++++ ...tration.java => EndpointRegistration.java} | 20 ++--- .../EndpointRequestUpgradeStrategy.java | 35 ++++++++ ...rter.java => ServletEndpointExporter.java} | 5 +- .../TomcatHandshakeRequestHandler.java | 79 ------------------ .../TomcatRequestUpgradeStrategy.java | 61 ++++++++++++++ .../server/endpoint/package-info.java | 2 +- 8 files changed, 197 insertions(+), 94 deletions(-) rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{ServerEndpointExporter.java => EndpointExporter.java} (94%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{ServerEndpointRegistration.java => EndpointRegistration.java} (89%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{ServletServerEndpointExporter.java => ServletEndpointExporter.java} (88%) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointExporter.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java index bb82114484..5a601bfe88 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java @@ -33,12 +33,14 @@ import org.springframework.util.Assert; * Java WebSocket runtime and also configures the underlying * {@link javax.websocket.server.ServerContainer}. * + *

If the runtime is a Servlet container, use {@link ServletEndpointExporter}. + * * @author Rossen Stoyanchev * @since 4.0 */ -public class ServerEndpointExporter implements BeanPostProcessor, InitializingBean { +public class EndpointExporter implements BeanPostProcessor, InitializingBean { - private static Log logger = LogFactory.getLog(ServerEndpointExporter.class); + private static Log logger = LogFactory.getLog(EndpointExporter.class); private Long maxSessionIdleTimeout; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java new file mode 100644 index 0000000000..aae44c148c --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java @@ -0,0 +1,83 @@ +/* + * 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.websocket.Endpoint; + +import org.springframework.beans.BeanUtils; +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.StandardWebSocketHandlerAdapter; +import org.springframework.websocket.server.AbstractHandshakeRequestHandler; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class EndpointHandshakeRequestHandler extends AbstractHandshakeRequestHandler { + + private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( + "org.apache.tomcat.websocket.server.WsHandshakeRequest", EndpointHandshakeRequestHandler.class.getClassLoader()); + + private final EndpointRegistration endpointRegistration; + + private final EndpointRequestUpgradeStrategy upgradeStrategy; + + + public EndpointHandshakeRequestHandler(WebSocketHandler webSocketHandler) { + this(new StandardWebSocketHandlerAdapter(webSocketHandler)); + } + + public EndpointHandshakeRequestHandler(Endpoint endpoint) { + this.endpointRegistration = new EndpointRegistration("/dummy", endpoint); + this.upgradeStrategy = createRequestUpgradeStrategy(); + } + + private static EndpointRequestUpgradeStrategy createRequestUpgradeStrategy() { + String className; + if (tomcatWebSocketPresent) { + className = "org.springframework.websocket.server.endpoint.TomcatRequestUpgradeStrategy"; + } + else { + throw new IllegalStateException("No suitable EndpointRequestUpgradeStrategy"); + } + try { + Class clazz = ClassUtils.forName(className, EndpointHandshakeRequestHandler.class.getClassLoader()); + return (EndpointRequestUpgradeStrategy) 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); + } + +} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointRegistration.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 8179253e6b..bbc4d6b814 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServerEndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -46,13 +46,13 @@ import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; * which case it will be adapted via {@link StandardWebSocketHandlerAdapter}. * *

- * Beans of this type are detected by {@link ServerEndpointExporter} and + * Beans of this type are detected by {@link EndpointExporter} and * registered with a Java WebSocket runtime at startup. * * @author Rossen Stoyanchev * @since 4.0 */ -public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFactoryAware { +public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAware { private final String path; @@ -69,14 +69,14 @@ public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFac private final Configurator configurator = new Configurator() {}; - public ServerEndpointRegistration(String path, String beanName) { + public EndpointRegistration(String path, String beanName) { Assert.hasText(path, "path must not be empty"); Assert.notNull(beanName, "beanName is required"); this.path = path; this.bean = beanName; } - public ServerEndpointRegistration(String path, Object bean) { + public EndpointRegistration(String path, Object bean) { Assert.hasText(path, "path must not be empty"); Assert.notNull(bean, "bean is required"); this.path = path; @@ -104,7 +104,7 @@ public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFac } } - protected Endpoint getEndpoint() { + public Endpoint getEndpoint() { Object bean = this.bean; if (this.bean instanceof String) { bean = this.beanFactory.getBean((String) this.bean); @@ -166,23 +166,23 @@ public class ServerEndpointRegistration implements ServerEndpointConfig, BeanFac @SuppressWarnings("unchecked") @Override public T getEndpointInstance(Class clazz) throws InstantiationException { - return (T) ServerEndpointRegistration.this.getEndpoint(); + return (T) EndpointRegistration.this.getEndpoint(); } @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - ServerEndpointRegistration.this.modifyHandshake(request, response); + EndpointRegistration.this.modifyHandshake(request, response); } @Override public boolean checkOrigin(String originHeaderValue) { - return ServerEndpointRegistration.this.checkOrigin(originHeaderValue); + return EndpointRegistration.this.checkOrigin(originHeaderValue); } @Override public String getNegotiatedSubprotocol(List supported, List requested) { - return ServerEndpointRegistration.this.selectSubProtocol(requested); + return EndpointRegistration.this.selectSubProtocol(requested); } @Override public List getNegotiatedExtensions(List installed, List requested) { - return ServerEndpointRegistration.this.selectExtensions(requested); + return EndpointRegistration.this.selectExtensions(requested); } }; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java new file mode 100644 index 0000000000..745001b206 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java @@ -0,0 +1,35 @@ +/* + * 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 org.springframework.http.server.ServerHttpRequest; +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. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface EndpointRequestUpgradeStrategy { + + void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, + EndpointRegistration registration) throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java similarity index 88% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerEndpointExporter.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java index 2c7ca73808..2e36774f79 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerEndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java @@ -27,7 +27,7 @@ import org.springframework.web.context.ServletContextAware; * @author Rossen Stoyanchev * @since 4.0 */ -public class ServletServerEndpointExporter extends ServerEndpointExporter implements ServletContextAware { +public class ServletEndpointExporter extends EndpointExporter implements ServletContextAware { private ServletContext servletContext; @@ -43,7 +43,8 @@ public class ServletServerEndpointExporter extends ServerEndpointExporter implem @Override public void afterPropertiesSet() throws Exception { - // TODO: remove hard dependency on Tomcat (see Tomcat's WsListener) + + // TODO: this is needed (see WsListener) but remove hard dependency WsServerContainer sc = WsServerContainer.getServerContainer(); sc.setServletContext(this.servletContext); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java deleted file mode 100644 index a185f67ad3..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatHandshakeRequestHandler.java +++ /dev/null @@ -1,79 +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 java.lang.reflect.Method; -import java.util.Collections; - -import javax.servlet.http.HttpServletRequest; -import javax.websocket.Endpoint; -import javax.websocket.server.ServerEndpointConfig; - -import org.apache.tomcat.websocket.server.WsHandshakeRequest; -import org.apache.tomcat.websocket.server.WsHttpUpgradeHandler; -import org.apache.tomcat.websocket.server.WsServerContainer; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; -import org.springframework.websocket.server.AbstractHandshakeRequestHandler; - - -/** - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class TomcatHandshakeRequestHandler extends AbstractHandshakeRequestHandler { - - private final Endpoint endpoint; - - private final ServerEndpointConfig endpointConfig; - - - public TomcatHandshakeRequestHandler(WebSocketHandler webSocketHandler) { - this(new StandardWebSocketHandlerAdapter(webSocketHandler)); - } - - public TomcatHandshakeRequestHandler(Endpoint endpoint) { - this.endpoint = endpoint; - this.endpointConfig = new ServerEndpointRegistration("/dummy", this.endpoint); - } - - @Override - public void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol) throws Exception { - - logger.debug("Upgrading HTTP request"); - - Assert.isTrue(request instanceof ServletServerHttpRequest); - HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); - - WsHandshakeRequest wsRequest = new WsHandshakeRequest(servletRequest); - Method method = ReflectionUtils.findMethod(WsHandshakeRequest.class, "finished"); - ReflectionUtils.makeAccessible(method); - method.invoke(wsRequest); - - WsHttpUpgradeHandler wsHandler = servletRequest.upgrade(WsHttpUpgradeHandler.class); - - wsHandler.preInit(this.endpoint, this.endpointConfig, WsServerContainer.getServerContainer(), - wsRequest, protocol, Collections. emptyMap(), servletRequest.isSecure()); - } - -} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java new file mode 100644 index 0000000000..b25c1a47c3 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java @@ -0,0 +1,61 @@ +/* + * 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 java.lang.reflect.Method; +import java.util.Collections; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.tomcat.websocket.server.WsHandshakeRequest; +import org.apache.tomcat.websocket.server.WsHttpUpgradeHandler; +import org.apache.tomcat.websocket.server.WsServerContainer; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class TomcatRequestUpgradeStrategy implements EndpointRequestUpgradeStrategy { + + + @Override + public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, + EndpointRegistration registration) throws Exception { + + Assert.isTrue(request instanceof ServletServerHttpRequest); + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + + WsHttpUpgradeHandler wsHandler = servletRequest.upgrade(WsHttpUpgradeHandler.class); + + WsHandshakeRequest wsRequest = new WsHandshakeRequest(servletRequest); + Method method = ReflectionUtils.findMethod(WsHandshakeRequest.class, "finished"); + ReflectionUtils.makeAccessible(method); + method.invoke(wsRequest); + + wsHandler.preInit(registration.getEndpoint(), registration, + WsServerContainer.getServerContainer(), wsRequest, protocol, + Collections. emptyMap(), servletRequest.isSecure()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java index a5f66584f8..3ede1f92bc 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java @@ -1,7 +1,7 @@ /** * - * Server-specific support for working with standard Java WebSocket Endpoint's. + * Server-specific support for configuring and adapting standard Java WebSocket endpoint's. * */ package org.springframework.websocket.server.endpoint; From 88447e503b1c0eeccceb2de28876af9bcd4db90f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 1 Apr 2013 15:39:04 -0400 Subject: [PATCH 06/51] Add first cut of SockJS server support --- build.gradle | 3 + .../mock/http/MockHttpInputMessage.java | 7 + .../mock/http/MockHttpOutputMessage.java | 7 + .../java/org/springframework/http/Cookie.java | 25 + .../org/springframework/http/Cookies.java | 59 +++ .../springframework/http/DefaultCookie.java | 40 ++ .../org/springframework/http/HttpMessage.java | 5 + .../client/AbstractClientHttpRequest.java | 6 + .../client/AbstractClientHttpResponse.java | 6 + .../BufferingClientHttpRequestWrapper.java | 7 +- .../BufferingClientHttpResponseWrapper.java | 6 +- .../client/support/HttpRequestWrapper.java | 8 + .../converter/FormHttpMessageConverter.java | 6 + .../http/server/AsyncServerHttpRequest.java | 34 ++ .../server/AsyncServletServerHttpRequest.java | 139 ++++++ .../http/server/ServerHttpRequest.java | 6 + .../http/server/ServletServerHttpRequest.java | 30 ++ .../server/ServletServerHttpResponse.java | 19 + .../http/MockHttpInputMessage.java | 8 + .../http/MockHttpOutputMessage.java | 8 + ...rceptingClientHttpRequestFactoryTests.java | 15 + .../springframework/sockjs/SockJsHandler.java | 36 ++ .../sockjs/SockJsHandlerAdapter.java | 43 ++ .../springframework/sockjs/SockJsSession.java | 32 ++ .../sockjs/SockJsSessionSupport.java | 128 ++++++ .../springframework/sockjs/TransportType.java | 86 ++++ .../sockjs/server/AbstractServerSession.java | 151 ++++++ .../sockjs/server/AbstractSockJsService.java | 431 ++++++++++++++++++ .../sockjs/server/SockJsConfiguration.java | 69 +++ .../sockjs/server/SockJsFrame.java | 167 +++++++ .../server/SockJsWebSocketSessionAdapter.java | 79 ++++ .../sockjs/server/TransportHandler.java | 39 ++ .../server/TransportHandlerRegistrar.java | 28 ++ .../server/TransportHandlerRegistry.java | 28 ++ .../server/WebSocketSockJsHandlerAdapter.java | 93 ++++ .../server/support/DefaultSockJsService.java | 216 +++++++++ .../DefaultTransportHandlerRegistrar.java | 50 ++ ...AbstractHttpReceivingTransportHandler.java | 93 ++++ .../AbstractHttpSendingTransportHandler.java | 74 +++ .../transport/AbstractHttpServerSession.java | 147 ++++++ .../AbstractStreamingTransportHandler.java | 62 +++ .../EventSourceTransportHandler.java | 60 +++ .../transport/HtmlFileTransportHandler.java | 115 +++++ .../JsonpPollingTransportHandler.java | 86 ++++ .../transport/JsonpTransportHandler.java | 68 +++ .../transport/PollingHttpServerSession.java | 44 ++ .../transport/StreamingHttpServerSession.java | 72 +++ .../transport/WebSocketTransportHandler.java | 60 +++ .../transport/XhrPollingTransportHandler.java | 57 +++ .../XhrStreamingTransportHandler.java | 62 +++ .../server/transport/XhrTransportHandler.java | 42 ++ .../websocket/WebSocketHandler.java | 10 +- .../websocket/WebSocketHandlerAdapter.java | 10 +- .../{Session.java => WebSocketSession.java} | 6 +- .../StandardWebSocketHandlerAdapter.java | 54 +-- ...a => WebSocketStandardSessionAdapter.java} | 25 +- .../server/endpoint/EndpointRegistration.java | 3 + 57 files changed, 3220 insertions(+), 50 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/http/Cookie.java create mode 100644 spring-web/src/main/java/org/springframework/http/Cookies.java create mode 100644 spring-web/src/main/java/org/springframework/http/DefaultCookie.java create mode 100644 spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java create mode 100644 spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java rename spring-websocket/src/main/java/org/springframework/websocket/{Session.java => WebSocketSession.java} (89%) rename spring-websocket/src/main/java/org/springframework/websocket/endpoint/{StandardSessionAdapter.java => WebSocketStandardSessionAdapter.java} (61%) diff --git a/build.gradle b/build.gradle index 3bb431d2ac..e8986cd15a 100644 --- a/build.gradle +++ b/build.gradle @@ -526,6 +526,9 @@ project("spring-websocket") { optional("org.eclipse.jetty:jetty-websocket:8.1.10.v20130312") optional("org.glassfish.tyrus:tyrus-websocket-core:1.0-SNAPSHOT") + + optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") + } repositories { diff --git a/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java b/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java index 5de14e9954..d73a1ea0ef 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java +++ b/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java @@ -19,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.util.Assert; @@ -35,6 +36,8 @@ public class MockHttpInputMessage implements HttpInputMessage { private final InputStream body; + private final Cookies cookies = new Cookies(); + public MockHttpInputMessage(byte[] contents) { this.body = (contents != null) ? new ByteArrayInputStream(contents) : null; @@ -53,4 +56,8 @@ public class MockHttpInputMessage implements HttpInputMessage { return this.body; } + @Override + public Cookies getCookies() { + return this.cookies ; + } } diff --git a/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java b/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java index 43fa1b3e7e..8cda7862a5 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java +++ b/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java @@ -21,6 +21,7 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpOutputMessage; @@ -38,6 +39,7 @@ public class MockHttpOutputMessage implements HttpOutputMessage { private final ByteArrayOutputStream body = new ByteArrayOutputStream(); + private final Cookies cookies = new Cookies(); /** * Return the headers. @@ -83,4 +85,9 @@ public class MockHttpOutputMessage implements HttpOutputMessage { } } + @Override + public Cookies getCookies() { + return this.cookies; + } + } diff --git a/spring-web/src/main/java/org/springframework/http/Cookie.java b/spring-web/src/main/java/org/springframework/http/Cookie.java new file mode 100644 index 0000000000..a60c73c87f --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/Cookie.java @@ -0,0 +1,25 @@ +/* + * 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.http; + + +public interface Cookie { + + String getName(); + + String getValue(); + +} diff --git a/spring-web/src/main/java/org/springframework/http/Cookies.java b/spring-web/src/main/java/org/springframework/http/Cookies.java new file mode 100644 index 0000000000..7dc13536c9 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/Cookies.java @@ -0,0 +1,59 @@ +/* + * 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.http; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class Cookies { + + private final List cookies; + + + public Cookies() { + this.cookies = new ArrayList(); + } + + private Cookies(Cookies cookies) { + this.cookies = Collections.unmodifiableList(cookies.getCookies()); + } + + public static Cookies readOnlyCookies(Cookies cookies) { + return new Cookies(cookies); + } + + public List getCookies() { + return this.cookies; + } + + public Cookie getCookie(String name) { + for (Cookie c : this.cookies) { + if (c.getName().equals(name)) { + return c; + } + } + return null; + } + + public Cookie addCookie(String name, String value) { + DefaultCookie cookie = new DefaultCookie(name, value); + this.cookies.add(cookie); + return cookie; + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/DefaultCookie.java b/spring-web/src/main/java/org/springframework/http/DefaultCookie.java new file mode 100644 index 0000000000..82a09ba26e --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/DefaultCookie.java @@ -0,0 +1,40 @@ +/* + * 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.http; + +import org.springframework.util.Assert; + +public class DefaultCookie implements Cookie { + + private final String name; + + private final String value; + + DefaultCookie(String name, String value) { + Assert.hasText(name, "cookie name must not be empty"); + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/HttpMessage.java b/spring-web/src/main/java/org/springframework/http/HttpMessage.java index 80f7ca292d..05824e67ab 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpMessage.java +++ b/spring-web/src/main/java/org/springframework/http/HttpMessage.java @@ -31,4 +31,9 @@ public interface HttpMessage { */ HttpHeaders getHeaders(); + /** + * TODO .. + */ + Cookies getCookies(); + } diff --git a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java index 47422a0065..9084967078 100644 --- a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpRequest.java @@ -19,6 +19,7 @@ package org.springframework.http.client; import java.io.IOException; import java.io.OutputStream; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; @@ -44,6 +45,11 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest { return getBodyInternal(this.headers); } + public Cookies getCookies() { + // TODO + throw new UnsupportedOperationException(); + } + public final ClientHttpResponse execute() throws IOException { checkExecuted(); ClientHttpResponse result = executeInternal(this.headers); diff --git a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpResponse.java index cd6166575b..33c123cff9 100644 --- a/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/AbstractClientHttpResponse.java @@ -18,6 +18,7 @@ package org.springframework.http.client; import java.io.IOException; +import org.springframework.http.Cookies; import org.springframework.http.HttpStatus; /** @@ -32,4 +33,9 @@ public abstract class AbstractClientHttpResponse implements ClientHttpResponse { return HttpStatus.valueOf(getRawStatusCode()); } + public Cookies getCookies() { + // TODO + throw new UnsupportedOperationException(); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java index bb87844420..794ca6ac2a 100644 --- a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpRequestWrapper.java @@ -17,9 +17,9 @@ package org.springframework.http.client; import java.io.IOException; -import java.io.OutputStream; import java.net.URI; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; @@ -58,4 +58,9 @@ final class BufferingClientHttpRequestWrapper extends AbstractBufferingClientHtt return new BufferingClientHttpResponseWrapper(response); } + @Override + public Cookies getCookies() { + return this.request.getCookies(); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java index f075b202bd..382b3fa20a 100644 --- a/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java @@ -20,9 +20,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; /** @@ -67,6 +67,10 @@ final class BufferingClientHttpResponseWrapper implements ClientHttpResponse { return new ByteArrayInputStream(this.body); } + public Cookies getCookies() { + return this.response.getCookies(); + } + public void close() { this.response.close(); } diff --git a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java index 4aecd01dcd..c9c8ef9955 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java @@ -18,6 +18,7 @@ package org.springframework.http.client.support; import java.net.URI; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; @@ -73,4 +74,11 @@ public class HttpRequestWrapper implements HttpRequest { return this.request.getHeaders(); } + /** + * Returns the cookies of the wrapped request. + */ + public Cookies getCookies() { + return this.request.getCookies(); + } + } diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index 516eb92ab2..5ddcfead7b 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Random; import org.springframework.core.io.Resource; +import org.springframework.http.Cookies; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; @@ -383,6 +384,11 @@ public class FormHttpMessageConverter implements HttpMessageConverter> entry : this.headers.entrySet()) { diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java new file mode 100644 index 0000000000..0253ac66e1 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java @@ -0,0 +1,34 @@ +/* + * 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.http.server; + + +/** + * TODO.. + */ +public interface AsyncServerHttpRequest extends ServerHttpRequest { + + void setTimeout(long timeout); + + void startAsync(); + + boolean isAsyncStarted(); + + void completeAsync(); + + boolean isAsyncCompleted(); + +} diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java new file mode 100644 index 0000000000..37d424e017 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java @@ -0,0 +1,139 @@ +/* + * 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.http.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.Assert; + + +public class AsyncServletServerHttpRequest extends ServletServerHttpRequest + implements AsyncServerHttpRequest, AsyncListener { + + private Long timeout; + + private AsyncContext asyncContext; + + private AtomicBoolean asyncCompleted = new AtomicBoolean(false); + + private final List timeoutHandlers = new ArrayList(); + + private final List completionHandlers = new ArrayList(); + + private final HttpServletResponse servletResponse; + + + /** + * Create a new instance for the given request/response pair. + */ + public AsyncServletServerHttpRequest(HttpServletRequest request, HttpServletResponse response) { + super(request); + this.servletResponse = response; + } + + /** + * Timeout period begins after the container thread has exited. + */ + public void setTimeout(long timeout) { + Assert.state(!isAsyncStarted(), "Cannot change the timeout with concurrent handling in progress"); + this.timeout = timeout; + } + + public void addTimeoutHandler(Runnable timeoutHandler) { + this.timeoutHandlers.add(timeoutHandler); + } + + public void addCompletionHandler(Runnable runnable) { + this.completionHandlers.add(runnable); + } + + public boolean isAsyncStarted() { + return ((this.asyncContext != null) && getServletRequest().isAsyncStarted()); + } + + /** + * Whether async request processing has completed. + *

It is important to avoid use of request and response objects after async + * processing has completed. Servlet containers often re-use them. + */ + public boolean isAsyncCompleted() { + return this.asyncCompleted.get(); + } + + public void startAsync() { + Assert.state(getServletRequest().isAsyncSupported(), + "Async support must be enabled on a servlet and for all filters involved " + + "in async request processing. This is done in Java code using the Servlet API " + + "or by adding \"true\" to servlet and " + + "filter declarations in web.xml."); + Assert.state(!isAsyncCompleted(), "Async processing has already completed"); + if (isAsyncStarted()) { + return; + } + this.asyncContext = getServletRequest().startAsync(getServletRequest(), this.servletResponse); + this.asyncContext.addListener(this); + if (this.timeout != null) { + this.asyncContext.setTimeout(this.timeout); + } + } + + public void dispatch() { + Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); + this.asyncContext.dispatch(); + } + + public void completeAsync() { + Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); + if (isAsyncStarted() && !isAsyncCompleted()) { + this.asyncContext.complete(); + } + } + + // --------------------------------------------------------------------- + // Implementation of AsyncListener methods + // --------------------------------------------------------------------- + + public void onStartAsync(AsyncEvent event) throws IOException { + } + + public void onError(AsyncEvent event) throws IOException { + } + + public void onTimeout(AsyncEvent event) throws IOException { + for (Runnable handler : this.timeoutHandlers) { + handler.run(); + } + } + + public void onComplete(AsyncEvent event) throws IOException { + for (Runnable handler : this.completionHandlers) { + handler.run(); + } + this.asyncContext = null; + this.asyncCompleted.set(true); + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java index ce8fcd3ad2..669835afc1 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java @@ -18,6 +18,7 @@ package org.springframework.http.server; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpRequest; +import org.springframework.util.MultiValueMap; /** * Represents a server-side HTTP request. @@ -27,4 +28,9 @@ import org.springframework.http.HttpRequest; */ public interface ServerHttpRequest extends HttpRequest, HttpInputMessage { + /** + * Returns the map of query parameters. Empty if no query has been set. + */ + MultiValueMap getQueryParams(); + } diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index 5dd0dbd424..38d6e3df42 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -33,12 +33,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; /** * {@link ServerHttpRequest} implementation that is based on a {@link HttpServletRequest}. @@ -58,6 +62,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest { private HttpHeaders headers; + private Cookies cookies; + + private MultiValueMap queryParams; + /** * Construct a new instance of the ServletServerHttpRequest based on the given {@link HttpServletRequest}. @@ -123,6 +131,28 @@ public class ServletServerHttpRequest implements ServerHttpRequest { return this.headers; } + public Cookies getCookies() { + if (this.cookies == null) { + this.cookies = new Cookies(); + for (Cookie cookie : this.servletRequest.getCookies()) { + this.cookies.addCookie(cookie.getName(), cookie.getValue()); + } + } + return this.cookies; + } + + public MultiValueMap getQueryParams() { + if (this.queryParams == null) { + this.queryParams = new LinkedMultiValueMap(this.servletRequest.getParameterMap().size()); + for (String name : this.servletRequest.getParameterMap().keySet()) { + for (String value : this.servletRequest.getParameterValues(name)) { + this.queryParams.add(name, value); + } + } + } + return this.queryParams; + } + public InputStream getBody() throws IOException { if (isFormPost(this.servletRequest)) { return getBodyFromServletRequestParameters(this.servletRequest); diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index 985085e51e..b090132457 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; +import org.springframework.http.Cookie; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; @@ -40,6 +42,8 @@ public class ServletServerHttpResponse implements ServerHttpResponse { private boolean headersWritten = false; + private final Cookies cookies = new Cookies(); + /** * Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}. @@ -66,12 +70,18 @@ public class ServletServerHttpResponse implements ServerHttpResponse { return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); } + public Cookies getCookies() { + return (this.headersWritten ? Cookies.readOnlyCookies(this.cookies) : this.cookies); + } + public OutputStream getBody() throws IOException { + writeCookies(); writeHeaders(); return this.servletResponse.getOutputStream(); } public void close() { + writeCookies(); writeHeaders(); } @@ -95,4 +105,13 @@ public class ServletServerHttpResponse implements ServerHttpResponse { } } + private void writeCookies() { + if (!this.headersWritten) { + for (Cookie source : this.cookies.getCookies()) { + javax.servlet.http.Cookie target = new javax.servlet.http.Cookie(source.getName(), source.getValue()); + target.setPath("/"); + this.servletResponse.addCookie(target); + } + } + } } diff --git a/spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java b/spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java index 18412ce33c..0ca8a29aca 100644 --- a/spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java +++ b/spring-web/src/test/java/org/springframework/http/MockHttpInputMessage.java @@ -31,6 +31,9 @@ public class MockHttpInputMessage implements HttpInputMessage { private final InputStream body; + private final Cookies cookies = new Cookies(); + + public MockHttpInputMessage(byte[] contents) { Assert.notNull(contents, "'contents' must not be null"); this.body = new ByteArrayInputStream(contents); @@ -50,4 +53,9 @@ public class MockHttpInputMessage implements HttpInputMessage { public InputStream getBody() throws IOException { return body; } + + @Override + public Cookies getCookies() { + return this.cookies ; + } } diff --git a/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java b/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java index cb08fa91a1..3287a7d93f 100644 --- a/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java +++ b/spring-web/src/test/java/org/springframework/http/MockHttpOutputMessage.java @@ -32,6 +32,9 @@ public class MockHttpOutputMessage implements HttpOutputMessage { private final ByteArrayOutputStream body = spy(new ByteArrayOutputStream()); + private final Cookies cookies = new Cookies(); + + @Override public HttpHeaders getHeaders() { return headers; @@ -50,4 +53,9 @@ public class MockHttpOutputMessage implements HttpOutputMessage { byte[] bytes = getBodyAsBytes(); return new String(bytes, charset); } + + @Override + public Cookies getCookies() { + return this.cookies; + } } diff --git a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java index 2df6331b8e..0358bf42bf 100644 --- a/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTests.java @@ -29,6 +29,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.springframework.http.Cookies; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; @@ -253,6 +254,8 @@ public class InterceptingClientHttpRequestFactoryTests { private boolean executed = false; + private Cookies cookies = new Cookies(); + private RequestMock() { } @@ -289,6 +292,11 @@ public class InterceptingClientHttpRequestFactoryTests { executed = true; return responseMock; } + + @Override + public Cookies getCookies() { + return this.cookies ; + } } private static class ResponseMock implements ClientHttpResponse { @@ -299,6 +307,8 @@ public class InterceptingClientHttpRequestFactoryTests { private HttpHeaders headers = new HttpHeaders(); + private Cookies cookies = new Cookies(); + @Override public HttpStatus getStatusCode() throws IOException { return statusCode; @@ -327,5 +337,10 @@ public class InterceptingClientHttpRequestFactoryTests { @Override public void close() { } + + @Override + public Cookies getCookies() { + return this.cookies ; + } } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java new file mode 100644 index 0000000000..00b4935c56 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.sockjs; + + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsHandler { + + void newSession(SockJsSession session) throws Exception; + + void handleMessage(SockJsSession session, String message) throws Exception; + + void handleException(SockJsSession session, Throwable exception); + + void sessionClosed(SockJsSession session); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java new file mode 100644 index 0000000000..08b93d1296 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java @@ -0,0 +1,43 @@ +/* + * 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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SockJsHandlerAdapter implements SockJsHandler { + + @Override + public void newSession(SockJsSession session) throws Exception { + } + + @Override + public void handleMessage(SockJsSession session, String message) throws Exception { + } + + @Override + public void handleException(SockJsSession session, Throwable exception) { + } + + @Override + public void sessionClosed(SockJsSession session) { + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java new file mode 100644 index 0000000000..77a61128e3 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java @@ -0,0 +1,32 @@ +/* + * 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; + + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsSession { + + void sendMessage(String text) throws Exception; + + void close(); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java new file mode 100644 index 0000000000..f35e1e8fd5 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java @@ -0,0 +1,128 @@ +/* + * 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; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class SockJsSessionSupport implements SockJsSession { + + protected Log logger = LogFactory.getLog(this.getClass()); + + private final String sessionId; + + private final SockJsHandler delegate; + + private State state = State.NEW; + + private long timeCreated = System.currentTimeMillis(); + + private long timeLastActive = System.currentTimeMillis(); + + + /** + * + * @param sessionId + * @param delegate the recipient of SockJS messages + */ + public SockJsSessionSupport(String sessionId, SockJsHandler delegate) { + Assert.notNull(sessionId, "sessionId is required"); + Assert.notNull(delegate, "SockJsHandler is required"); + this.sessionId = sessionId; + this.delegate = delegate; + } + + public String getId() { + return this.sessionId; + } + + public SockJsHandler getSockJsHandler() { + return this.delegate; + } + + public boolean isNew() { + return State.NEW.equals(this.state); + } + + public boolean isOpen() { + return State.OPEN.equals(this.state); + } + + public boolean isClosed() { + return State.CLOSED.equals(this.state); + } + + /** + * Polling and Streaming sessions periodically close the current HTTP request and + * wait for the next request to come through. During this "downtime" the session is + * still open but inactive and unable to send messages and therefore has to buffer + * them temporarily. A WebSocket session by contrast is stateful and remain active + * until closed. + */ + public abstract boolean isActive(); + + /** + * Return the time since the session was last active, or otherwise if the + * session is new, the time since the session was created. + */ + public long getTimeSinceLastActive() { + if (isNew()) { + return (System.currentTimeMillis() - this.timeCreated); + } + else { + return isActive() ? 0 : System.currentTimeMillis() - this.timeLastActive; + } + } + + /** + * Should be invoked whenever the session becomes inactive. + */ + protected void updateLastActiveTime() { + this.timeLastActive = System.currentTimeMillis(); + } + + public void connectionInitialized() throws Exception { + this.state = State.OPEN; + this.delegate.newSession(this); + } + + public void delegateMessages(String... messages) throws Exception { + for (String message : messages) { + this.delegate.handleMessage(this, message); + } + } + + public void close() { + this.state = State.CLOSED; + } + + public String toString() { + return getClass().getSimpleName() + " [id=" + sessionId + "]"; + } + + + private enum State { NEW, OPEN, CLOSED } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java b/spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java new file mode 100644 index 0000000000..130e1f2f44 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java @@ -0,0 +1,86 @@ +/* + * 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; + +import org.springframework.http.HttpMethod; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public enum TransportType { + + WEBSOCKET("websocket", HttpMethod.GET, false /* CORS ? */), + + XHR("xhr", HttpMethod.POST, true), + XHR_SEND("xhr_send", HttpMethod.POST, true), + + JSONP("jsonp", HttpMethod.GET, false), + JSONP_SEND("jsonp_send", HttpMethod.POST, false), + + XHR_STREAMING("xhr_streaming", HttpMethod.POST, true), + EVENT_SOURCE("eventsource", HttpMethod.GET, false), + HTML_FILE("htmlfile", HttpMethod.GET, false); + + + private final String value; + + private final HttpMethod httpMethod; + + private final boolean corsSupported; + + + private TransportType(String value, HttpMethod httpMethod, boolean supportsCors) { + this.value = value; + this.httpMethod = httpMethod; + this.corsSupported = supportsCors; + } + + public String value() { + return this.value; + } + + /** + * The HTTP method for this transport. + */ + public HttpMethod getHttpMethod() { + return this.httpMethod; + } + + /** + * Are cross-domain requests (CORS) supported? + */ + public boolean isCorsSupported() { + return this.corsSupported; + } + + public static TransportType fromValue(String transportValue) { + for (TransportType type : values()) { + if (type.value().equals(transportValue)) { + return type; + } + } + throw new IllegalArgumentException("No matching constant for [" + transportValue + "]"); + } + + @Override + public String toString() { + return this.value; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java new file mode 100644 index 0000000000..89941b9840 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java @@ -0,0 +1,151 @@ +/* + * 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.EOFException; +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.ScheduledFuture; + +import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.sockjs.SockJsHandler; +import org.springframework.sockjs.SockJsSession; +import org.springframework.sockjs.SockJsSessionSupport; +import org.springframework.util.Assert; + + +/** + * Provides partial implementations of {@link SockJsSession} methods to send messages, + * including heartbeat messages and to manage session state. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractServerSession extends SockJsSessionSupport { + + private final SockJsConfiguration sockJsConfig; + + private ScheduledFuture heartbeatTask; + + + public AbstractServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) { + super(sessionId, delegate); + Assert.notNull(sockJsConfig, "sockJsConfig is required"); + this.sockJsConfig = sockJsConfig; + } + + public SockJsConfiguration getSockJsConfig() { + return this.sockJsConfig; + } + + public final synchronized void sendMessage(String message) { + Assert.isTrue(!isClosed(), "Cannot send a message, session has been closed"); + sendMessageInternal(message); + } + + protected abstract void sendMessageInternal(String message); + + public final synchronized void close() { + if (!isClosed()) { + logger.debug("Closing session"); + + // set the status + super.close(); + + if (isActive()) { + // deliver messages "in flight" before sending close frame + writeFrame(SockJsFrame.closeFrameGoAway()); + } + + cancelHeartbeat(); + closeInternal(); + + getSockJsHandler().sessionClosed(this); + } + } + + protected abstract void closeInternal(); + + /** + * For internal use within a TransportHandler and the (TransportHandler-specific) + * session sub-class. The frame is written only if the connection is active. + */ + protected void writeFrame(SockJsFrame frame) { + if (logger.isTraceEnabled()) { + logger.trace("Preparing to write " + frame); + } + try { + writeFrameInternal(frame); + } + catch (EOFException ex) { + logger.warn("Failed to send message due to client disconnect. Terminating connection abruptly"); + deactivate(); + close(); + } + catch (Throwable t) { + logger.error("Failed to send message. Terminating connection abruptly", t); + deactivate(); + close(); + } + } + + protected abstract void writeFrameInternal(SockJsFrame frame) throws Exception; + + /** + * Some {@link TransportHandler} types cannot detect if a client connection is closed + * or lost and will eventually fail to send messages. When that happens, we need a way + * to disconnect the underlying connection before calling {@link #close()}. + */ + protected abstract void deactivate(); + + public synchronized void sendHeartbeat() { + if (isActive()) { + writeFrame(SockJsFrame.heartbeatFrame()); + scheduleHeartbeat(); + } + } + + protected void scheduleHeartbeat() { + Assert.notNull(getSockJsConfig().getHeartbeatScheduler(), "heartbeatScheduler not configured"); + cancelHeartbeat(); + if (!isActive()) { + return; + } + Date time = new Date(System.currentTimeMillis() + getSockJsConfig().getHeartbeatTime()); + this.heartbeatTask = getSockJsConfig().getHeartbeatScheduler().schedule(new Runnable() { + public void run() { + sendHeartbeat(); + } + }, time); + if (logger.isTraceEnabled()) { + logger.trace("Scheduled heartbeat after " + getSockJsConfig().getHeartbeatTime()/1000 + " seconds"); + } + } + + protected void cancelHeartbeat() { + if ((this.heartbeatTask != null) && !this.heartbeatTask.isDone()) { + if (logger.isTraceEnabled()) { + logger.trace("Cancelling heartbeat"); + } + this.heartbeatTask.cancel(false); + } + this.heartbeatTask = null; + } + + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java new file mode 100644 index 0000000000..d56303fa0e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -0,0 +1,431 @@ +/* + * 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.nio.charset.Charset; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Random; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +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.SockJsHandler; +import org.springframework.sockjs.TransportType; +import org.springframework.sockjs.server.support.DefaultTransportHandlerRegistrar; +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; + + +/** + * Provides support for SockJS configuration options and serves the static SockJS URLs. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractSockJsService implements SockJsConfiguration { + + protected final Log logger = LogFactory.getLog(getClass()); + + private static final int ONE_YEAR = 365 * 24 * 60 * 60; + + + private String sockJsServiceName = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); + + private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js"; + + private int streamBytesLimit = 128 * 1024; + + private boolean jsessionIdCookieNeeded = true; + + private long heartbeatTime = 25 * 1000; + + private TaskScheduler heartbeatScheduler; + + private long disconnectDelay = 5 * 1000; + + private boolean webSocketsEnabled = true; + + private HandshakeRequestHandler handshakeRequestHandler; + + + /** + * Class constructor... + * + */ + public AbstractSockJsService() { + this.heartbeatScheduler = createScheduler("SockJs-heartbeat-"); + } + + protected TaskScheduler createScheduler(String threadNamePrefix) { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setThreadNamePrefix(threadNamePrefix); + return scheduler; + } + + /** + * A unique name for the service, possibly the prefix at which it is deployed. + * Used mainly for logging purposes. + */ + public void setSockJsServiceName(String serviceName) { + this.sockJsServiceName = serviceName; + } + + /** + * The SockJS service name. + * @see #setSockJsServiceName(String) + */ + public String getSockJsServiceName() { + return this.sockJsServiceName; + } + + /** + * Transports which don't support cross-domain communication natively (e.g. + * "eventsource", "htmlfile") rely on serving a simple page (using the + * "foreign" domain) from an invisible iframe. Code run from this iframe + * doesn't need to worry about cross-domain issues since it is running from + * a domain local to the SockJS server. The iframe does need to load the + * SockJS javascript client library and this option allows configuring its + * url. + *

+ * By default this is set to point to + * "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". + */ + public AbstractSockJsService setSockJsClientLibraryUrl(String clientLibraryUrl) { + this.clientLibraryUrl = clientLibraryUrl; + return this; + } + + /** + * The URL to the SockJS JavaScript client library. + * @see #setSockJsClientLibraryUrl(String) + */ + public String getSockJsClientLibraryUrl() { + return this.clientLibraryUrl; + } + + public AbstractSockJsService setStreamBytesLimit(int streamBytesLimit) { + this.streamBytesLimit = streamBytesLimit; + return this; + } + + public int getStreamBytesLimit() { + return streamBytesLimit; + } + + /** + * Some load balancers do sticky sessions, but only if there is a JSESSIONID + * cookie. Even if it is set to a dummy value, it doesn't matter since + * session information is added by the load balancer. + *

+ * Set this option to indicate if a JSESSIONID cookie should be created. The + * default value is "true". + */ + public AbstractSockJsService setJsessionIdCookieNeeded(boolean jsessionIdCookieNeeded) { + this.jsessionIdCookieNeeded = jsessionIdCookieNeeded; + return this; + } + + /** + * Whether setting JSESSIONID cookie is necessary. + * @see #setJsessionIdCookieNeeded(boolean) + */ + public boolean isJsessionIdCookieNeeded() { + return this.jsessionIdCookieNeeded; + } + + public AbstractSockJsService setHeartbeatTime(long heartbeatTime) { + this.heartbeatTime = heartbeatTime; + return this; + } + + public long getHeartbeatTime() { + return this.heartbeatTime; + } + + public TaskScheduler getHeartbeatScheduler() { + return this.heartbeatScheduler; + } + + public void setHeartbeatScheduler(TaskScheduler heartbeatScheduler) { + Assert.notNull(heartbeatScheduler, "heartbeatScheduler is required"); + this.heartbeatScheduler = heartbeatScheduler; + } + + public AbstractSockJsService setDisconnectDelay(long disconnectDelay) { + this.disconnectDelay = disconnectDelay; + return this; + } + + public long getDisconnectDelay() { + return this.disconnectDelay; + } + + /** + * Some load balancers don't support websockets. This option can be used to + * disable the WebSocket transport on the server side. + *

+ * The default value is "true". + */ + public AbstractSockJsService setWebSocketsEnabled(boolean webSocketsEnabled) { + this.webSocketsEnabled = webSocketsEnabled; + return this; + } + + /** + * Whether WebSocket transport is enabled. + * @see #setWebSocketsEnabled(boolean) + */ + public boolean isWebSocketsEnabled() { + 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 AbstractSockJsService setWebsocketHandler(HandshakeRequestHandler handshakeRequestHandler) { + this.handshakeRequestHandler = handshakeRequestHandler; + return this; + } + + + /** + * TODO + * + * @param request + * @param response + * @param sockJsPath + * + * @throws Exception + */ + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath) + throws Exception { + + logger.debug(request.getMethod() + " [" + sockJsPath + "]"); + + try { + request.getHeaders(); + } + catch (IllegalArgumentException ex) { + // Ignore invalid Content-Type (TODO!!) + } + + if (sockJsPath.equals("") || sockJsPath.equals("/")) { + response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); + response.getBody().write("Welcome to SockJS!\n".getBytes("UTF-8")); + return; + } + else if (sockJsPath.equals("/info")) { + this.infoHandler.handle(request, response); + return; + } + else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) { + this.iframeHandler.handle(request, response); + return; + } + else if (sockJsPath.equals("/websocket")) { + Assert.notNull(this.handshakeRequestHandler, "No handler for raw Websockets configured"); + this.handshakeRequestHandler.doHandshake(request, response); + return; + } + + String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); + if (pathSegments.length != 3) { + logger.debug("Expected /{server}/{session}/{transport} but got " + sockJsPath); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + String serverId = pathSegments[0]; + String sessionId = pathSegments[1]; + String transport = pathSegments[2]; + + if (!validateRequest(serverId, sessionId, transport)) { + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + handleRequestInternal(request, response, sessionId, TransportType.fromValue(transport)); + + } + + protected boolean validateRequest(String serverId, String sessionId, String transport) { + + if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) { + logger.debug("Empty server, session, or transport value"); + return false; + } + + // Server and session id's must not contain "." + if (serverId.contains(".") || sessionId.contains(".")) { + logger.debug("Server or session contain a \".\""); + return false; + } + + if (!isWebSocketsEnabled() && transport.equals(TransportType.WEBSOCKET.value())) { + logger.debug("Websocket transport is disabled"); + return false; + } + + return true; + } + + protected abstract void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + String sessionId, TransportType transportType) throws Exception; + + protected void addCorsHeaders(ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) { + + String origin = request.getHeaders().getFirst("origin"); + origin = ((origin == null) || origin.equals("null")) ? "*" : origin; + + response.getHeaders().add("Access-Control-Allow-Origin", origin); + response.getHeaders().add("Access-Control-Allow-Credentials", "true"); + + List accessControllerHeaders = request.getHeaders().get("Access-Control-Request-Headers"); + if (accessControllerHeaders != null) { + for (String header : accessControllerHeaders) { + response.getHeaders().add("Access-Control-Allow-Headers", header); + } + } + + if (!ObjectUtils.isEmpty(httpMethods)) { + response.getHeaders().add("Access-Control-Allow-Methods", StringUtils.arrayToDelimitedString(httpMethods, ", ")); + response.getHeaders().add("Access-Control-Max-Age", String.valueOf(ONE_YEAR)); + } + } + + protected void addCacheHeaders(ServerHttpResponse response) { + response.getHeaders().setCacheControl("public, max-age=" + ONE_YEAR); + response.getHeaders().setExpires(new Date().getTime() + ONE_YEAR * 1000); + } + + protected void addNoCacheHeaders(ServerHttpResponse response) { + response.getHeaders().setCacheControl("no-store, no-cache, must-revalidate, max-age=0"); + } + + protected void sendMethodNotAllowed(ServerHttpResponse response, List httpMethods) throws IOException { + logger.debug("Sending Method Not Allowed (405)"); + response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED); + response.getHeaders().setAllow(new HashSet(httpMethods)); + response.getBody(); // ensure headers are flushed (TODO!) + } + + + private interface SockJsRequestHandler { + + void handle(ServerHttpRequest request, ServerHttpResponse response) throws Exception; + } + + private static final Random random = new Random(); + + private final SockJsRequestHandler infoHandler = new SockJsRequestHandler() { + + private static final String INFO_CONTENT = + "{\"entropy\":%s,\"origins\":[\"*:*\"],\"cookie_needed\":%s,\"websocket\":%s}"; + + public void handle(ServerHttpRequest request, ServerHttpResponse response) throws Exception { + + if (HttpMethod.GET.equals(request.getMethod())) { + + response.getHeaders().setContentType(new MediaType("application", "json", Charset.forName("UTF-8"))); + + addCorsHeaders(request, response); + addNoCacheHeaders(response); + + String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieNeeded(), isWebSocketsEnabled()); + response.getBody().write(content.getBytes()); + } + else if (HttpMethod.OPTIONS.equals(request.getMethod())) { + + response.setStatusCode(HttpStatus.NO_CONTENT); + + addCorsHeaders(request, response, HttpMethod.GET, HttpMethod.OPTIONS); + addCacheHeaders(response); + + response.getBody(); // ensure headers are flushed (TODO!) + } + else { + sendMethodNotAllowed(response, Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET)); + } + } + }; + + private final SockJsRequestHandler iframeHandler = new SockJsRequestHandler() { + + private static final String IFRAME_CONTENT = + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "

Don't panic!

\n" + + "

This is a SockJS hidden iframe. It's used for cross domain magic.

\n" + + "\n" + + ""; + + public void handle(ServerHttpRequest request, ServerHttpResponse response) throws Exception { + + if (!HttpMethod.GET.equals(request.getMethod())) { + sendMethodNotAllowed(response, Arrays.asList(HttpMethod.GET)); + return; + } + + String content = String.format(IFRAME_CONTENT, getSockJsClientLibraryUrl()); + byte[] contentBytes = content.getBytes(Charset.forName("UTF-8")); + StringBuilder builder = new StringBuilder("\"0"); + DigestUtils.appendMd5DigestAsHex(contentBytes, builder); + builder.append('"'); + String etagValue = builder.toString(); + + List ifNoneMatch = request.getHeaders().getIfNoneMatch(); + if (!CollectionUtils.isEmpty(ifNoneMatch) && ifNoneMatch.get(0).equals(etagValue)) { + response.setStatusCode(HttpStatus.NOT_MODIFIED); + return; + } + + response.getHeaders().setContentType(new MediaType("text", "html", Charset.forName("UTF-8"))); + response.getHeaders().setContentLength(contentBytes.length); + + addCacheHeaders(response); + response.getHeaders().setETag(etagValue); + response.getBody().write(contentBytes); + } + }; + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java new file mode 100644 index 0000000000..c8242874a6 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java @@ -0,0 +1,69 @@ +/* + * 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.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + + +/** + * + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsConfiguration { + + + /** + * Streaming transports save responses on the client side and don't free + * memory used by delivered messages. Such transports need to recycle the + * connection once in a while. This property sets a minimum number of bytes + * that can be send over a single HTTP streaming request before it will be + * closed. After that client will open a new request. Setting this value to + * one effectively disables streaming and will make streaming transports to + * behave like polling transports. + *

+ * The default value is 128K (i.e. 128 * 1024). + */ + public int getStreamBytesLimit(); + + /** + * The amount of time in milliseconds before a client is considered + * disconnected after not having a receiving connection, i.e. an active + * connection over which the server can send data to the client. + *

+ * The default value is 5000. + */ + public long getDisconnectDelay(); + + /** + * The amount of time in milliseconds when the server has not sent any + * messages and after which the server should send a heartbeat frame to the + * client in order to keep the connection from breaking. + *

+ * The default value is 25,000 (25 seconds). + */ + public long getHeartbeatTime(); + + /** + * A scheduler instance to use for scheduling heartbeat frames. + *

+ * By default a {@link ThreadPoolTaskScheduler} with default settings is used. + */ + public TaskScheduler getHeartbeatScheduler(); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java new file mode 100644 index 0000000000..131354fbde --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java @@ -0,0 +1,167 @@ +/* + * 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.nio.charset.Charset; + +import org.springframework.util.Assert; + +import com.fasterxml.jackson.core.io.JsonStringEncoder; + + +/** + * + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SockJsFrame { + + private static final SockJsFrame OPEN_FRAME = new SockJsFrame("o"); + + private static final SockJsFrame HEARTBEAT_FRAME = new SockJsFrame("h"); + + private static final SockJsFrame CLOSE_GO_AWAY_FRAME = closeFrame(3000, "Go away!"); + + private static final SockJsFrame CLOSE_ANOTHER_CONNECTION_OPEN = closeFrame(2010, "Another connection still open"); + + + private final String content; + + + private SockJsFrame(String content) { + this.content = content; + } + + public static SockJsFrame openFrame() { + return OPEN_FRAME; + } + + public static SockJsFrame heartbeatFrame() { + return HEARTBEAT_FRAME; + } + + public static SockJsFrame messageFrame(String... messages) { + return new MessageFrame(messages); + } + + public static SockJsFrame closeFrameGoAway() { + return CLOSE_GO_AWAY_FRAME; + } + + public static SockJsFrame closeFrameAnotherConnectionOpen() { + return CLOSE_ANOTHER_CONNECTION_OPEN; + } + + public static SockJsFrame closeFrame(int code, String reason) { + return new SockJsFrame("c[" + code + ",\"" + reason + "\"]"); + } + + + public String getContent() { + return this.content; + } + + public byte[] getContentBytes() { + return this.content.getBytes(Charset.forName("UTF-8")); + } + + public String toString() { + String quoted = this.content.replace("\n", "\\n").replace("\r", "\\r"); + return "SockJsFrame content='" + quoted + "'"; + } + + + private static class MessageFrame extends SockJsFrame { + + public MessageFrame(String... messages) { + super(prepareContent(messages)); + } + + public static String prepareContent(String... messages) { + Assert.notNull(messages, "messages required"); + StringBuilder sb = new StringBuilder(); + sb.append("a["); + for (int i=0; i < messages.length; i++) { + sb.append('"'); + // TODO: dependency on Jackson + char[] quotedChars = JsonStringEncoder.getInstance().quoteAsString(messages[i]); + sb.append(escapeSockJsCharacters(quotedChars)); + sb.append('"'); + if (i < messages.length - 1) { + sb.append(','); + } + } + sb.append(']'); + return sb.toString(); + } + + private static String escapeSockJsCharacters(char[] chars) { + StringBuilder result = new StringBuilder(); + for (char ch : chars) { + if (isSockJsEscapeCharacter(ch)) { + result.append('\\').append('u'); + String hex = Integer.toHexString(ch).toLowerCase(); + for (int i = 0; i < (4 - hex.length()); i++) { + result.append('0'); + } + result.append(hex); + } + else { + result.append(ch); + } + } + return result.toString(); + } + + private static boolean isSockJsEscapeCharacter(char ch) { + return (ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F') + || (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F') + || (ch >= '\uFFF0' && ch <= '\uFFFF') || (ch >= '\uD800' && ch <= '\uDFFF'); + } + } + + public interface FrameFormat { + + SockJsFrame format(SockJsFrame frame); + } + + public static class DefaultFrameFormat implements FrameFormat { + + private final String format; + + public DefaultFrameFormat(String format) { + Assert.notNull(format, "format is required"); + this.format = format; + } + + /** + * + * @param format a String with a single %s formatting character where the + * frame content is to be inserted; e.g. "data: %s\r\n\r\n" + * @return new SockJsFrame instance with the formatted content + */ + public SockJsFrame format(SockJsFrame frame) { + String content = String.format(this.format, preProcessContent(frame.getContent())); + return new SockJsFrame(content); + } + + protected String preProcessContent(String content) { + return content; + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java new file mode 100644 index 0000000000..ea2610ab0d --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java @@ -0,0 +1,79 @@ +/* + * 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 SockJsWebSocketSessionAdapter extends AbstractServerSession { + + private static Log logger = LogFactory.getLog(SockJsWebSocketSessionAdapter.class); + + private WebSocketSession webSocketSession; + + + public SockJsWebSocketSessionAdapter(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) { + super(sessionId, delegate, sockJsConfig); + } + + public void setWebSocketSession(WebSocketSession webSocketSession) throws Exception { + this.webSocketSession = webSocketSession; + scheduleHeartbeat(); + connectionInitialized(); + } + + @Override + public boolean isActive() { + return (this.webSocketSession != null); + } + + @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(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java new file mode 100644 index 0000000000..3efe70404d --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2013 the toriginal 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.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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface TransportHandler { + + TransportType getTransportType(); + + SockJsSessionSupport createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config); + + void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java new file mode 100644 index 0000000000..7ff8b8f553 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java @@ -0,0 +1,28 @@ +/* + * 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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface TransportHandlerRegistrar { + + void registerTransportHandlers(TransportHandlerRegistry registry); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java new file mode 100644 index 0000000000..0c4d232e9e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java @@ -0,0 +1,28 @@ +/* + * 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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface TransportHandlerRegistry { + + void registerHandler(TransportHandler handler); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java new file mode 100644 index 0000000000..95bb75ff08 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java @@ -0,0 +1,93 @@ +/* + * 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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class WebSocketSockJsHandlerAdapter implements WebSocketHandler { + + private static final Log logger = LogFactory.getLog(WebSocketSockJsHandlerAdapter.class); + + private final SockJsWebSocketSessionAdapter sockJsSession; + + // TODO: the JSON library used must be configurable + private final ObjectMapper objectMapper = new ObjectMapper(); + + + public WebSocketSockJsHandlerAdapter(SockJsWebSocketSessionAdapter sockJsSession) { + this.sockJsSession = sockJsSession; + } + + @Override + public void newSession(WebSocketSession webSocketSession) throws Exception { + logger.debug("WebSocket connection established"); + webSocketSession.sendText(SockJsFrame.openFrame().getContent()); + this.sockJsSession.setWebSocketSession(webSocketSession); + } + + @Override + public void handleTextMessage(WebSocketSession session, String message) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("Received payload " + message + " for " + sockJsSession); + } + if (StringUtils.isEmpty(message)) { + logger.debug("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(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java new file mode 100644 index 0000000000..c00c49222a --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -0,0 +1,216 @@ +/* + * 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.support; + +import java.util.Arrays; +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.factory.InitializingBean; +import org.springframework.http.Cookie; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.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.util.Assert; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class DefaultSockJsService extends AbstractSockJsService implements TransportHandlerRegistry, InitializingBean { + + private static final AtomicLong webSocketSessionIdSuffix = new AtomicLong(); + + + private final SockJsHandler sockJsHandler; + + private TaskScheduler sessionTimeoutScheduler; + + private final Map sessions = new ConcurrentHashMap(); + + private final Map transportHandlers = new HashMap(); + + + /** + * Class constructor... + * + */ + public DefaultSockJsService(SockJsHandler sockJsHandler) { + Assert.notNull(sockJsHandler, "sockJsHandler is required"); + this.sockJsHandler = sockJsHandler; + this.sessionTimeoutScheduler = createScheduler("SockJs-sessionTimeout-"); + new DefaultTransportHandlerRegistrar().registerTransportHandlers(this); + } + + /** + * A scheduler instance to use for scheduling periodic expires session cleanup. + *

+ * By default a {@link ThreadPoolTaskScheduler} with default settings is used. + */ + public TaskScheduler getSessionTimeoutScheduler() { + return this.sessionTimeoutScheduler; + } + + public void setSessionTimeoutScheduler(TaskScheduler sessionTimeoutScheduler) { + Assert.notNull(sessionTimeoutScheduler, "sessionTimeoutScheduler is required"); + this.sessionTimeoutScheduler = sessionTimeoutScheduler; + } + + @Override + public void registerHandler(TransportHandler transportHandler) { + Assert.notNull(transportHandler, "transportHandler is required"); + this.transportHandlers.put(transportHandler.getTransportType(), transportHandler); + } + + public void setTransportHandlerRegistrar(TransportHandlerRegistrar registrar) { + Assert.notNull(registrar, "registrar is required"); + this.transportHandlers.clear(); + registrar.registerTransportHandlers(this); + } + + @Override + public void afterPropertiesSet() throws Exception { + + this.sessionTimeoutScheduler.scheduleAtFixedRate(new Runnable() { + public void run() { + try { + int count = sessions.size(); + if (logger.isTraceEnabled() && (count != 0)) { + logger.trace("Checking " + count + " session(s) for timeouts [" + getSockJsServiceName() + "]"); + } + for (SockJsSessionSupport session : sessions.values()) { + if (session.getTimeSinceLastActive() > getDisconnectDelay()) { + if (logger.isTraceEnabled()) { + logger.trace("Removing " + session + " for [" + getSockJsServiceName() + "]"); + } + session.close(); + sessions.remove(session.getId()); + } + } + if (logger.isTraceEnabled() && (count != 0)) { + logger.trace(sessions.size() + " remaining session(s) [" + getSockJsServiceName() + "]"); + } + } + catch (Throwable t) { + logger.error("Failed to complete session timeout checks for [" + getSockJsServiceName() + "]", t); + } + } + }, getDisconnectDelay()); + } + + @Override + protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + String sessionId, TransportType transportType) throws Exception { + + TransportHandler transportHandler = this.transportHandlers.get(transportType); + + if (transportHandler == null) { + logger.debug("Transport handler not found"); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + HttpMethod supportedMethod = transportType.getHttpMethod(); + if (!supportedMethod.equals(request.getMethod())) { + if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.isCorsSupported()) { + response.setStatusCode(HttpStatus.NO_CONTENT); + addCorsHeaders(request, response, supportedMethod, HttpMethod.OPTIONS); + addCacheHeaders(response); + response.getBody(); // ensure headers are flushed (TODO!) + } + else { + List supportedMethods = Arrays.asList(supportedMethod); + if (transportType.isCorsSupported()) { + supportedMethods.add(HttpMethod.OPTIONS); + } + sendMethodNotAllowed(response, supportedMethods); + } + return; + } + + SockJsSessionSupport session = getSockJsSession(sessionId, transportHandler); + if (session == null) { + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + addNoCacheHeaders(response); + + if (isJsessionIdCookieNeeded()) { + Cookie cookie = request.getCookies().getCookie("JSESSIONID"); + String jsid = (cookie != null) ? cookie.getValue() : "dummy"; + // TODO: Jetty sets Expires header, so bypass Cookie object for now + response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/"); // TODO + } + + if (transportType.isCorsSupported()) { + addCorsHeaders(request, response); + } + + transportHandler.handleRequest(request, response, session); + + response.close(); // ensure headers are flushed (TODO !!) + } + + 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"); + return null; + } + + synchronized (this.sessions) { + session = this.sessions.get(sessionId); + if (session != null) { + return session; + } + + logger.debug("Creating new session with session id \"" + sessionId + "\""); + session = transportHandler.createSession(sessionId, this.sockJsHandler, this); + this.sessions.put(sessionId, session); + + return session; + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java new file mode 100644 index 0000000000..a6c50e4977 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.sockjs.server.support; + +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.XhrPollingTransportHandler; +import org.springframework.sockjs.server.transport.XhrStreamingTransportHandler; +import org.springframework.sockjs.server.transport.XhrTransportHandler; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class DefaultTransportHandlerRegistrar implements TransportHandlerRegistrar { + + public void registerTransportHandlers(TransportHandlerRegistry registry) { + + registry.registerHandler(new XhrPollingTransportHandler()); + registry.registerHandler(new XhrTransportHandler()); + + registry.registerHandler(new JsonpPollingTransportHandler()); + registry.registerHandler(new JsonpTransportHandler()); + + registry.registerHandler(new XhrStreamingTransportHandler()); + registry.registerHandler(new EventSourceTransportHandler()); + registry.registerHandler(new HtmlFileTransportHandler()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java new file mode 100644 index 0000000000..08ad9b4e76 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -0,0 +1,93 @@ +/* + * 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 java.nio.charset.Charset; +import java.util.Arrays; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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; +import com.fasterxml.jackson.databind.ObjectMapper; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractHttpReceivingTransportHandler implements TransportHandler { + + protected final Log logger = LogFactory.getLog(this.getClass()); + + // TODO: the JSON library used must be configurable + private final ObjectMapper objectMapper = new ObjectMapper(); + + + public ObjectMapper getObjectMapper() { + return this.objectMapper; + } + + @Override + public SockJsSessionSupport createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) { + return null; + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception { + + String[] messages = null; + try { + messages = readMessages(request); + } + catch (JsonMappingException ex) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("Payload expected.".getBytes("UTF-8")); + return; + } + catch (IOException ex) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("Broken JSON encoding.".getBytes("UTF-8")); + return; + } + + if (logger.isTraceEnabled()) { + logger.trace("Received messages: " + Arrays.asList(messages)); + } + + session.delegateMessages(messages); + + response.setStatusCode(getResponseStatus()); + response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); + } + + protected abstract String[] readMessages(ServerHttpRequest request) throws IOException; + + protected abstract HttpStatus getResponseStatus(); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java new file mode 100644 index 0000000000..90ef3c29a6 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -0,0 +1,74 @@ +/* + * 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.SockJsFrame; +import org.springframework.sockjs.server.TransportHandler; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractHttpSendingTransportHandler implements TransportHandler { + + protected final Log logger = LogFactory.getLog(this.getClass()); + + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception { + + AbstractHttpServerSession httpServerSession = (AbstractHttpServerSession) session; + + // Set content type before writing + response.getHeaders().setContentType(getContentType()); + + if (httpServerSession.isNew()) { + handleNewSession(request, response, httpServerSession); + } + else if (httpServerSession.isActive()) { + logger.debug("another " + getTransportType() + " connection still open: " + httpServerSession); + httpServerSession.writeFrame(response.getBody(), SockJsFrame.closeFrameAnotherConnectionOpen()); + } + else { + logger.debug("starting " + getTransportType() + " async request"); + httpServerSession.setCurrentRequest(request, response, getFrameFormat(request)); + } + } + + protected void handleNewSession(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSession session) throws Exception { + + logger.debug("Opening " + getTransportType() + " connection"); + session.setFrameFormat(getFrameFormat(request)); + session.writeFrame(response.getBody(), SockJsFrame.openFrame()); + session.connectionInitialized(); + } + + protected abstract MediaType getContentType(); + + protected abstract FrameFormat getFrameFormat(ServerHttpRequest request); + +} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java new file mode 100644 index 0000000000..ad4939c900 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java @@ -0,0 +1,147 @@ +/* + * 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 java.io.OutputStream; +import java.util.concurrent.ArrayBlockingQueue; +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.util.Assert; + +/** + * An abstract base class for use with HTTP-based transports. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractHttpServerSession extends AbstractServerSession { + + private FrameFormat frameFormat; + + private final BlockingQueue messageCache = new ArrayBlockingQueue(100); + + private AsyncServerHttpRequest asyncRequest; + + private OutputStream outputStream; + + + public AbstractHttpServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) { + super(sessionId, delegate, sockJsConfig); + } + + public void setFrameFormat(FrameFormat frameFormat) { + this.frameFormat = frameFormat; + } + + public synchronized void setCurrentRequest(ServerHttpRequest request, ServerHttpResponse response, + FrameFormat frameFormat) throws IOException { + + if (isClosed()) { + logger.debug("connection already closed"); + writeFrame(response.getBody(), SockJsFrame.closeFrameGoAway()); + return; + } + + Assert.isInstanceOf(AsyncServerHttpRequest.class, request, "Expected AsyncServerHttpRequest"); + + this.asyncRequest = (AsyncServerHttpRequest) request; + this.asyncRequest.setTimeout(-1); + this.asyncRequest.startAsync(); + + this.outputStream = response.getBody(); + this.frameFormat = frameFormat; + + scheduleHeartbeat(); + tryFlush(); + } + + public synchronized boolean isActive() { + return ((this.asyncRequest != null) && (!this.asyncRequest.isAsyncCompleted())); + } + + protected BlockingQueue getMessageCache() { + return this.messageCache; + } + + protected final synchronized void sendMessageInternal(String message) { + // assert close() was not called + // threads: TH-Session-Endpoint or any other thread + this.messageCache.add(message); + tryFlush(); + } + + private void tryFlush() { + if (isActive() && !getMessageCache().isEmpty()) { + logger.trace("Flushing messages"); + flush(); + } + } + + /** + * Only called if the connection is currently active + */ + protected abstract void flush(); + + protected void closeInternal() { + resetRequest(); + } + + protected synchronized void writeFrameInternal(SockJsFrame frame) throws IOException { + if (isActive()) { + writeFrame(this.outputStream, frame); + } + } + + /** + * This method may be called by a {@link TransportHandler} to write a frame + * even when the connection is not active, as long as a valid OutputStream + * is provided. + */ + public void writeFrame(OutputStream outputStream, SockJsFrame frame) throws IOException { + frame = this.frameFormat.format(frame); + if (logger.isTraceEnabled()) { + logger.trace("Writing " + frame); + } + outputStream.write(frame.getContentBytes()); + } + + @Override + protected void deactivate() { + this.outputStream = null; + this.asyncRequest = null; + updateLastActiveTime(); + } + + protected synchronized void resetRequest() { + if (isActive()) { + this.asyncRequest.completeAsync(); + } + this.outputStream = null; + this.asyncRequest = null; + updateLastActiveTime(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java new file mode 100644 index 0000000000..10816de521 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -0,0 +1,62 @@ +/* + * 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.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; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractStreamingTransportHandler extends AbstractHttpSendingTransportHandler { + + + @Override + public StreamingHttpServerSession createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) { + return new StreamingHttpServerSession(sessionId, handler, config); + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception { + + writePrelude(request, response); + + super.handleRequest(request, response, session); + } + + protected abstract void writePrelude(ServerHttpRequest request, ServerHttpResponse response) + throws IOException; + + @Override + protected void handleNewSession(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSession session) throws IOException, Exception { + + super.handleNewSession(request, response, session); + + session.setCurrentRequest(request, response, getFrameFormat(request)); + } + +} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java new file mode 100644 index 0000000000..2e05a41927 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -0,0 +1,60 @@ +/* + * 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 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.SockJsFrame.DefaultFrameFormat; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class EventSourceTransportHandler extends AbstractStreamingTransportHandler { + + + @Override + public TransportType getTransportType() { + return TransportType.EVENT_SOURCE; + } + + @Override + protected MediaType getContentType() { + return new MediaType("text", "event-stream", Charset.forName("UTF-8")); + } + + @Override + protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + response.getBody().write('\r'); + response.getBody().write('\n'); + response.getBody().flush(); + } + + @Override + protected FrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultFrameFormat("data: %s\r\n\r\n"); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java new file mode 100644 index 0000000000..cc1e98ac22 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -0,0 +1,115 @@ +/* + * 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 java.nio.charset.Charset; + +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.SockJsSessionSupport; +import org.springframework.sockjs.TransportType; +import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.util.StringUtils; +import org.springframework.web.util.JavaScriptUtils; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler { + + private static final String PARTIAL_HTML_CONTENT; + + static { + StringBuilder sb = new StringBuilder( + "\n" + + "\n" + + " \n" + + " \n" + + "

Don't panic!

\n" + + " " + ); + + // Safari needs at least 1024 bytes to parse the website. + // http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors + int spaces = 1024 - sb.length(); + for (int i=0; i < spaces; i++) { + sb.append(' '); + } + + PARTIAL_HTML_CONTENT = sb.toString(); + } + + + @Override + public TransportType getTransportType() { + return TransportType.HTML_FILE; + } + + @Override + protected MediaType getContentType() { + return new MediaType("text", "html", Charset.forName("UTF-8")); + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception { + + String callback = request.getQueryParams().getFirst("c"); + if (! StringUtils.hasText(callback)) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); + return; + } + + super.handleRequest(request, response, session); + } + + @Override + protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + + // we already validated the parameter.. + String callback = request.getQueryParams().getFirst("c"); + + String html = String.format(PARTIAL_HTML_CONTENT, callback); + response.getBody().write(html.getBytes("UTF-8")); + response.getBody().flush(); + } + + @Override + protected FrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultFrameFormat("\r\n") { + @Override + protected String preProcessContent(String content) { + return JavaScriptUtils.javaScriptEscape(content); + } + }; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java new file mode 100644 index 0000000000..c696c6cf12 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -0,0 +1,86 @@ +/* + * 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.nio.charset.Charset; + +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.TransportType; +import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.util.StringUtils; +import org.springframework.web.util.JavaScriptUtils; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHandler { + + + @Override + public TransportType getTransportType() { + return TransportType.JSONP; + } + + @Override + protected MediaType getContentType() { + return new MediaType("application", "javascript", Charset.forName("UTF-8")); + } + + @Override + public PollingHttpServerSession createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) { + return new PollingHttpServerSession(sessionId, handler, config); + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception { + + String callback = request.getQueryParams().getFirst("c"); + if (! StringUtils.hasText(callback)) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); + return; + } + + super.handleRequest(request, response, session); + } + + @Override + protected FrameFormat getFrameFormat(ServerHttpRequest request) { + + // we already validated the parameter.. + String callback = request.getQueryParams().getFirst("c"); + + return new SockJsFrame.DefaultFrameFormat(callback + "(\"%s\");\r\n") { + @Override + protected String preProcessContent(String content) { + return JavaScriptUtils.javaScriptEscape(content); + } + }; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java new file mode 100644 index 0000000000..694bb94453 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -0,0 +1,68 @@ +/* + * 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.http.HttpStatus; +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; + +public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler { + + + @Override + public TransportType getTransportType() { + return TransportType.JSONP_SEND; + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsSessionSupport sockJsSession) throws Exception { + + if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { + if (request.getQueryParams().getFirst("d") == null) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("Payload expected.".getBytes("UTF-8")); + return; + } + } + + super.handleRequest(request, response, sockJsSession); + + response.getBody().write("ok".getBytes("UTF-8")); + } + + @Override + protected String[] readMessages(ServerHttpRequest request) throws IOException { + if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { + String d = request.getQueryParams().getFirst("d"); + return getObjectMapper().readValue(d, String[].class); + } + else { + return getObjectMapper().readValue(request.getBody(), String[].class); + } + } + + @Override + protected HttpStatus getResponseStatus() { + return HttpStatus.OK; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java new file mode 100644 index 0000000000..f7b6b13a07 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java @@ -0,0 +1,44 @@ +/* + * 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.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); + } + + @Override + protected void flush() { + cancelHeartbeat(); + String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]); + getMessageCache().clear(); + writeFrame(SockJsFrame.messageFrame(messages)); + } + + @Override + protected void writeFrame(SockJsFrame frame) { + super.writeFrame(frame); + resetRequest(); + } + +} + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java new file mode 100644 index 0000000000..d327568331 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java @@ -0,0 +1,72 @@ +/* + * 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 java.io.OutputStream; + +import org.springframework.sockjs.SockJsHandler; +import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.server.SockJsFrame; + + +public class StreamingHttpServerSession extends AbstractHttpServerSession { + + private int byteCount; + + + public StreamingHttpServerSession(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) { + super(sessionId, delegate, sockJsConfig); + } + + protected void flush() { + + cancelHeartbeat(); + + do { + String message = getMessageCache().poll(); + SockJsFrame frame = SockJsFrame.messageFrame(message); + writeFrame(frame); + + this.byteCount += frame.getContentBytes().length + 1; + if (logger.isTraceEnabled()) { + logger.trace(this.byteCount + " bytes written, " + getMessageCache().size() + " more messages"); + } + if (this.byteCount >= getSockJsConfig().getStreamBytesLimit()) { + if (logger.isTraceEnabled()) { + logger.trace("Streamed bytes limit reached. Recycling current request"); + } + resetRequest(); + break; + } + } while (!getMessageCache().isEmpty()); + + scheduleHeartbeat(); + } + + @Override + protected synchronized void resetRequest() { + super.resetRequest(); + this.byteCount = 0; + } + + @Override + public void writeFrame(OutputStream outputStream, SockJsFrame frame) throws IOException { + super.writeFrame(outputStream, frame); + outputStream.flush(); + } +} + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java new file mode 100644 index 0000000000..9abf049b16 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -0,0 +1,60 @@ +/* + * 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.SockJsWebSocketSessionAdapter; +import org.springframework.sockjs.server.TransportHandler; +import org.springframework.sockjs.server.WebSocketSockJsHandlerAdapter; +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 SockJsWebSocketSessionAdapter(sessionId, handler, config); + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + throws Exception { + + SockJsWebSocketSessionAdapter sockJsSession = (SockJsWebSocketSessionAdapter) session; + WebSocketSockJsHandlerAdapter webSocketHandler = new WebSocketSockJsHandlerAdapter(sockJsSession); + HandshakeRequestHandler handshakeRequestHandler = new EndpointHandshakeRequestHandler(webSocketHandler); + handshakeRequestHandler.doHandshake(request, response); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java new file mode 100644 index 0000000000..7301377608 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -0,0 +1,57 @@ +/* + * 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.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.SockJsFrame.DefaultFrameFormat; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHandler { + + + @Override + public TransportType getTransportType() { + return TransportType.XHR; + } + + @Override + protected MediaType getContentType() { + return new MediaType("application", "javascript", Charset.forName("UTF-8")); + } + + @Override + protected FrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultFrameFormat("%s\n"); + } + + public PollingHttpServerSession createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) { + return new PollingHttpServerSession(sessionId, handler, config); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java new file mode 100644 index 0000000000..d141cb2c7e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -0,0 +1,62 @@ +/* + * 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 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.SockJsFrame.DefaultFrameFormat; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; + + +/** + * TODO + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class XhrStreamingTransportHandler extends AbstractStreamingTransportHandler { + + + @Override + public TransportType getTransportType() { + return TransportType.XHR_STREAMING; + } + + @Override + protected MediaType getContentType() { + return new MediaType("application", "javascript", Charset.forName("UTF-8")); + } + + @Override + protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + for (int i=0; i < 2048; i++) { + response.getBody().write('h'); + } + response.getBody().write('\n'); + response.getBody().flush(); + } + + @Override + protected FrameFormat getFrameFormat(ServerHttpRequest request) { + return new DefaultFrameFormat("%s\n"); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java new file mode 100644 index 0000000000..f5fe4b13ff --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java @@ -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 java.io.IOException; + +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.sockjs.TransportType; + +public class XhrTransportHandler extends AbstractHttpReceivingTransportHandler { + + + @Override + public TransportType getTransportType() { + return TransportType.XHR_SEND; + } + + @Override + protected String[] readMessages(ServerHttpRequest request) throws IOException { + return getObjectMapper().readValue(request.getBody(), String[].class); + } + + @Override + protected HttpStatus getResponseStatus() { + return HttpStatus.NO_CONTENT; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 7a71ab595b..5877a5b452 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -26,14 +26,14 @@ import java.io.InputStream; */ public interface WebSocketHandler { - void newSession(Session session) throws Exception; + void newSession(WebSocketSession session) throws Exception; - void handleTextMessage(Session session, String message) throws Exception; + void handleTextMessage(WebSocketSession session, String message) throws Exception; - void handleBinaryMessage(Session session, InputStream message) throws Exception; + void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception; - void handleException(Session session, Throwable exception); + void handleException(WebSocketSession session, Throwable exception); - void sessionClosed(Session session, int statusCode, String reason) throws Exception; + void sessionClosed(WebSocketSession session, int statusCode, String reason) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index 032abc6b46..261aff0451 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -26,23 +26,23 @@ import java.io.InputStream; public class WebSocketHandlerAdapter implements WebSocketHandler { @Override - public void newSession(Session session) throws Exception { + public void newSession(WebSocketSession session) throws Exception { } @Override - public void handleTextMessage(Session session, String message) throws Exception { + public void handleTextMessage(WebSocketSession session, String message) throws Exception { } @Override - public void handleBinaryMessage(Session session, InputStream message) throws Exception { + public void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception { } @Override - public void handleException(Session session, Throwable exception) { + public void handleException(WebSocketSession session, Throwable exception) { } @Override - public void sessionClosed(Session session, int statusCode, String reason) throws Exception { + public void sessionClosed(WebSocketSession session, int statusCode, String reason) throws Exception { } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/Session.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/Session.java rename to spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index a6104614ee..947a565bb7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/Session.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -23,10 +23,12 @@ package org.springframework.websocket; * @author Rossen Stoyanchev * @since 4.0 */ -public interface Session { +public interface WebSocketSession { void sendText(String text) throws Exception; - void close(int code, String reason) throws Exception; + void close(); + + void close(int code, String reason); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java index 608be8f932..2722cb09ed 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java @@ -27,7 +27,7 @@ import javax.websocket.MessageHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; -import org.springframework.websocket.Session; +import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.WebSocketHandler; @@ -42,7 +42,7 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { private final WebSocketHandler webSocketHandler; - private final Map sessionMap = new ConcurrentHashMap(); + private final Map sessionMap = new ConcurrentHashMap(); public StandardWebSocketHandlerAdapter(WebSocketHandler webSocketHandler) { @@ -50,13 +50,13 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { } @Override - public void onOpen(javax.websocket.Session sourceSession, EndpointConfig config) { - logger.debug("New WebSocket session: " + sourceSession); + public void onOpen(javax.websocket.Session session, EndpointConfig config) { + logger.debug("New WebSocket session: " + session); try { - Session session = new StandardSessionAdapter(sourceSession); - this.sessionMap.put(sourceSession.getId(), session); - sourceSession.addMessageHandler(new StandardMessageHandler(sourceSession.getId())); - this.webSocketHandler.newSession(session); + WebSocketSession webSocketSession = new WebSocketStandardSessionAdapter(session); + this.sessionMap.put(session.getId(), webSocketSession); + session.addMessageHandler(new StandardMessageHandler(session.getId())); + this.webSocketHandler.newSession(webSocketSession); } catch (Throwable ex) { // TODO @@ -65,18 +65,18 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { } @Override - public void onClose(javax.websocket.Session sourceSession, CloseReason closeReason) { - String id = sourceSession.getId(); + public void onClose(javax.websocket.Session session, CloseReason closeReason) { + String id = session.getId(); if (logger.isDebugEnabled()) { - logger.debug("Closing session: " + sourceSession + ", " + closeReason); + logger.debug("Closing session: " + session + ", " + closeReason); } try { - Session session = getSession(id); + WebSocketSession webSocketSession = getSession(id); this.sessionMap.remove(id); int code = closeReason.getCloseCode().getCode(); String reason = closeReason.getReasonPhrase(); - session.close(code, reason); - this.webSocketHandler.sessionClosed(session, code, reason); + webSocketSession.close(code, reason); + this.webSocketHandler.sessionClosed(webSocketSession, code, reason); } catch (Throwable ex) { // TODO @@ -85,11 +85,11 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { } @Override - public void onError(javax.websocket.Session sourceSession, Throwable exception) { - logger.error("Error for WebSocket session: " + sourceSession.getId(), exception); + public void onError(javax.websocket.Session session, Throwable exception) { + logger.error("Error for WebSocket session: " + session.getId(), exception); try { - Session session = getSession(sourceSession.getId()); - this.webSocketHandler.handleException(session, exception); + WebSocketSession webSocketSession = getSession(session.getId()); + this.webSocketHandler.handleException(webSocketSession, exception); } catch (Throwable ex) { // TODO @@ -97,28 +97,28 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { } } - private Session getSession(String sourceSessionId) { - Session session = this.sessionMap.get(sourceSessionId); - Assert.notNull(session, "No session"); - return session; + private WebSocketSession getSession(String sourceSessionId) { + WebSocketSession webSocketSession = this.sessionMap.get(sourceSessionId); + Assert.notNull(webSocketSession, "No session"); + return webSocketSession; } private class StandardMessageHandler implements MessageHandler.Whole { - private final String sourceSessionId; + private final String sessionId; - public StandardMessageHandler(String sourceSessionId) { - this.sourceSessionId = sourceSessionId; + public StandardMessageHandler(String sessionId) { + this.sessionId = sessionId; } @Override public void onMessage(String message) { if (logger.isTraceEnabled()) { - logger.trace("Message for session [" + this.sourceSessionId + "]: " + message); + logger.trace("Message for session [" + this.sessionId + "]: " + message); } try { - Session session = getSession(this.sourceSessionId); + WebSocketSession session = getSession(this.sessionId); StandardWebSocketHandlerAdapter.this.webSocketHandler.handleTextMessage(session, message); } catch (Throwable ex) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketStandardSessionAdapter.java similarity index 61% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardSessionAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketStandardSessionAdapter.java index 4366ec026b..ce3dd1c531 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketStandardSessionAdapter.java @@ -18,7 +18,7 @@ package org.springframework.websocket.endpoint; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.websocket.Session; +import org.springframework.websocket.WebSocketSession; /** @@ -26,26 +26,33 @@ import org.springframework.websocket.Session; * @author Rossen Stoyanchev * @since 4.0 */ -public class StandardSessionAdapter implements Session { +public class WebSocketStandardSessionAdapter implements WebSocketSession { - private static Log logger = LogFactory.getLog(StandardSessionAdapter.class); + private static Log logger = LogFactory.getLog(WebSocketStandardSessionAdapter.class); - private javax.websocket.Session sourceSession; + private javax.websocket.Session session; - public StandardSessionAdapter(javax.websocket.Session sourceSession) { - this.sourceSession = sourceSession; + public WebSocketStandardSessionAdapter(javax.websocket.Session session) { + this.session = session; } @Override public void sendText(String text) throws Exception { logger.trace("Sending text message: " + text); - this.sourceSession.getBasicRemote().sendText(text); + // TODO: check closed + this.session.getBasicRemote().sendText(text); } @Override - public void close(int code, String reason) throws Exception { - this.sourceSession = null; + public void close() { + // TODO: delegate with code and reason + this.session = null; + } + + @Override + public void close(int code, String reason) { + this.session = null; } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index bbc4d6b814..17ee09328b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -35,6 +35,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.web.context.ContextLoader; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; @@ -69,6 +70,8 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final Configurator configurator = new Configurator() {}; + // ContextLoader.getCurrentWebApplicationContext().getAutowireCapableBeanFactory().createBean(Class) + public EndpointRegistration(String path, String beanName) { Assert.hasText(path, "path must not be empty"); Assert.notNull(beanName, "beanName is required"); From 914e969ac3a90270d262c4cb817a5ed1eecb2baa Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 2 Apr 2013 11:55:07 -0400 Subject: [PATCH 07/51] Add support for @ServerEndpoint annotated classes --- .../server/endpoint/EndpointExporter.java | 67 ++++++++++++++++--- .../server/endpoint/EndpointRegistration.java | 50 +++++++++++--- .../server/endpoint/SpringConfigurator.java | 63 +++++++++++++++++ 3 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java index 5a601bfe88..9c287e7071 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java @@ -15,33 +15,41 @@ */ package org.springframework.websocket.server.endpoint; +import java.util.Map; + import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerContainerProvider; +import javax.websocket.server.ServerEndpoint; 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.util.Assert; +import org.springframework.util.ObjectUtils; /** * BeanPostProcessor that detects beans of type - * {@link javax.websocket.server.ServerEndpointConfig} and registers them with a standard - * Java WebSocket runtime and also configures the underlying - * {@link javax.websocket.server.ServerContainer}. + * {@link javax.websocket.server.ServerEndpointConfig} and registers the corresponding + * {@link javax.websocket.Endpoint} with a standard Java WebSocket runtime. * *

If the runtime is a Servlet container, use {@link ServletEndpointExporter}. * * @author Rossen Stoyanchev * @since 4.0 */ -public class EndpointExporter implements BeanPostProcessor, InitializingBean { +public class EndpointExporter implements InitializingBean, BeanPostProcessor, BeanFactoryAware { private static Log logger = LogFactory.getLog(EndpointExporter.class); + private Class[] annotatedEndpointClasses; + private Long maxSessionIdleTimeout; private Integer maxTextMessageBufferSize; @@ -49,6 +57,14 @@ public class EndpointExporter implements BeanPostProcessor, InitializingBean { private Integer maxBinaryMessageBufferSize; + /** + * 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)}. @@ -85,8 +101,29 @@ public class EndpointExporter implements BeanPostProcessor, InitializingBean { return this.maxBinaryMessageBufferSize; } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof ListableBeanFactory) { + ListableBeanFactory lbf = (ListableBeanFactory) beanFactory; + Map 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"); + } + ServerContainerProvider.getServerContainer().addEndpoint(beanType); + } + catch (DeploymentException e) { + throw new IllegalStateException("Failed to register @ServerEndpoint bean type " + beanName, e); + } + } + } + } + @Override public void afterPropertiesSet() throws Exception { + ServerContainer serverContainer = ServerContainerProvider.getServerContainer(); Assert.notNull(serverContainer, "javax.websocket.server.ServerContainer not available"); @@ -99,19 +136,33 @@ public class EndpointExporter implements BeanPostProcessor, InitializingBean { if (this.maxBinaryMessageBufferSize != null) { serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize); } + + 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); + } + } + } } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ServerEndpointConfig) { ServerEndpointConfig sec = (ServerEndpointConfig) bean; - ServerContainer serverContainer = ServerContainerProvider.getServerContainer(); try { - logger.debug("Registering javax.websocket.Endpoint for path " + sec.getPath()); - serverContainer.addEndpoint(sec); + if (logger.isInfoEnabled()) { + logger.info("Registering bean '" + beanName + + "' as javax.websocket.Endpoint under path " + sec.getPath()); + } + ServerContainerProvider.getServerContainer().addEndpoint(sec); } catch (DeploymentException e) { - throw new IllegalStateException("Failed to deploy Endpoint " + bean, e); + throw new IllegalStateException("Failed to deploy Endpoint bean " + bean, e); } } return bean; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 17ee09328b..385f8ece2b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -36,6 +36,7 @@ import org.springframework.beans.factory.BeanFactoryAware; 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.WebSocketHandler; import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; @@ -57,6 +58,8 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final String path; + private final Class endpointClass; + private final Object bean; private List subprotocols = new ArrayList(); @@ -70,20 +73,33 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final Configurator configurator = new Configurator() {}; - // ContextLoader.getCurrentWebApplicationContext().getAutowireCapableBeanFactory().createBean(Class) - - public EndpointRegistration(String path, String beanName) { - Assert.hasText(path, "path must not be empty"); - Assert.notNull(beanName, "beanName is required"); - this.path = path; - this.bean = beanName; + /** + * Class constructor with the {@code javax.webscoket.Endpoint} class. + * TODO + * + * @param path + * @param endpointClass + */ + public EndpointRegistration(String path, Class endpointClass) { + this(path, endpointClass, null); } public EndpointRegistration(String path, Object bean) { + this(path, null, bean); + } + + public EndpointRegistration(String path, String beanName) { + this(path, null, beanName); + } + + private EndpointRegistration(String path, Class endpointClass, Object bean) { Assert.hasText(path, "path must not be empty"); - Assert.notNull(bean, "bean is required"); + Assert.isTrue((endpointClass != null || bean != null), "Neither endpoint class nor endpoint bean provided"); this.path = path; + this.endpointClass = endpointClass; this.bean = bean; + // this will fail if the bean is not a valid Endpoint type + getEndpointClass(); } @Override @@ -94,20 +110,34 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw @SuppressWarnings("unchecked") @Override public Class getEndpointClass() { + if (this.endpointClass != null) { + return this.endpointClass; + } Class beanClass = this.bean.getClass(); if (beanClass.equals(String.class)) { beanClass = this.beanFactory.getType((String) this.bean); } beanClass = ClassUtils.getUserClass(beanClass); - if (WebSocketHandler.class.isAssignableFrom(beanClass)) { + if (Endpoint.class.isAssignableFrom(beanClass)) { + return (Class) beanClass; + } + else if (WebSocketHandler.class.isAssignableFrom(beanClass)) { return StandardWebSocketHandlerAdapter.class; } else { - return (Class) beanClass; + throw new IllegalStateException("Invalid endpoint bean: must be of type ... TODO "); } } public Endpoint getEndpoint() { + if (this.endpointClass != null) { + WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); + if (wac == null) { + throw new IllegalStateException("Failed to find WebApplicationContext. " + + "Was org.springframework.web.context.ContextLoader used to load the WebApplicationContext?"); + } + return wac.getAutowireCapableBeanFactory().createBean(this.endpointClass); + } Object bean = this.bean; if (this.bean instanceof String) { bean = this.beanFactory.getBean((String) this.bean); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java new file mode 100644 index 0000000000..d412fd9e29 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java @@ -0,0 +1,63 @@ +/* + * 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 java.util.Map; + +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig.Configurator; + +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; + + +/** + * This should be used in conjuction with {@link ServerEndpoint @ServerEndpoint} classes. + * + *

For {@link javax.websocket.Endpoint}, see {@link EndpointExporter}. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SpringConfigurator extends Configurator { + + + @Override + public T getEndpointInstance(Class endpointClass) throws InstantiationException { + + WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); + if (wac == null) { + throw new IllegalStateException("Failed to find WebApplicationContext. " + + "Was org.springframework.web.context.ContextLoader used to load the WebApplicationContext?"); + } + + Map beans = wac.getBeansOfType(endpointClass); + if (beans.isEmpty()) { + // Initialize a new bean instance + return wac.getAutowireCapableBeanFactory().createBean(endpointClass); + } + if (beans.size() == 1) { + // Return the matching bean instance + return beans.values().iterator().next(); + } + else { + // This should never happen (@ServerEndpoint has a single path mapping) .. + throw new IllegalStateException("Found more than one matching beans of type " + endpointClass); + } + } + +} From 6cf17449fa1014c378fe101fc1cb4c3670984817 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 4 Apr 2013 15:48:59 -0400 Subject: [PATCH 08/51] Add endpoint connection manager --- .../AbstractEndpointConnectionManager.java | 227 ++++++++++++++++++ .../AnnotatedEndpointConnectionManager.java | 46 ++++ .../client/EndpointConnectionManager.java | 78 ++++++ .../server/endpoint/EndpointExporter.java | 15 +- .../server/endpoint/EndpointRegistration.java | 28 +-- .../endpoint/ServletEndpointExporter.java | 26 +- .../server/endpoint/SpringConfigurator.java | 21 +- 7 files changed, 402 insertions(+), 39 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java new file mode 100644 index 0000000000..1cb0d35102 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java @@ -0,0 +1,227 @@ +/* + * 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.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.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractEndpointConnectionManager implements ApplicationContextAware, SmartLifecycle { + + protected final Log logger = LogFactory.getLog(getClass()); + + private final Class endpointClass; + + private final Object endpointBean; + + private final URI uri; + + private boolean autoStartup = false; + + private int phase = Integer.MAX_VALUE; + + private final WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer(); + + private Session session; + + private ApplicationContext applicationContext; + + private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-"); + + private final Object lifecycleMonitor = new Object(); + + + public AbstractEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { + Assert.notNull(endpointClass, "endpointClass is required"); + this.endpointClass = endpointClass; + this.endpointBean = null; + this.uri = initUri(uriTemplate, uriVariables); + } + + public AbstractEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { + Assert.notNull(endpointBean, "endpointBean is required"); + this.endpointClass = null; + this.endpointBean = endpointBean; + this.uri = initUri(uriTemplate, uriVariables); + } + + private static URI initUri(String uri, Object... uriVariables) { + return UriComponentsBuilder.fromUriString(uri).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 {@link #setDefaultUri(URI) default URI} after + * this endpoint connection factory has been initialized and the Spring context has + * been refreshed. + *

Default is "false". + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * Return the value for the 'autoStartup' property. If "true", this endpoint + * connection factory will connect to the {@link #setDefaultUri(URI) default URI} upon + * a ContextRefreshedEvent. + */ + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Specify the phase in which this endpoint connection factory should be + * auto-connected and closed. The startup order proceeds from lowest to highest, and + * the shutdown order is the reverse of that. By default this value is + * Integer.MAX_VALUE meaning that this endpoint connection factory connects as late as + * possible and is closed as soon as possible. + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * Return the phase in which this endpoint connection factory will be auto-connected + * and stopped. + */ + public int getPhase() { + return this.phase; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + protected URI getUri() { + return this.uri; + } + + protected WebSocketContainer getWebSocketContainer() { + return this.webSocketContainer; + } + + protected Object getEndpoint() { + if (this.endpointClass != null) { + return this.applicationContext.getAutowireCapableBeanFactory().createBean(this.endpointClass); + } + else { + return this.endpointBean; + } + } + + /** + * Auto-connects to the configured {@link #setDefaultUri(URI) default URI}. + */ + public void start() { + synchronized (this.lifecycleMonitor) { + if (!isRunning()) { + this.taskExecutor.execute(new Runnable() { + @Override + public void run() { + synchronized (lifecycleMonitor) { + try { + logger.info("Connecting to endpoint at URI " + uri); + session = connect(getEndpoint()); + logger.info("Successfully connected"); + } + catch (Exception ex) { + logger.error("Failed to connect to endpoint at " + uri, ex); + } + } + } + }); + } + } + } + + protected abstract Session connect(Object endpoint) throws DeploymentException, IOException; + + /** + * Deactivates the configured message endpoint. + */ + public void stop() { + synchronized (this.lifecycleMonitor) { + if (isRunning()) { + try { + this.session.close(); + } + catch (IOException e) { + // ignore + } + } + this.session = null; + } + } + + public void stop(Runnable callback) { + synchronized (this.lifecycleMonitor) { + this.stop(); + callback.run(); + } + } + + /** + * Return whether the configured message endpoint is currently active. + */ + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + if ((this.session != null) && this.session.isOpen()) { + return true; + } + this.session = null; + return false; + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java new file mode 100644 index 0000000000..ccc4c3f4a0 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java @@ -0,0 +1,46 @@ +/* + * 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.io.IOException; + +import javax.websocket.DeploymentException; +import javax.websocket.Session; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class AnnotatedEndpointConnectionManager extends AbstractEndpointConnectionManager { + + + public AnnotatedEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { + super(endpointClass, uriTemplate, uriVariables); + } + + public AnnotatedEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { + super(endpointBean, uriTemplate, uriVariables); + } + + @Override + protected Session connect(Object endpoint) throws DeploymentException, IOException { + return getWebSocketContainer().connectToServer(endpoint, getUri()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java new file mode 100644 index 0000000000..472a9df942 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java @@ -0,0 +1,78 @@ +/* + * 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.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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class EndpointConnectionManager extends AbstractEndpointConnectionManager { + + private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); + + + public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { + super(endpointClass, uriTemplate, uriVariables); + } + + public EndpointConnectionManager(Endpoint endpointBean, String uriTemplate, Object... uriVariables) { + super(endpointBean, uriTemplate, uriVariables); + } + + public void setSubProtocols(String... subprotocols) { + this.configBuilder.preferredSubprotocols(Arrays.asList(subprotocols)); + } + + public void setExtensions(Extension... extensions) { + this.configBuilder.extensions(Arrays.asList(extensions)); + } + + public void setEncoders(List> encoders) { + this.configBuilder.encoders(encoders); + } + + public void setDecoders(List> decoders) { + this.configBuilder.decoders(decoders); + } + + public void setConfigurator(Configurator configurator) { + this.configBuilder.configurator(configurator); + } + + @Override + protected Session connect(Object endpoint) throws DeploymentException, IOException { + Endpoint typedEndpoint = (Endpoint) endpoint; + ClientEndpointConfig endpointConfig = this.configBuilder.build(); + return getWebSocketContainer().connectToServer(typedEndpoint, endpointConfig, getUri()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java index 9c287e7071..0596a27a81 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java @@ -19,7 +19,6 @@ import java.util.Map; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; -import javax.websocket.server.ServerContainerProvider; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; @@ -44,7 +43,7 @@ import org.springframework.util.ObjectUtils; * @author Rossen Stoyanchev * @since 4.0 */ -public class EndpointExporter implements InitializingBean, BeanPostProcessor, BeanFactoryAware { +public abstract class EndpointExporter implements InitializingBean, BeanPostProcessor, BeanFactoryAware { private static Log logger = LogFactory.getLog(EndpointExporter.class); @@ -112,7 +111,7 @@ public class EndpointExporter implements InitializingBean, BeanPostProcessor, Be if (logger.isInfoEnabled()) { logger.info("Detected @ServerEndpoint bean '" + beanName + "', registering it as an endpoint by type"); } - ServerContainerProvider.getServerContainer().addEndpoint(beanType); + getServerContainer().addEndpoint(beanType); } catch (DeploymentException e) { throw new IllegalStateException("Failed to register @ServerEndpoint bean type " + beanName, e); @@ -121,10 +120,16 @@ public class EndpointExporter implements InitializingBean, BeanPostProcessor, Be } } + /** + * Return the {@link ServerContainer} instance, a process which is undefined outside + * of standalone containers (section 6.4 of the spec). + */ + protected abstract ServerContainer getServerContainer(); + @Override public void afterPropertiesSet() throws Exception { - ServerContainer serverContainer = ServerContainerProvider.getServerContainer(); + ServerContainer serverContainer = getServerContainer(); Assert.notNull(serverContainer, "javax.websocket.server.ServerContainer not available"); if (this.maxSessionIdleTimeout != null) { @@ -159,7 +164,7 @@ public class EndpointExporter implements InitializingBean, BeanPostProcessor, Be logger.info("Registering bean '" + beanName + "' as javax.websocket.Endpoint under path " + sec.getPath()); } - ServerContainerProvider.getServerContainer().addEndpoint(sec); + getServerContainer().addEndpoint(sec); } catch (DeploymentException e) { throw new IllegalStateException("Failed to deploy Endpoint bean " + bean, e); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 385f8ece2b..12367774fb 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -37,8 +37,6 @@ 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.WebSocketHandler; -import org.springframework.websocket.endpoint.StandardWebSocketHandlerAdapter; /** @@ -60,7 +58,7 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final Class endpointClass; - private final Object bean; + private final Object endpointBean; private List subprotocols = new ArrayList(); @@ -97,9 +95,7 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw Assert.isTrue((endpointClass != null || bean != null), "Neither endpoint class nor endpoint bean provided"); this.path = path; this.endpointClass = endpointClass; - this.bean = bean; - // this will fail if the bean is not a valid Endpoint type - getEndpointClass(); + this.endpointBean = bean; } @Override @@ -113,17 +109,14 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw if (this.endpointClass != null) { return this.endpointClass; } - Class beanClass = this.bean.getClass(); + Class beanClass = this.endpointBean.getClass(); if (beanClass.equals(String.class)) { - beanClass = this.beanFactory.getType((String) this.bean); + beanClass = this.beanFactory.getType((String) this.endpointBean); } beanClass = ClassUtils.getUserClass(beanClass); if (Endpoint.class.isAssignableFrom(beanClass)) { return (Class) beanClass; } - else if (WebSocketHandler.class.isAssignableFrom(beanClass)) { - return StandardWebSocketHandlerAdapter.class; - } else { throw new IllegalStateException("Invalid endpoint bean: must be of type ... TODO "); } @@ -138,16 +131,11 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw } return wac.getAutowireCapableBeanFactory().createBean(this.endpointClass); } - Object bean = this.bean; - if (this.bean instanceof String) { - bean = this.beanFactory.getBean((String) this.bean); - } - if (bean instanceof WebSocketHandler) { - return new StandardWebSocketHandlerAdapter((WebSocketHandler) bean); - } - else { - return (Endpoint) bean; + Object bean = this.endpointBean; + if (this.endpointBean instanceof String) { + bean = this.beanFactory.getBean((String) this.endpointBean); } + return (Endpoint) bean; } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java index 2e36774f79..d62b2f44c6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java @@ -17,18 +17,25 @@ package org.springframework.websocket.server.endpoint; import javax.servlet.ServletContext; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerContainerProvider; -import org.apache.tomcat.websocket.server.WsServerContainer; +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; @@ -38,17 +45,18 @@ public class ServletEndpointExporter extends EndpointExporter implements Servlet } public ServletContext getServletContext() { - return servletContext; + return this.servletContext; } @Override - public void afterPropertiesSet() throws Exception { - - // TODO: this is needed (see WsListener) but remove hard dependency - WsServerContainer sc = WsServerContainer.getServerContainer(); - sc.setServletContext(this.servletContext); - - super.afterPropertiesSet(); + 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; } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java index d412fd9e29..6ed65012e6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java @@ -21,6 +21,8 @@ import java.util.Map; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig.Configurator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; @@ -35,27 +37,36 @@ import org.springframework.web.context.WebApplicationContext; */ public class SpringConfigurator extends Configurator { + private static Log logger = LogFactory.getLog(SpringConfigurator.class); + @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); if (wac == null) { - throw new IllegalStateException("Failed to find WebApplicationContext. " - + "Was org.springframework.web.context.ContextLoader used to load the WebApplicationContext?"); + String message = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?"; + logger.error(message); + throw new IllegalStateException(message); } Map beans = wac.getBeansOfType(endpointClass); if (beans.isEmpty()) { - // Initialize a new bean instance + if (logger.isTraceEnabled()) { + logger.trace("Creating new @ServerEndpoint instance of type " + endpointClass); + } return wac.getAutowireCapableBeanFactory().createBean(endpointClass); } if (beans.size() == 1) { - // Return the matching bean instance + if (logger.isTraceEnabled()) { + logger.trace("Using @ServerEndpoint singleton " + beans.keySet().iterator().next()); + } return beans.values().iterator().next(); } else { - // This should never happen (@ServerEndpoint has a single path mapping) .. + // This should never happen .. + String message = "Found more than one matching @ServerEndpoint beans of type " + endpointClass; + logger.error(message); throw new IllegalStateException("Found more than one matching beans of type " + endpointClass); } } From 41153efd03fa3b2811932170bea9810533f62b52 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 5 Apr 2013 09:48:11 -0400 Subject: [PATCH 09/51] Polish in EndpointRegistration --- .../AbstractEndpointConnectionManager.java | 15 ++-- .../server/endpoint/EndpointRegistration.java | 74 ++++++++++--------- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java index 1cb0d35102..a95b9ab53f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java @@ -101,9 +101,8 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo } /** - * Set whether to auto-connect to the {@link #setDefaultUri(URI) default URI} after - * this endpoint connection factory has been initialized and the Spring context has - * been refreshed. + * Set whether to auto-connect to the remote endpoint after this connection manager + * has been initialized and the Spring context has been refreshed. *

Default is "false". */ public void setAutoStartup(boolean autoStartup) { @@ -112,17 +111,17 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo /** * Return the value for the 'autoStartup' property. If "true", this endpoint - * connection factory will connect to the {@link #setDefaultUri(URI) default URI} upon - * a ContextRefreshedEvent. + * connection manager will connect to the remote endpoint upon a + * ContextRefreshedEvent. */ public boolean isAutoStartup() { return this.autoStartup; } /** - * Specify the phase in which this endpoint connection factory should be - * auto-connected and closed. The startup order proceeds from lowest to highest, and - * the shutdown order is the reverse of that. By default this value is + * Specify the phase in which a connection should be established to the remote + * endpoint and subsequently closed. The startup order proceeds from lowest to + * highest, and the shutdown order is the reverse of that. By default this value is * Integer.MAX_VALUE meaning that this endpoint connection factory connects as late as * possible and is closed as soon as possible. */ diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 12367774fb..843cb07598 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -17,7 +17,6 @@ package org.springframework.websocket.server.endpoint; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,6 +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.StandardWebSocketHandlerAdapter; /** @@ -60,16 +60,20 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final Object endpointBean; + private List> encoders; + + private List> decoders; + private List subprotocols = new ArrayList(); private List extensions = new ArrayList(); - private Map userProperties = new HashMap(); + private final Map userProperties = new HashMap(); + + private Configurator configurator = new Configurator() {}; private BeanFactory beanFactory; - private final Configurator configurator = new Configurator() {}; - /** * Class constructor with the {@code javax.webscoket.Endpoint} class. @@ -138,18 +142,13 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw return (Endpoint) bean; } - @Override - public List getSubprotocols() { - return this.subprotocols; - } - public void setSubprotocols(List subprotocols) { this.subprotocols = subprotocols; } @Override - public List getExtensions() { - return this.extensions; + public List getSubprotocols() { + return this.subprotocols; } public void setExtensions(List extensions) { @@ -157,23 +156,37 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw this.extensions = extensions; } + @Override + public List getExtensions() { + return this.extensions; + } + + public void setUserProperties(Map userProperties) { + this.userProperties.clear(); + this.userProperties.putAll(userProperties); + } + @Override public Map getUserProperties() { return this.userProperties; } - public void setUserProperties(Map userProperties) { - this.userProperties = userProperties; + public void setEncoders(List> encoders) { + this.encoders = encoders; } @Override public List> getEncoders() { - return Collections.emptyList(); + return this.encoders; + } + + public void setDecoders(List> decoders) { + this.decoders = decoders; } @Override public List> getDecoders() { - return Collections.emptyList(); + return this.decoders; } @Override @@ -181,6 +194,13 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw this.beanFactory = beanFactory; } + /** + * The {@link Configurator#getEndpointInstance(Class)} method is always ignored. + */ + public void setConfigurator(Configurator configurator) { + this.configurator = configurator; + } + @Override public Configurator getConfigurator() { return new Configurator() { @@ -191,37 +211,21 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw } @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { - EndpointRegistration.this.modifyHandshake(request, response); + EndpointRegistration.this.configurator.modifyHandshake(sec, request, response); } @Override public boolean checkOrigin(String originHeaderValue) { - return EndpointRegistration.this.checkOrigin(originHeaderValue); + return EndpointRegistration.this.configurator.checkOrigin(originHeaderValue); } @Override public String getNegotiatedSubprotocol(List supported, List requested) { - return EndpointRegistration.this.selectSubProtocol(requested); + return EndpointRegistration.this.configurator.getNegotiatedSubprotocol(supported, requested); } @Override public List getNegotiatedExtensions(List installed, List requested) { - return EndpointRegistration.this.selectExtensions(requested); + return EndpointRegistration.this.configurator.getNegotiatedExtensions(installed, requested); } }; } - protected void modifyHandshake(HandshakeRequest request, HandshakeResponse response) { - this.configurator.modifyHandshake(this, request, response); - } - - protected boolean checkOrigin(String originHeaderValue) { - return this.configurator.checkOrigin(originHeaderValue); - } - - protected String selectSubProtocol(List requested) { - return this.configurator.getNegotiatedSubprotocol(getSubprotocols(), requested); - } - - protected List selectExtensions(List requested) { - return this.configurator.getNegotiatedExtensions(getExtensions(), requested); - } - } From 4ad6091510f4ddbd864cd23e64e3752b8f3a0659 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 5 Apr 2013 16:34:33 -0400 Subject: [PATCH 10/51] Debug and test SockJS server support --- build.gradle | 1 + .../sockjs/server/AbstractServerSession.java | 3 - .../sockjs/server/AbstractSockJsService.java | 36 ++++------ ...apter.java => SockJsWebSocketHandler.java} | 21 +++--- ...va => StandardWebSocketServerSession.java} | 9 +-- .../server/support/DefaultSockJsService.java | 11 +-- .../DefaultTransportHandlerRegistrar.java | 3 + .../support/SockJsServiceHandlerMapping.java | 70 +++++++++++++++++++ .../SockJsServiceHttpRequestHandler.java | 66 +++++++++++++++++ .../AbstractHttpSendingTransportHandler.java | 15 ++-- .../AbstractStreamingTransportHandler.java | 8 +-- .../transport/HtmlFileTransportHandler.java | 8 +-- .../JsonpPollingTransportHandler.java | 6 +- .../transport/StreamingHttpServerSession.java | 3 +- .../transport/WebSocketTransportHandler.java | 10 +-- .../websocket/WebSocketSession.java | 2 + ...ter.java => StandardWebSocketSession.java} | 12 +++- ...ter.java => WebSocketHandlerEndpoint.java} | 16 +++-- .../EndpointHandshakeRequestHandler.java | 4 +- .../server/endpoint/EndpointRegistration.java | 8 +-- 20 files changed, 229 insertions(+), 83 deletions(-) rename spring-websocket/src/main/java/org/springframework/sockjs/server/{WebSocketSockJsHandlerAdapter.java => SockJsWebSocketHandler.java} (78%) rename spring-websocket/src/main/java/org/springframework/sockjs/server/{SockJsWebSocketSessionAdapter.java => StandardWebSocketServerSession.java} (81%) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java rename spring-websocket/src/main/java/org/springframework/websocket/endpoint/{WebSocketStandardSessionAdapter.java => StandardWebSocketSession.java} (76%) rename spring-websocket/src/main/java/org/springframework/websocket/endpoint/{StandardWebSocketHandlerAdapter.java => WebSocketHandlerEndpoint.java} (87%) diff --git a/build.gradle b/build.gradle index e8986cd15a..1708b1587c 100644 --- a/build.gradle +++ b/build.gradle @@ -515,6 +515,7 @@ project("spring-websocket") { compile(project(":spring-core")) compile(project(":spring-context")) compile(project(":spring-web")) + optional(project(":spring-webmvc")) optional("org.apache.tomcat:tomcat-servlet-api:8.0-SNAPSHOT") // TODO: replace with "javax.servlet:javax.servlet-api" optional("org.apache.tomcat:tomcat-websocket-api:8.0-SNAPSHOT") // TODO: replace with "javax.websocket:javax.websocket-api" diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java index 89941b9840..67e193f314 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java @@ -17,12 +17,9 @@ package org.springframework.sockjs.server; import java.io.EOFException; -import java.io.IOException; import java.util.Date; import java.util.concurrent.ScheduledFuture; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSession; import org.springframework.sockjs.SockJsSessionSupport; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index d56303fa0e..fb7c5edcf5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -32,9 +32,7 @@ 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.SockJsHandler; import org.springframework.sockjs.TransportType; -import org.springframework.sockjs.server.support.DefaultTransportHandlerRegistrar; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.DigestUtils; @@ -56,7 +54,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { private static final int ONE_YEAR = 365 * 24 * 60 * 60; - private String sockJsServiceName = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); + private final String prefix; private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js"; @@ -78,31 +76,28 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { /** * Class constructor... * + * @param prefix the path prefix for the SockJS service. All requests with a path + * that begins with the specified prefix will be handled by this service. In a + * Servlet container this is the path within the current servlet mapping. */ - public AbstractSockJsService() { + public AbstractSockJsService(String prefix) { + Assert.hasText(prefix, "prefix is required"); + this.prefix = prefix; this.heartbeatScheduler = createScheduler("SockJs-heartbeat-"); } protected TaskScheduler createScheduler(String threadNamePrefix) { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setThreadNamePrefix(threadNamePrefix); + scheduler.afterPropertiesSet(); return scheduler; } /** - * A unique name for the service, possibly the prefix at which it is deployed. - * Used mainly for logging purposes. + * The path prefix to which the SockJS service is mapped. */ - public void setSockJsServiceName(String serviceName) { - this.sockJsServiceName = serviceName; - } - - /** - * The SockJS service name. - * @see #setSockJsServiceName(String) - */ - public String getSockJsServiceName() { - return this.sockJsServiceName; + public String getPrefix() { + return this.prefix; } /** @@ -178,9 +173,8 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { this.heartbeatScheduler = heartbeatScheduler; } - public AbstractSockJsService setDisconnectDelay(long disconnectDelay) { + public void setDisconnectDelay(long disconnectDelay) { this.disconnectDelay = disconnectDelay; - return this; } public long getDisconnectDelay() { @@ -193,9 +187,8 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { *

* The default value is "true". */ - public AbstractSockJsService setWebSocketsEnabled(boolean webSocketsEnabled) { + public void setWebSocketsEnabled(boolean webSocketsEnabled) { this.webSocketsEnabled = webSocketsEnabled; - return this; } /** @@ -212,9 +205,8 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { * heartbeats, only raw WebSocket protocol. This property allows setting a * handler for requests for raw WebSocket communication. */ - public AbstractSockJsService setWebsocketHandler(HandshakeRequestHandler handshakeRequestHandler) { + public void setWebsocketHandler(HandshakeRequestHandler handshakeRequestHandler) { this.handshakeRequestHandler = handshakeRequestHandler; - return this; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java similarity index 78% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java index 95bb75ff08..b271994862 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/WebSocketSockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java @@ -29,38 +29,41 @@ 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 WebSocketSockJsHandlerAdapter implements WebSocketHandler { +public class SockJsWebSocketHandler implements WebSocketHandler { - private static final Log logger = LogFactory.getLog(WebSocketSockJsHandlerAdapter.class); + private static final Log logger = LogFactory.getLog(SockJsWebSocketHandler.class); - private final SockJsWebSocketSessionAdapter sockJsSession; + private final StandardWebSocketServerSession sockJsSession; // TODO: the JSON library used must be configurable private final ObjectMapper objectMapper = new ObjectMapper(); - public WebSocketSockJsHandlerAdapter(SockJsWebSocketSessionAdapter sockJsSession) { + public SockJsWebSocketHandler(StandardWebSocketServerSession sockJsSession) { this.sockJsSession = sockJsSession; } @Override public void newSession(WebSocketSession webSocketSession) throws Exception { - logger.debug("WebSocket connection established"); - webSocketSession.sendText(SockJsFrame.openFrame().getContent()); + if (logger.isDebugEnabled()) { + logger.debug("New session: " + webSocketSession); + } this.sockJsSession.setWebSocketSession(webSocketSession); } @Override public void handleTextMessage(WebSocketSession session, String message) throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("Received payload " + message + " for " + sockJsSession); + if (logger.isTraceEnabled()) { + logger.trace("Received payload " + message + " for " + sockJsSession); } if (StringUtils.isEmpty(message)) { - logger.debug("Ignoring empty payload"); + logger.trace("Ignoring empty payload"); return; } try { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java similarity index 81% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java index ea2610ab0d..183e964305 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java @@ -27,26 +27,27 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsWebSocketSessionAdapter extends AbstractServerSession { +public class StandardWebSocketServerSession extends AbstractServerSession { - private static Log logger = LogFactory.getLog(SockJsWebSocketSessionAdapter.class); + private static Log logger = LogFactory.getLog(StandardWebSocketServerSession.class); private WebSocketSession webSocketSession; - public SockJsWebSocketSessionAdapter(String sessionId, SockJsHandler delegate, SockJsConfiguration sockJsConfig) { + 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); + return ((this.webSocketSession != null) && this.webSocketSession.isOpen()); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index c00c49222a..d6dcb87562 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -64,7 +64,8 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans * Class constructor... * */ - public DefaultSockJsService(SockJsHandler sockJsHandler) { + public DefaultSockJsService(String prefix, SockJsHandler sockJsHandler) { + super(prefix); Assert.notNull(sockJsHandler, "sockJsHandler is required"); this.sockJsHandler = sockJsHandler; this.sessionTimeoutScheduler = createScheduler("SockJs-sessionTimeout-"); @@ -105,23 +106,23 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans try { int count = sessions.size(); if (logger.isTraceEnabled() && (count != 0)) { - logger.trace("Checking " + count + " session(s) for timeouts [" + getSockJsServiceName() + "]"); + logger.trace("Checking " + count + " session(s) for timeouts [" + getPrefix() + "]"); } for (SockJsSessionSupport session : sessions.values()) { if (session.getTimeSinceLastActive() > getDisconnectDelay()) { if (logger.isTraceEnabled()) { - logger.trace("Removing " + session + " for [" + getSockJsServiceName() + "]"); + logger.trace("Removing " + session + " for [" + getPrefix() + "]"); } session.close(); sessions.remove(session.getId()); } } if (logger.isTraceEnabled() && (count != 0)) { - logger.trace(sessions.size() + " remaining session(s) [" + getSockJsServiceName() + "]"); + logger.trace(sessions.size() + " remaining session(s) [" + getPrefix() + "]"); } } catch (Throwable t) { - logger.error("Failed to complete session timeout checks for [" + getSockJsServiceName() + "]", t); + logger.error("Failed to complete session timeout checks for [" + getPrefix() + "]", t); } } }, getDisconnectDelay()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java index a6c50e4977..6f41298d60 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java @@ -21,6 +21,7 @@ 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; @@ -36,6 +37,8 @@ public class DefaultTransportHandlerRegistrar implements TransportHandlerRegistr public void registerTransportHandlers(TransportHandlerRegistry registry) { + registry.registerHandler(new WebSocketTransportHandler()); + registry.registerHandler(new XhrPollingTransportHandler()); registry.registerHandler(new XhrTransportHandler()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java new file mode 100644 index 0000000000..c40565ecac --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java @@ -0,0 +1,70 @@ +/* + * 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.support; + +import java.util.Arrays; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.sockjs.server.AbstractSockJsService; +import org.springframework.web.servlet.handler.AbstractHandlerMapping; + +/** + * A Spring MVC HandlerMapping matching requests to SockJS services by prefix. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SockJsServiceHandlerMapping extends AbstractHandlerMapping { + + private static Log logger = LogFactory.getLog(SockJsServiceHandlerMapping.class); + + private final List sockJsServices; + + + public SockJsServiceHandlerMapping(AbstractSockJsService... sockJsServices) { + this.sockJsServices = Arrays.asList(sockJsServices); + } + + @Override + protected Object getHandlerInternal(HttpServletRequest request) throws Exception { + + String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); + if (logger.isDebugEnabled()) { + logger.debug("Looking for SockJS service match to path " + lookupPath); + } + + for (AbstractSockJsService service : this.sockJsServices) { + if (lookupPath.startsWith(service.getPrefix())) { + if (logger.isDebugEnabled()) { + logger.debug("Matched to " + service); + } + String sockJsPath = lookupPath.substring(service.getPrefix().length()); + return new SockJsServiceHttpRequestHandler(sockJsPath, service); + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Did not find a match"); + } + + return null; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java new file mode 100644 index 0000000000..57d3baf333 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java @@ -0,0 +1,66 @@ +/* + * 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.support; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.server.AsyncServletServerHttpRequest; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.sockjs.server.AbstractSockJsService; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.util.NestedServletException; + +/** + * A Spring MVC {@link HttpRequestHandler} wrapping the invocation of a SockJS service. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SockJsServiceHttpRequestHandler implements HttpRequestHandler { + + private final String sockJsPath; + + private final AbstractSockJsService sockJsService; + + + public SockJsServiceHttpRequestHandler(String sockJsPath, AbstractSockJsService sockJsService) { + this.sockJsService = sockJsService; + this.sockJsPath = sockJsPath; + } + + @Override + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response); + ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + + try { + this.sockJsService.handleRequest(httpRequest, httpResponse, this.sockJsPath); + } + catch (Exception ex) { + // TODO + throw new NestedServletException("SockJS service failure", ex); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 90ef3c29a6..9a59a79e67 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -15,6 +15,8 @@ */ package org.springframework.sockjs.server.transport; +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.MediaType; @@ -37,14 +39,19 @@ public abstract class AbstractHttpSendingTransportHandler implements TransportHa @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) - throws Exception { - - AbstractHttpServerSession httpServerSession = (AbstractHttpServerSession) session; + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsSessionSupport session) throws Exception { // Set content type before writing response.getHeaders().setContentType(getContentType()); + AbstractHttpServerSession httpServerSession = (AbstractHttpServerSession) session; + handleRequestInternal(request, response, httpServerSession); + } + + protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSession httpServerSession) throws Exception, IOException { + if (httpServerSession.isNew()) { handleNewSession(request, response, httpServerSession); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java index 10816de521..e7740d6d49 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -20,7 +20,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.SockJsSessionSupport; import org.springframework.sockjs.server.SockJsConfiguration; @@ -39,12 +38,11 @@ public abstract class AbstractStreamingTransportHandler extends AbstractHttpSend } @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) - throws Exception { + public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSession session) throws Exception { writePrelude(request, response); - - super.handleRequest(request, response, session); + super.handleRequestInternal(request, response, session); } protected abstract void writePrelude(ServerHttpRequest request, ServerHttpResponse response) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index cc1e98ac22..adc983e0b4 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -22,7 +22,6 @@ 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.SockJsSessionSupport; import org.springframework.sockjs.TransportType; import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; @@ -78,8 +77,8 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler } @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) - throws Exception { + public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSession session) throws Exception { String callback = request.getQueryParams().getFirst("c"); if (! StringUtils.hasText(callback)) { @@ -87,8 +86,7 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); return; } - - super.handleRequest(request, response, session); + super.handleRequestInternal(request, response, session); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index c696c6cf12..bff94ad72e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -22,7 +22,6 @@ 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.TransportType; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -56,8 +55,8 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) - throws Exception { + public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSession session) throws Exception { String callback = request.getQueryParams().getFirst("c"); if (! StringUtils.hasText(callback)) { @@ -65,7 +64,6 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); return; } - super.handleRequest(request, response, session); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java index d327568331..2eb846683f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java @@ -43,7 +43,8 @@ public class StreamingHttpServerSession extends AbstractHttpServerSession { this.byteCount += frame.getContentBytes().length + 1; if (logger.isTraceEnabled()) { - logger.trace(this.byteCount + " bytes written, " + getMessageCache().size() + " more messages"); + logger.trace(this.byteCount + " bytes written so far, " + + getMessageCache().size() + " more messages not flushed"); } if (this.byteCount >= getSockJsConfig().getStreamBytesLimit()) { if (logger.isTraceEnabled()) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 9abf049b16..40ced92ad5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -22,9 +22,9 @@ 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.SockJsWebSocketSessionAdapter; +import org.springframework.sockjs.server.StandardWebSocketServerSession; import org.springframework.sockjs.server.TransportHandler; -import org.springframework.sockjs.server.WebSocketSockJsHandlerAdapter; +import org.springframework.sockjs.server.SockJsWebSocketHandler; import org.springframework.websocket.server.HandshakeRequestHandler; import org.springframework.websocket.server.endpoint.EndpointHandshakeRequestHandler; @@ -44,15 +44,15 @@ public class WebSocketTransportHandler implements TransportHandler { @Override public SockJsSessionSupport createSession(String sessionId, SockJsHandler handler, SockJsConfiguration config) { - return new SockJsWebSocketSessionAdapter(sessionId, handler, config); + return new StandardWebSocketServerSession(sessionId, handler, config); } @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception { - SockJsWebSocketSessionAdapter sockJsSession = (SockJsWebSocketSessionAdapter) session; - WebSocketSockJsHandlerAdapter webSocketHandler = new WebSocketSockJsHandlerAdapter(sockJsSession); + StandardWebSocketServerSession sockJsSession = (StandardWebSocketServerSession) session; + SockJsWebSocketHandler webSocketHandler = new SockJsWebSocketHandler(sockJsSession); HandshakeRequestHandler handshakeRequestHandler = new EndpointHandshakeRequestHandler(webSocketHandler); handshakeRequestHandler.doHandshake(request, response); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index 947a565bb7..d930dde9d0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -25,6 +25,8 @@ package org.springframework.websocket; */ public interface WebSocketSession { + boolean isOpen(); + void sendText(String text) throws Exception; void close(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketStandardSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java similarity index 76% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketStandardSessionAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index ce3dd1c531..302a033392 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketStandardSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -22,21 +22,27 @@ import org.springframework.websocket.WebSocketSession; /** + * A {@link WebSocketSession} that delegates to a {@link javax.websocket.Session}. * * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketStandardSessionAdapter implements WebSocketSession { +public class StandardWebSocketSession implements WebSocketSession { - private static Log logger = LogFactory.getLog(WebSocketStandardSessionAdapter.class); + private static Log logger = LogFactory.getLog(StandardWebSocketSession.class); private javax.websocket.Session session; - public WebSocketStandardSessionAdapter(javax.websocket.Session session) { + public StandardWebSocketSession(javax.websocket.Session session) { this.session = session; } + @Override + public boolean isOpen() { + return ((this.session != null) && this.session.isOpen()); + } + @Override public void sendText(String text) throws Exception { logger.trace("Sending text message: " + text); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java similarity index 87% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 2722cb09ed..da3d7a768a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -32,28 +32,31 @@ import org.springframework.websocket.WebSocketHandler; /** + * An {@link Endpoint} that delegates to a {@link WebSocketHandler}. * * @author Rossen Stoyanchev * @since 4.0 */ -public class StandardWebSocketHandlerAdapter extends Endpoint { +public class WebSocketHandlerEndpoint extends Endpoint { - private static Log logger = LogFactory.getLog(StandardWebSocketHandlerAdapter.class); + private static Log logger = LogFactory.getLog(WebSocketHandlerEndpoint.class); private final WebSocketHandler webSocketHandler; private final Map sessionMap = new ConcurrentHashMap(); - public StandardWebSocketHandlerAdapter(WebSocketHandler webSocketHandler) { + public WebSocketHandlerEndpoint(WebSocketHandler webSocketHandler) { this.webSocketHandler = webSocketHandler; } @Override public void onOpen(javax.websocket.Session session, EndpointConfig config) { - logger.debug("New WebSocket session: " + session); + if (logger.isDebugEnabled()) { + logger.debug("New session: " + session); + } try { - WebSocketSession webSocketSession = new WebSocketStandardSessionAdapter(session); + WebSocketSession webSocketSession = new StandardWebSocketSession(session); this.sessionMap.put(session.getId(), webSocketSession); session.addMessageHandler(new StandardMessageHandler(session.getId())); this.webSocketHandler.newSession(webSocketSession); @@ -75,7 +78,6 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { this.sessionMap.remove(id); int code = closeReason.getCloseCode().getCode(); String reason = closeReason.getReasonPhrase(); - webSocketSession.close(code, reason); this.webSocketHandler.sessionClosed(webSocketSession, code, reason); } catch (Throwable ex) { @@ -119,7 +121,7 @@ public class StandardWebSocketHandlerAdapter extends Endpoint { } try { WebSocketSession session = getSession(this.sessionId); - StandardWebSocketHandlerAdapter.this.webSocketHandler.handleTextMessage(session, message); + WebSocketHandlerEndpoint.this.webSocketHandler.handleTextMessage(session, message); } catch (Throwable ex) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java index aae44c148c..abb66ff4cc 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java @@ -23,7 +23,7 @@ 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.StandardWebSocketHandlerAdapter; +import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.server.AbstractHandshakeRequestHandler; @@ -43,7 +43,7 @@ public class EndpointHandshakeRequestHandler extends AbstractHandshakeRequestHan public EndpointHandshakeRequestHandler(WebSocketHandler webSocketHandler) { - this(new StandardWebSocketHandlerAdapter(webSocketHandler)); + this(new WebSocketHandlerEndpoint(webSocketHandler)); } public EndpointHandshakeRequestHandler(Endpoint endpoint) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 843cb07598..9dfc509042 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -36,14 +36,14 @@ 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.StandardWebSocketHandlerAdapter; +import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; /** * An implementation of {@link javax.websocket.server.ServerEndpointConfig} that also * holds the target {@link javax.websocket.Endpoint} as a reference or a bean name. * The target can also be {@link org.springframework.websocket.WebSocketHandler}, in - * which case it will be adapted via {@link StandardWebSocketHandlerAdapter}. + * which case it will be adapted via {@link WebSocketHandlerEndpoint}. * *

* Beans of this type are detected by {@link EndpointExporter} and @@ -60,9 +60,9 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final Object endpointBean; - private List> encoders; + private List> encoders = new ArrayList>(); - private List> decoders; + private List> decoders = new ArrayList>(); private List subprotocols = new ArrayList(); From 6bd6311214c9d8a4c7c0af56a71c435c777fabb8 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 9 Apr 2013 09:43:20 -0400 Subject: [PATCH 11/51] 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 --- .../http/server/ServletServerHttpRequest.java | 6 +- .../sockjs/SockJsSessionSupport.java | 21 +-- .../sockjs/server/AbstractServerSession.java | 23 ++-- .../sockjs/server/AbstractSockJsService.java | 20 +-- .../sockjs/server/SockJsConfiguration.java | 8 ++ .../sockjs/server/SockJsWebSocketHandler.java | 96 -------------- .../StandardWebSocketServerSession.java | 80 ------------ .../sockjs/server/TransportHandler.java | 8 +- .../server/TransportHandlerRegistrar.java | 2 +- .../sockjs/{ => server}/TransportType.java | 8 +- .../server/support/DefaultSockJsService.java | 86 ++++++++---- .../DefaultTransportHandlerRegistrar.java | 40 ++++-- ...AbstractHttpReceivingTransportHandler.java | 17 ++- .../AbstractHttpSendingTransportHandler.java | 23 +++- .../transport/AbstractHttpServerSession.java | 7 +- .../AbstractSockJsWebSocketHandler.java | 98 ++++++++++++++ .../AbstractStreamingTransportHandler.java | 9 +- .../AbstractWebSocketTransportHandler.java | 80 ++++++++++++ .../EndpointWebSocketTransportHandler.java | 42 ++++++ .../EventSourceTransportHandler.java | 7 +- .../transport/HtmlFileTransportHandler.java | 7 +- .../JsonpPollingTransportHandler.java | 11 +- .../transport/JsonpTransportHandler.java | 2 +- .../transport/PollingHttpServerSession.java | 5 +- .../transport/SockJsWebSocketHandler.java | 122 ++++++++++++++++++ .../transport/StreamingHttpServerSession.java | 5 +- .../WebSocketSockJsHandlerAdapter.java | 76 +++++++++++ .../transport/WebSocketTransportHandler.java | 60 --------- .../transport/XhrPollingTransportHandler.java | 11 +- .../XhrStreamingTransportHandler.java | 7 +- .../server/transport/XhrTransportHandler.java | 2 +- .../AbstractEndpointConnectionManager.java | 2 + ...ler.java => AbstractHandshakeHandler.java} | 51 +++++++- ...uestHandler.java => HandshakeHandler.java} | 2 +- ...ler.java => EndpointHandshakeHandler.java} | 40 +++--- .../server/endpoint/EndpointRegistration.java | 2 +- .../TomcatRequestUpgradeStrategy.java | 2 +- ...a => WebSocketRequestUpgradeStrategy.java} | 8 +- .../StandardWebSocketSession.java | 2 +- .../WebSocketHandlerEndpoint.java | 10 +- .../{endpoint => support}/package-info.java | 2 +- 41 files changed, 727 insertions(+), 383 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java rename spring-websocket/src/main/java/org/springframework/sockjs/{ => server}/TransportType.java (92%) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java rename spring-websocket/src/main/java/org/springframework/websocket/server/{AbstractHandshakeRequestHandler.java => AbstractHandshakeHandler.java} (76%) rename spring-websocket/src/main/java/org/springframework/websocket/server/{HandshakeRequestHandler.java => HandshakeHandler.java} (95%) rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{EndpointHandshakeRequestHandler.java => EndpointHandshakeHandler.java} (62%) rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{EndpointRequestUpgradeStrategy.java => WebSocketRequestUpgradeStrategy.java} (81%) rename spring-websocket/src/main/java/org/springframework/websocket/{endpoint => support}/StandardWebSocketSession.java (97%) rename spring-websocket/src/main/java/org/springframework/websocket/{endpoint => support}/WebSocketHandlerEndpoint.java (92%) rename spring-websocket/src/main/java/org/springframework/websocket/{endpoint => support}/package-info.java (62%) diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index 38d6e3df42..5f4f90e9a8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -134,8 +134,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest { public Cookies getCookies() { if (this.cookies == null) { this.cookies = new Cookies(); - for (Cookie cookie : this.servletRequest.getCookies()) { - this.cookies.addCookie(cookie.getName(), cookie.getValue()); + if (this.servletRequest.getCookies() != null) { + for (Cookie cookie : this.servletRequest.getCookies()) { + this.cookies.addCookie(cookie.getName(), cookie.getValue()); + } } } return this.cookies; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java index f35e1e8fd5..6e20a0b131 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java @@ -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() { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java index 67e193f314..b4624914bb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java @@ -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(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index fb7c5edcf5..412704b335 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -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)) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java index c8242874a6..a122a769e3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java @@ -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(); + } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java deleted file mode 100644 index b271994862..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsWebSocketHandler.java +++ /dev/null @@ -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(); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java deleted file mode 100644 index 183e964305..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/StandardWebSocketServerSession.java +++ /dev/null @@ -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(); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 3efe70404d..93ada27d1e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java index 7ff8b8f553..f89c2ea81b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java @@ -23,6 +23,6 @@ package org.springframework.sockjs.server; */ public interface TransportHandlerRegistrar { - void registerTransportHandlers(TransportHandlerRegistry registry); + void registerTransportHandlers(TransportHandlerRegistry registry, SockJsConfiguration config); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java similarity index 92% rename from spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java index 130e1f2f44..5373d3edc8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java @@ -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() { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index d6dcb87562..69f325da2c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -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 sockJsHandlerClass; private final SockJsHandler sockJsHandler; @@ -59,17 +63,24 @@ public class DefaultSockJsService extends AbstractSockJsService implements Trans private final Map transportHandlers = new HashMap(); + private AutowireCapableBeanFactory beanFactory; + + + public DefaultSockJsService(String prefix, Class sockJsHandlerClass) { + this(prefix, sockJsHandlerClass, null); + } - /** - * Class constructor... - * - */ public DefaultSockJsService(String prefix, SockJsHandler sockJsHandler) { + this(prefix, null, sockJsHandler); + } + + private DefaultSockJsService(String prefix, Class 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; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java index 6f41298d60..fbbeab48a4 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java @@ -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); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 08ad9b4e76..0597e8621f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -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 diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 9a59a79e67..dffa4389c0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -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, diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java index ad4939c900..0c0d60ae66 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java @@ -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) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java new file mode 100644 index 0000000000..9f13cd10b1 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java @@ -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 sessions = + new ConcurrentHashMap(); + + + 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(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java index e7740d6d49..89b076d93f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -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 diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java new file mode 100644 index 0000000000..0221c33a77 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java @@ -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); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java new file mode 100644 index 0000000000..b6762111f3 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java @@ -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); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index 2e05a41927..0725e6284c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index adc983e0b4..188c59d9ab 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index bff94ad72e..28fe888714 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -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 diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index 694bb94453..e839b77f4e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -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 { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java index f7b6b13a07..3dd0e8d517 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java @@ -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 diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java new file mode 100644 index 0000000000..db5da4c362 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -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(); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java index 2eb846683f..accff89ee6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java @@ -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() { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java new file mode 100644 index 0000000000..e617ab794f --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java @@ -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(); + } + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java deleted file mode 100644 index 40ced92ad5..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ /dev/null @@ -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); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index 7301377608..78733099e0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -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()); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index d141cb2c7e..6a69747497 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java index f5fe4b13ff..e38417ac5a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java @@ -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 { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java index a95b9ab53f..8e5d3f754a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java @@ -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 { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java similarity index 76% rename from spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java index 0f8de38855..d3807d0146 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java @@ -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 handlerClass; + private List protocols; + private AutowireCapableBeanFactory beanFactory; + + + public AbstractHandshakeHandler(WebSocketHandler webSocketHandler) { + Assert.notNull(webSocketHandler, "webSocketHandler is required"); + this.webSocketHandler = webSocketHandler; + this.handlerClass = null; + } + + public AbstractHandshakeHandler(Class 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; - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 9e2f9f1962..684bc36253 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java similarity index 62% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java index abb66ff4cc..8eeb33026d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java @@ -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 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)); } } \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 9dfc509042..8e373c0261 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -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; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java index b25c1a47c3..4e1f8d633b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java @@ -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 diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/WebSocketRequestUpgradeStrategy.java similarity index 81% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/WebSocketRequestUpgradeStrategy.java index 745001b206..9a1b43dc12 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/WebSocketRequestUpgradeStrategy.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketSession.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java rename to spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketSession.java index 302a033392..d3384ed6b0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketSession.java @@ -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; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerEndpoint.java similarity index 92% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java rename to spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerEndpoint.java index da3d7a768a..017d0181ea 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerEndpoint.java @@ -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 sessionMap = new ConcurrentHashMap(); + private final Map sessions = new ConcurrentHashMap(); 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; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java similarity index 62% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java rename to spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java index 1e5962b911..b30d21c7ab 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java @@ -4,5 +4,5 @@ * Support for working with standard Java WebSocket Endpoint's. * */ -package org.springframework.websocket.endpoint; +package org.springframework.websocket.support; From 592da431a8ac6b26a92ffc158adc14918f889505 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 10 Apr 2013 09:14:38 -0400 Subject: [PATCH 12/51] Add Glassfish request upgrade strategy --- build.gradle | 4 +- .../server/AbstractHandshakeHandler.java | 94 ++++++----- .../endpoint/EndpointHandshakeHandler.java | 27 +++- ...va => EndpointRequestUpgradeStrategy.java} | 10 +- .../GlassfishRequestUpgradeStrategy.java | 152 ++++++++++++++++++ .../TomcatRequestUpgradeStrategy.java | 31 ++-- 6 files changed, 252 insertions(+), 66 deletions(-) rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{WebSocketRequestUpgradeStrategy.java => EndpointRequestUpgradeStrategy.java} (85%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{ => support}/TomcatRequestUpgradeStrategy.java (59%) diff --git a/build.gradle b/build.gradle index 1708b1587c..57b4a48509 100644 --- a/build.gradle +++ b/build.gradle @@ -525,10 +525,10 @@ project("spring-websocket") { exclude group: "org.apache.tomcat", module: "tomcat-servlet-api" } - optional("org.eclipse.jetty:jetty-websocket:8.1.10.v20130312") optional("org.glassfish.tyrus:tyrus-websocket-core:1.0-SNAPSHOT") + optional("org.glassfish.tyrus:tyrus-container-servlet:1.0-SNAPSHOT") - optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") + optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") // required for SockJS support currently } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java index d3807d0146..eaba42d78b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java @@ -37,6 +37,7 @@ 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.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.websocket.WebSocketHandler; @@ -56,7 +57,7 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean private final Class handlerClass; - private List protocols; + private List supportedProtocols; private AutowireCapableBeanFactory beanFactory; @@ -73,12 +74,12 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean this.handlerClass = handlerClass; } - public void setProtocols(String... protocols) { - this.protocols = Arrays.asList(protocols); + public void setSupportedProtocols(String... protocols) { + this.supportedProtocols = Arrays.asList(protocols); } - public String[] getProtocols() { - return this.protocols.toArray(new String[this.protocols.size()]); + public String[] getSupportedProtocols() { + return this.supportedProtocols.toArray(new String[this.supportedProtocols.size()]); } @Override @@ -109,16 +110,20 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean logger.debug("Only HTTP GET is allowed, current method is " + request.getMethod()); return false; } - if (!validateUpgradeHeader(request, response)) { + if (!"WebSocket".equalsIgnoreCase(request.getHeaders().getUpgrade())) { + handleInvalidUpgradeHeader(request, response); return false; } - if (!validateConnectHeader(request, response)) { + if (!request.getHeaders().getConnection().contains("Upgrade")) { + handleInvalidConnectHeader(request, response); return false; } - if (!validateWebSocketVersion(request, response)) { + if (!isWebSocketVersionSupported(request)) { + handleWebSocketVersionNotSupported(request, response); return false; } - if (!validateOrigin(request, response)) { + if (!isValidOrigin(request)) { + response.setStatusCode(HttpStatus.FORBIDDEN); return false; } String wsKey = request.getHeaders().getSecWebSocketKey(); @@ -127,8 +132,9 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean response.setStatusCode(HttpStatus.BAD_REQUEST); return false; } + String protocol = selectProtocol(request.getHeaders().getSecWebSocketProtocol()); - // TODO: request.getHeaders().getSecWebSocketExtensions()) + // TODO: select extensions response.setStatusCode(HttpStatus.SWITCHING_PROTOCOLS); response.getHeaders().setUpgrade("WebSocket"); @@ -139,7 +145,7 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean logger.debug("Successfully negotiated WebSocket handshake"); - // TODO: surely there is a better way to flush the headers + // TODO: surely there is a better way to flush headers response.getBody(); doHandshakeInternal(request, response, protocol); @@ -150,46 +156,46 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean 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); - response.getBody().write("Can \"Upgrade\" only to \"websocket\".".getBytes("UTF-8")); - logger.debug("Invalid Upgrade header " + request.getHeaders().getUpgrade()); - return false; - } - return true; + + protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + logger.debug("Invalid Upgrade header " + request.getHeaders().getUpgrade()); + response.setStatusCode(HttpStatus.BAD_REQUEST); + response.getBody().write("Can \"Upgrade\" only to \"websocket\".".getBytes("UTF-8")); } - protected boolean validateConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { - if (!request.getHeaders().getConnection().contains("Upgrade")) { - response.setStatusCode(HttpStatus.BAD_REQUEST); - response.getBody().write("\"Connection\" must be \"upgrade\".".getBytes("UTF-8")); - logger.debug("Invalid Connection header " + request.getHeaders().getConnection()); - return false; - } - return true; + protected void handleInvalidConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + logger.debug("Invalid Connection header " + request.getHeaders().getConnection()); + response.setStatusCode(HttpStatus.BAD_REQUEST); + response.getBody().write("\"Connection\" must be \"upgrade\".".getBytes("UTF-8")); } - protected boolean validateWebSocketVersion(ServerHttpRequest request, ServerHttpResponse response) { - if (!"13".equals(request.getHeaders().getSecWebSocketVersion())) { - response.setStatusCode(HttpStatus.UPGRADE_REQUIRED); - response.getHeaders().set("Sec-WebSocket-Version", "13"); - logger.debug("WebSocket version not supported " + request.getHeaders().get("Sec-WebSocket-Version")); - return false; + protected boolean isWebSocketVersionSupported(ServerHttpRequest request) { + String requestedVersion = request.getHeaders().getSecWebSocketVersion(); + for (String supportedVersion : getSupportedVerions()) { + if (supportedVersion.equals(requestedVersion)) { + return true; + } } - return true; + return false; } - protected boolean validateOrigin(ServerHttpRequest request, ServerHttpResponse response) { + protected String[] getSupportedVerions() { + return new String[] { "13" }; + } + + protected void handleWebSocketVersionNotSupported(ServerHttpRequest request, ServerHttpResponse response) { + logger.debug("WebSocket version not supported " + request.getHeaders().get("Sec-WebSocket-Version")); + response.setStatusCode(HttpStatus.UPGRADE_REQUIRED); + response.getHeaders().setSecWebSocketVersion(StringUtils.arrayToCommaDelimitedString(getSupportedVerions())); + } + + protected boolean isValidOrigin(ServerHttpRequest request) { String origin = request.getHeaders().getOrigin(); if (origin != null) { - UriComponentsBuilder originUriBuilder = UriComponentsBuilder.fromHttpUrl(origin); - + // UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(origin); // TODO - // Check scheme, port, and host against list of configured origins (allow wild cards in the host?) - // Another strategy might be to match current request's scheme/port/host - - // response.setStatusCode(HttpStatus.FORBIDDEN); + // A simple strategy checks against the current request's scheme/port/host + // Or match scheme, port, and host against configured allowed origins (wild cards for hosts?) // return false; } return true; @@ -197,9 +203,9 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean protected String selectProtocol(List requestedProtocols) { if (requestedProtocols != null) { - for (String p : requestedProtocols) { - if (this.protocols.contains(p)) { - return p; + for (String protocol : requestedProtocols) { + if (this.supportedProtocols.contains(protocol)) { + return protocol; } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java index 8eeb33026d..9758aab2d4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java @@ -37,45 +37,56 @@ import org.springframework.websocket.support.WebSocketHandlerEndpoint; public class EndpointHandshakeHandler extends AbstractHandshakeHandler { private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( - "org.apache.tomcat.websocket.server.WsHandshakeRequest", EndpointHandshakeHandler.class.getClassLoader()); + "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); - private final WebSocketRequestUpgradeStrategy upgradeStrategy; + private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( + "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); + + private final EndpointRequestUpgradeStrategy upgradeStrategy; public EndpointHandshakeHandler(WebSocketHandler webSocketHandler) { super(webSocketHandler); - this.upgradeStrategy = createRequestUpgradeStrategy(); + this.upgradeStrategy = createUpgradeStrategy(); } public EndpointHandshakeHandler(Class handlerClass) { super(handlerClass); - this.upgradeStrategy = createRequestUpgradeStrategy(); + this.upgradeStrategy = createUpgradeStrategy(); } - private static WebSocketRequestUpgradeStrategy createRequestUpgradeStrategy() { + private static EndpointRequestUpgradeStrategy createUpgradeStrategy() { String className; if (tomcatWebSocketPresent) { - className = "org.springframework.websocket.server.endpoint.TomcatRequestUpgradeStrategy"; + className = "org.springframework.websocket.server.endpoint.support.TomcatRequestUpgradeStrategy"; + } + else if (glassfishWebSocketPresent) { + className = "org.springframework.websocket.server.endpoint.support.GlassfishRequestUpgradeStrategy"; } else { throw new IllegalStateException("No suitable EndpointRequestUpgradeStrategy"); } try { Class clazz = ClassUtils.forName(className, EndpointHandshakeHandler.class.getClassLoader()); - return (WebSocketRequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor()); + return (EndpointRequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor()); } catch (Throwable t) { throw new IllegalStateException("Failed to instantiate " + className, t); } } + @Override + protected String[] getSupportedVerions() { + return this.upgradeStrategy.getSupportedVersions(); + } + @Override public void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol) throws Exception { logger.debug("Upgrading HTTP request"); Endpoint endpoint = new WebSocketHandlerEndpoint(getWebSocketHandler()); - this.upgradeStrategy.upgrade(request, response, protocol, new EndpointRegistration("/dummy", endpoint)); + this.upgradeStrategy.upgrade(request, response, protocol, endpoint); } } \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/WebSocketRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java similarity index 85% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/WebSocketRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java index 9a1b43dc12..bcd8a70dd1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/WebSocketRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java @@ -16,6 +16,8 @@ package org.springframework.websocket.server.endpoint; +import javax.websocket.Endpoint; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -26,12 +28,14 @@ import org.springframework.http.server.ServerHttpResponse; * @author Rossen Stoyanchev * @since 4.0 */ -public interface WebSocketRequestUpgradeStrategy { +public interface EndpointRequestUpgradeStrategy { + + String[] getSupportedVersions(); /** * Invoked after the handshake checks have been performed and succeeded. */ - void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, - EndpointRegistration registration) throws Exception; + void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, Endpoint endpoint) + throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java new file mode 100644 index 0000000000..9690141b26 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java @@ -0,0 +1,152 @@ +/* + * 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.support; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import javax.websocket.Endpoint; + +import org.glassfish.tyrus.core.ComponentProviderService; +import org.glassfish.tyrus.core.EndpointWrapper; +import org.glassfish.tyrus.core.ErrorCollector; +import org.glassfish.tyrus.core.RequestContext; +import org.glassfish.tyrus.server.TyrusEndpoint; +import org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler; +import org.glassfish.tyrus.websockets.Connection; +import org.glassfish.tyrus.websockets.Version; +import org.glassfish.tyrus.websockets.WebSocketEngine; +import org.glassfish.tyrus.websockets.WebSocketEngine.WebSocketHolderListener; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.websocket.server.endpoint.EndpointRegistration; +import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class GlassfishRequestUpgradeStrategy implements EndpointRequestUpgradeStrategy { + + private final static Random random = new Random(); + + + @Override + public String[] getSupportedVersions() { + return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions()); + } + + @Override + public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, + Endpoint endpoint) throws Exception { + + Assert.isTrue(request instanceof ServletServerHttpRequest); + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + + Assert.isTrue(response instanceof ServletServerHttpResponse); + HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); + servletResponse = new AlreadyUpgradedResponseWrapper(servletResponse); + + TyrusEndpoint tyrusEndpoint = createTyrusEndpoint(servletRequest, endpoint); + WebSocketEngine.getEngine().register(tyrusEndpoint); + + try { + if (!performUpgrade(servletRequest, servletResponse, request.getHeaders(), tyrusEndpoint)) { + throw new IllegalStateException("Failed to upgrade HttpServletRequest"); + } + } + finally { + WebSocketEngine.getEngine().unregister(tyrusEndpoint); + } + } + + private boolean performUpgrade(HttpServletRequest request, HttpServletResponse response, + HttpHeaders headers, TyrusEndpoint tyrusEndpoint) throws Exception { + + final TyrusHttpUpgradeHandler upgradeHandler = request.upgrade(TyrusHttpUpgradeHandler.class); + + Connection connection = createConnection(upgradeHandler, response); + + RequestContext wsRequest = RequestContext.Builder.create() + .requestURI(URI.create(tyrusEndpoint.getPath())).requestPath(tyrusEndpoint.getPath()) + .connection(connection).secure(request.isSecure()).build(); + + for (String header : headers.keySet()) { + wsRequest.getHeaders().put(header, headers.get(header)); + } + + return WebSocketEngine.getEngine().upgrade(connection, wsRequest, new WebSocketHolderListener() { + @Override + public void onWebSocketHolder(WebSocketEngine.WebSocketHolder webSocketHolder) { + upgradeHandler.setWebSocketHolder(webSocketHolder); + } + }); + } + + private TyrusEndpoint createTyrusEndpoint(HttpServletRequest request, Endpoint endpoint) { + + // Use randomized path + String requestUri = request.getRequestURI(); + String randomValue = String.valueOf(random.nextLong()); + String endpointPath = requestUri.endsWith("/") ? requestUri + randomValue : requestUri + "/" + randomValue; + + EndpointRegistration endpointConfig = new EndpointRegistration(endpointPath, endpoint); + + return new TyrusEndpoint(new EndpointWrapper(endpoint, endpointConfig, + ComponentProviderService.create(), null, "/", new ErrorCollector(), + endpointConfig.getConfigurator())); + } + + private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) throws Exception { + String name = "org.glassfish.tyrus.servlet.ConnectionImpl"; + Class clazz = ClassUtils.forName(name, GlassfishRequestUpgradeStrategy.class.getClassLoader()); + Constructor constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class); + ReflectionUtils.makeAccessible(constructor); + return (Connection) constructor.newInstance(handler, response); + } + + + private static class AlreadyUpgradedResponseWrapper extends HttpServletResponseWrapper { + + public AlreadyUpgradedResponseWrapper(HttpServletResponse response) { + super(response); + } + + @Override + public void setStatus(int sc) { + Assert.isTrue(sc == HttpStatus.SWITCHING_PROTOCOLS.value(), "Unexpected status code " + sc); + } + @Override + public void addHeader(String name, String value) { + // ignore + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/TomcatRequestUpgradeStrategy.java similarity index 59% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/TomcatRequestUpgradeStrategy.java index 4e1f8d633b..3e4dc96b61 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/TomcatRequestUpgradeStrategy.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.websocket.server.endpoint.support; import java.lang.reflect.Method; import java.util.Collections; import javax.servlet.http.HttpServletRequest; +import javax.websocket.Endpoint; +import javax.websocket.server.ServerEndpointConfig; import org.apache.tomcat.websocket.server.WsHandshakeRequest; import org.apache.tomcat.websocket.server.WsHttpUpgradeHandler; @@ -29,6 +31,8 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; +import org.springframework.websocket.server.endpoint.EndpointRegistration; +import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; /** @@ -36,26 +40,35 @@ import org.springframework.util.ReflectionUtils; * @author Rossen Stoyanchev * @since 4.0 */ -public class TomcatRequestUpgradeStrategy implements WebSocketRequestUpgradeStrategy { +public class TomcatRequestUpgradeStrategy implements EndpointRequestUpgradeStrategy { + @Override + public String[] getSupportedVersions() { + return new String[] { "13" }; + } + @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, - EndpointRegistration registration) throws Exception { + Endpoint endpoint) throws Exception { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); - WsHttpUpgradeHandler wsHandler = servletRequest.upgrade(WsHttpUpgradeHandler.class); + WsHttpUpgradeHandler upgradeHandler = servletRequest.upgrade(WsHttpUpgradeHandler.class); - WsHandshakeRequest wsRequest = new WsHandshakeRequest(servletRequest); + WsHandshakeRequest webSocketRequest = new WsHandshakeRequest(servletRequest); Method method = ReflectionUtils.findMethod(WsHandshakeRequest.class, "finished"); ReflectionUtils.makeAccessible(method); - method.invoke(wsRequest); + method.invoke(webSocketRequest); - wsHandler.preInit(registration.getEndpoint(), registration, - WsServerContainer.getServerContainer(), wsRequest, protocol, - Collections. emptyMap(), servletRequest.isSecure()); + // TODO: use ServletContext attribute when Tomcat is updated + WsServerContainer serverContainer = WsServerContainer.getServerContainer(); + + ServerEndpointConfig endpointConfig = new EndpointRegistration("/shouldntmatter", endpoint); + + upgradeHandler.preInit(endpoint, endpointConfig, serverContainer, webSocketRequest, + protocol, Collections. emptyMap(), servletRequest.isSecure()); } } From db6f8f2d4b7c0c6b1cc1f24bf1859886c9f9f0b4 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 10 Apr 2013 17:39:27 -0400 Subject: [PATCH 13/51] Add package-info, javadoc, and update package names --- .../springframework/sockjs/package-info.java | 7 +++ .../sockjs/server/package-info.java | 7 +++ .../support/SockJsServiceHandlerMapping.java | 47 ++++++++++++++++++- .../sockjs/server/support/package-info.java | 9 ++++ .../EndpointWebSocketTransportHandler.java | 2 +- .../sockjs/server/transport/package-info.java | 11 +++++ .../websocket/client/package-info.java | 7 +++ .../StandardWebSocketSession.java | 2 +- .../WebSocketHandlerEndpoint.java | 2 +- .../websocket/endpoint/package-info.java | 8 ++++ .../websocket/package-info.java | 7 +++ .../server/AbstractHandshakeHandler.java | 26 +++++----- .../websocket/server/HandshakeHandler.java | 2 +- .../server/endpoint/EndpointExporter.java | 2 +- .../server/endpoint/EndpointRegistration.java | 2 +- .../EndpointRequestUpgradeStrategy.java | 3 +- .../endpoint/ServletEndpointExporter.java | 4 +- .../EndpointHandshakeHandler.java | 34 ++++++++++++-- .../GlassfishRequestUpgradeStrategy.java | 4 +- .../TomcatRequestUpgradeStrategy.java | 3 +- .../endpoint/handshake/package-info.java | 8 ++++ .../server/endpoint/package-info.java | 11 +++-- .../websocket/server/package-info.java | 7 +++ .../support/HandshakeHttpRequestHandler.java} | 34 +++++++------- .../server/support/package-info.java | 7 +++ .../websocket/support/package-info.java | 8 ---- 26 files changed, 204 insertions(+), 60 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java rename spring-websocket/src/main/java/org/springframework/websocket/{support => endpoint}/StandardWebSocketSession.java (97%) rename spring-websocket/src/main/java/org/springframework/websocket/{support => endpoint}/WebSocketHandlerEndpoint.java (98%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/package-info.java rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{ => handshake}/EndpointHandshakeHandler.java (72%) rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{support => handshake}/GlassfishRequestUpgradeStrategy.java (97%) rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{support => handshake}/TomcatRequestUpgradeStrategy.java (94%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java rename spring-websocket/src/main/java/org/springframework/{sockjs/server/support/SockJsServiceHttpRequestHandler.java => websocket/server/support/HandshakeHttpRequestHandler.java} (62%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java new file mode 100644 index 0000000000..5b2f27cafe --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java @@ -0,0 +1,7 @@ + +/** + * Common abstractions and Spring configuration support for the SockJS protocol. + * + */ +package org.springframework.sockjs; + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java new file mode 100644 index 0000000000..8748c1b582 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java @@ -0,0 +1,7 @@ + +/** + * Server-side SockJS abstractions and base classes. + * + */ +package org.springframework.sockjs.server; + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java index c40565ecac..656324fe5b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java @@ -15,18 +15,28 @@ */ package org.springframework.sockjs.server.support; +import java.io.IOException; import java.util.Arrays; import java.util.List; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.http.server.AsyncServletServerHttpRequest; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.sockjs.server.AbstractSockJsService; +import org.springframework.web.HttpRequestHandler; import org.springframework.web.servlet.handler.AbstractHandlerMapping; +import org.springframework.web.util.NestedServletException; /** - * A Spring MVC HandlerMapping matching requests to SockJS services by prefix. + * A Spring MVC HandlerMapping for matching requests to a SockJS services based on the + * {@link AbstractSockJsService#getPrefix() prefix} property of each service. * * @author Rossen Stoyanchev * @since 4.0 @@ -56,7 +66,7 @@ public class SockJsServiceHandlerMapping extends AbstractHandlerMapping { logger.debug("Matched to " + service); } String sockJsPath = lookupPath.substring(service.getPrefix().length()); - return new SockJsServiceHttpRequestHandler(sockJsPath, service); + return new SockJsServiceHttpRequestHandler(service, sockJsPath); } } @@ -67,4 +77,37 @@ public class SockJsServiceHandlerMapping extends AbstractHandlerMapping { return null; } + + /** + * {@link HttpRequestHandler} wrapping the invocation of the selected SockJS service. + */ + private static class SockJsServiceHttpRequestHandler implements HttpRequestHandler { + + private final String sockJsPath; + + private final AbstractSockJsService sockJsService; + + + public SockJsServiceHttpRequestHandler(AbstractSockJsService sockJsService, String sockJsPath) { + this.sockJsService = sockJsService; + this.sockJsPath = sockJsPath; + } + + @Override + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response); + ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + + try { + this.sockJsService.handleRequest(httpRequest, httpResponse, this.sockJsPath); + } + catch (Exception ex) { + // TODO + throw new NestedServletException("SockJS service failure", ex); + } + } + } + } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java new file mode 100644 index 0000000000..b2030dead7 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java @@ -0,0 +1,9 @@ + +/** + * Server-side SockJS classes including a + * {@link org.springframework.sockjs.server.support.DefaultSockJsService} implementation + * as well as a Spring MVC HandlerMapping mapping SockJS services to incoming requests. + * + */ +package org.springframework.sockjs.server.support; + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java index b6762111f3..8d41943c9a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java @@ -19,7 +19,7 @@ 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; +import org.springframework.websocket.server.endpoint.handshake.EndpointHandshakeHandler; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java new file mode 100644 index 0000000000..b31cef1145 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java @@ -0,0 +1,11 @@ + +/** + * Server-side support for SockJS transports including + * {@link org.springframework.sockjs.server.TransportHandler} implementations + * for processing incoming requests and their + * {@link org.springframework.sockjs.SockJsSession} counterparts for + * sending messages over the various transports. + * + */ +package org.springframework.sockjs.server.transport; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java new file mode 100644 index 0000000000..28e7e9eb86 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java @@ -0,0 +1,7 @@ + +/** + * Client-side support for WebSocket applications. + * + */ +package org.springframework.websocket.client; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketSession.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index d3384ed6b0..302a033392 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.websocket.endpoint; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerEndpoint.java rename to spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 017d0181ea..b70076b3d8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.websocket.endpoint; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java new file mode 100644 index 0000000000..99723de31d --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java @@ -0,0 +1,8 @@ + +/** + * Classes for use with the standard Java WebSocket endpoints from both client and + * server code. + * + */ +package org.springframework.websocket.endpoint; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/package-info.java new file mode 100644 index 0000000000..8bf3b32aa5 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/package-info.java @@ -0,0 +1,7 @@ + +/** + * Common abstractions and Spring configuration support for WebSocket applications. + * + */ +package org.springframework.websocket; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java index eaba42d78b..7ff08b8097 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java @@ -38,8 +38,6 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.websocket.WebSocketHandler; /** @@ -53,25 +51,25 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean protected Log logger = LogFactory.getLog(getClass()); - private final WebSocketHandler webSocketHandler; + private final Object webSocketHandler; - private final Class handlerClass; + private final Class webSocketHandlerClass; private List supportedProtocols; private AutowireCapableBeanFactory beanFactory; - public AbstractHandshakeHandler(WebSocketHandler webSocketHandler) { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.webSocketHandler = webSocketHandler; - this.handlerClass = null; + public AbstractHandshakeHandler(Object handler) { + Assert.notNull(handler, "webSocketHandler is required"); + this.webSocketHandler = handler; + this.webSocketHandlerClass = null; } - public AbstractHandshakeHandler(Class handlerClass) { + public AbstractHandshakeHandler(Class handlerClass) { Assert.notNull((handlerClass), "handlerClass is required"); this.webSocketHandler = null; - this.handlerClass = handlerClass; + this.webSocketHandlerClass = handlerClass; } public void setSupportedProtocols(String... protocols) { @@ -89,10 +87,10 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean } } - 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); + protected Object getWebSocketHandler() { + if (this.webSocketHandlerClass != null) { + Assert.notNull(this.beanFactory, "BeanFactory is required for WebSocket handler instances per request."); + return this.beanFactory.createBean(this.webSocketHandlerClass); } else { return this.webSocketHandler; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 684bc36253..a8da4981ac 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -21,13 +21,13 @@ import org.springframework.http.server.ServerHttpResponse; /** + * Abstraction for integrating a WebSocket implementation some HTTP processing pipeline. * * @author Rossen Stoyanchev * @since 4.0 */ public interface HandshakeHandler { - boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java index 0596a27a81..76194c4ecd 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java @@ -35,7 +35,7 @@ import org.springframework.util.ObjectUtils; /** * BeanPostProcessor that detects beans of type - * {@link javax.websocket.server.ServerEndpointConfig} and registers the corresponding + * {@link javax.websocket.server.ServerEndpointConfig} and registers the provided * {@link javax.websocket.Endpoint} with a standard Java WebSocket runtime. * *

If the runtime is a Servlet container, use {@link ServletEndpointExporter}. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 8e373c0261..9dfc509042 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -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.support.WebSocketHandlerEndpoint; +import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java index bcd8a70dd1..2047243429 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java @@ -23,7 +23,8 @@ import org.springframework.http.server.ServerHttpResponse; /** - * A strategy for performing the runtime-specific parts of a WebSocket upgrade request. + * A strategy for performing the container-specific steps for upgrading an HTTP request + * as part of a WebSocket handshake. * * @author Rossen Stoyanchev * @since 4.0 diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java index d62b2f44c6..b5b216fc8b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java @@ -32,10 +32,8 @@ import org.springframework.web.context.ServletContextAware; */ public class ServletEndpointExporter extends EndpointExporter implements ServletContextAware { - /** - * - */ private static final String SERVER_CONTAINER_ATTR_NAME = "javax.websocket.server.ServerContainer"; + private ServletContext servletContext; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java similarity index 72% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java index 9758aab2d4..a63033361f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.websocket.server.endpoint.handshake; import javax.websocket.Endpoint; @@ -23,13 +23,18 @@ 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.AbstractHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.support.WebSocketHandlerEndpoint; +import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; /** - * A {@link HandshakeHandler} for use with standard Java WebSocket. + * A {@link HandshakeHandler} for use with standard Java WebSocket runtimes. A + * container-specific {@link EndpointRequestUpgradeStrategy} is required since standard + * Java WebSocket currently does not provide any means of integrating a WebSocket + * handshake into an HTTP request processing pipeline. Currently available are + * implementations for Tomcat and Glassfish. * * @author Rossen Stoyanchev * @since 4.0 @@ -45,12 +50,17 @@ public class EndpointHandshakeHandler extends AbstractHandshakeHandler { private final EndpointRequestUpgradeStrategy upgradeStrategy; + public EndpointHandshakeHandler(Endpoint endpoint) { + super(endpoint); + this.upgradeStrategy = createUpgradeStrategy(); + } + public EndpointHandshakeHandler(WebSocketHandler webSocketHandler) { super(webSocketHandler); this.upgradeStrategy = createUpgradeStrategy(); } - public EndpointHandshakeHandler(Class handlerClass) { + public EndpointHandshakeHandler(Class handlerClass) { super(handlerClass); this.upgradeStrategy = createUpgradeStrategy(); } @@ -85,7 +95,21 @@ public class EndpointHandshakeHandler extends AbstractHandshakeHandler { throws Exception { logger.debug("Upgrading HTTP request"); - Endpoint endpoint = new WebSocketHandlerEndpoint(getWebSocketHandler()); + + Object webSocketHandler = getWebSocketHandler(); + + Endpoint endpoint; + if (webSocketHandler instanceof Endpoint) { + endpoint = (Endpoint) webSocketHandler; + } + else if (webSocketHandler instanceof WebSocketHandler) { + endpoint = new WebSocketHandlerEndpoint((WebSocketHandler) webSocketHandler); + } + else { + String className = webSocketHandler.getClass().getName(); + throw new IllegalArgumentException("Unexpected WebSocket handler type: " + className); + } + this.upgradeStrategy.upgrade(request, response, protocol, endpoint); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java index 9690141b26..52750dc4d1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint.support; +package org.springframework.websocket.server.endpoint.handshake; import java.lang.reflect.Constructor; import java.net.URI; @@ -49,6 +49,8 @@ import org.springframework.websocket.server.endpoint.EndpointRegistration; import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; /** + * Glassfish support for upgrading an {@link HttpServletRequest} during a WebSocket + * handshake. * * @author Rossen Stoyanchev * @since 4.0 diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/TomcatRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java index 3e4dc96b61..a44468bdab 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint.support; +package org.springframework.websocket.server.endpoint.handshake; import java.lang.reflect.Method; import java.util.Collections; @@ -36,6 +36,7 @@ import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrat /** + * Tomcat support for upgrading an {@link HttpServletRequest} during a WebSocket handshake. * * @author Rossen Stoyanchev * @since 4.0 diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java new file mode 100644 index 0000000000..8adf2395cb --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java @@ -0,0 +1,8 @@ + +/** + * WebSocket handshake support for use with standard Java WebSocket runtimes including + * container-specific strategies for upgrading the HttpServletRequest. + * + */ +package org.springframework.websocket.server.endpoint.handshake; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java index 3ede1f92bc..ce327f239e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java @@ -1,8 +1,13 @@ /** - * - * Server-specific support for configuring and adapting standard Java WebSocket endpoint's. - * + * Server classes for use with standard Java WebSocket endpoints including + * {@link org.springframework.websocket.server.endpoint.EndpointRegistration} and + * {@link org.springframework.websocket.server.endpoint.EndpointExporter} for + * registering type-based endpoints, + * {@link org.springframework.websocket.server.endpoint.SpringConfigurator} for + * instantiating annotated endpoints through Spring, and + * {@link org.springframework.websocket.server.endpoint.handshake.EndpointHandshakeHandler} + * for integrating endpoints into HTTP request processing. */ package org.springframework.websocket.server.endpoint; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java new file mode 100644 index 0000000000..8a44c0bc88 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java @@ -0,0 +1,7 @@ + +/** + * Server-side abstractions for WebSocket applications. + * + */ +package org.springframework.websocket.server; + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/HandshakeHttpRequestHandler.java similarity index 62% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/support/HandshakeHttpRequestHandler.java index 57d3baf333..26f5d59ae1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/HandshakeHttpRequestHandler.java @@ -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,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sockjs.server.support; + +package org.springframework.websocket.server.support; import java.io.IOException; @@ -21,45 +22,46 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.http.server.AsyncServletServerHttpRequest; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.sockjs.server.AbstractSockJsService; +import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; +import org.springframework.websocket.server.HandshakeHandler; + /** - * A Spring MVC {@link HttpRequestHandler} wrapping the invocation of a SockJS service. + * A Spring MVC {@link HttpRequestHandler} wrapping the invocation of a WebSocket + * {@link HandshakeHandler}; * * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsServiceHttpRequestHandler implements HttpRequestHandler { +public class HandshakeHttpRequestHandler implements HttpRequestHandler { - private final String sockJsPath; - - private final AbstractSockJsService sockJsService; + private final HandshakeHandler handshakeHandler; - public SockJsServiceHttpRequestHandler(String sockJsPath, AbstractSockJsService sockJsService) { - this.sockJsService = sockJsService; - this.sockJsPath = sockJsPath; + public HandshakeHttpRequestHandler(HandshakeHandler handshakeHandler) { + Assert.notNull(handshakeHandler, "handshakeHandler is required"); + this.handshakeHandler = handshakeHandler; } @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response); + ServerHttpRequest httpRequest = new ServletServerHttpRequest(request); ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - this.sockJsService.handleRequest(httpRequest, httpResponse, this.sockJsPath); + this.handshakeHandler.doHandshake(httpRequest, httpResponse); } - catch (Exception ex) { + catch (Exception e) { // TODO - throw new NestedServletException("SockJS service failure", ex); + throw new NestedServletException("HandshakeHandler failure", e); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java new file mode 100644 index 0000000000..c76d943d9f --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java @@ -0,0 +1,7 @@ + +/** + * Server-side support classes for WebSocket applications. + * + */ +package org.springframework.websocket.server.support; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java deleted file mode 100644 index b30d21c7ab..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ - -/** - * - * Support for working with standard Java WebSocket Endpoint's. - * - */ -package org.springframework.websocket.support; - From 3a2c15b0fd5d370f80964b8f4d62b353823e06cf Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 11 Apr 2013 17:15:18 -0400 Subject: [PATCH 14/51] Add flush method to ServerHttpResponse This is useful to make sure response headers are written to the underlying response. It is also useful in conjunction with long running, async requests and HTTP streaming, to ensure the Servlet response buffer is sent to the client without additional delay and also causes an IOException to be raised if the client has gone away. --- .../server/AsyncServletServerHttpRequest.java | 22 ++++- .../http/server/ServerHttpResponse.java | 6 ++ .../server/ServletServerHttpResponse.java | 7 ++ .../springframework/sockjs/SockJsSession.java | 4 +- .../sockjs/server/AbstractServerSession.java | 39 ++++++--- .../sockjs/server/AbstractSockJsService.java | 81 ++++++++++--------- .../server/NestedSockJsRuntimeException.java | 39 +++++++++ .../sockjs/server/SockJsFrame.java | 7 +- .../server/support/DefaultSockJsService.java | 7 +- .../AbstractHttpSendingTransportHandler.java | 4 +- .../transport/AbstractHttpServerSession.java | 33 ++++---- .../EventSourceTransportHandler.java | 2 +- .../transport/HtmlFileTransportHandler.java | 2 +- .../transport/PollingHttpServerSession.java | 6 +- .../transport/SockJsWebSocketHandler.java | 4 +- .../transport/StreamingHttpServerSession.java | 10 +-- .../WebSocketSockJsHandlerAdapter.java | 4 +- .../XhrStreamingTransportHandler.java | 2 +- .../websocket/WebSocketSession.java | 4 +- .../endpoint/StandardWebSocketSession.java | 4 +- .../server/endpoint/EndpointRegistration.java | 9 ++- .../handshake/EndpointHandshakeHandler.java | 70 +++++++++------- .../GlassfishRequestUpgradeStrategy.java | 8 +- .../RequestUpgradeStrategy.java} | 4 +- .../TomcatRequestUpgradeStrategy.java | 18 +++-- 25 files changed, 258 insertions(+), 138 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java rename spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/{EndpointRequestUpgradeStrategy.java => handshake/RequestUpgradeStrategy.java} (91%) diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java index 37d424e017..1e53fc7abf 100644 --- a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java @@ -116,21 +116,35 @@ public class AsyncServletServerHttpRequest extends ServletServerHttpRequest // Implementation of AsyncListener methods // --------------------------------------------------------------------- + @Override public void onStartAsync(AsyncEvent event) throws IOException { } + @Override public void onError(AsyncEvent event) throws IOException { } + @Override public void onTimeout(AsyncEvent event) throws IOException { - for (Runnable handler : this.timeoutHandlers) { - handler.run(); + try { + for (Runnable handler : this.timeoutHandlers) { + handler.run(); + } + } + catch (Throwable t) { + // ignore } } + @Override public void onComplete(AsyncEvent event) throws IOException { - for (Runnable handler : this.completionHandlers) { - handler.run(); + try { + for (Runnable handler : this.completionHandlers) { + handler.run(); + } + } + catch (Throwable t) { + // ignore } this.asyncContext = null; this.asyncCompleted.set(true); diff --git a/spring-web/src/main/java/org/springframework/http/server/ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServerHttpResponse.java index 8ce306271f..02541fab92 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServerHttpResponse.java @@ -17,6 +17,7 @@ package org.springframework.http.server; import java.io.Closeable; +import java.io.IOException; import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpStatus; @@ -35,6 +36,11 @@ public interface ServerHttpResponse extends HttpOutputMessage, Closeable { */ void setStatusCode(HttpStatus status); + /** + * TODO + */ + void flush() throws IOException; + /** * Close this response, freeing any resources created. */ diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index b090132457..3a2ba0445d 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -80,6 +80,13 @@ public class ServletServerHttpResponse implements ServerHttpResponse { return this.servletResponse.getOutputStream(); } + @Override + public void flush() throws IOException { + writeCookies(); + writeHeaders(); + this.servletResponse.flushBuffer(); + } + public void close() { writeCookies(); writeHeaders(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java index 77a61128e3..0dab8e17f5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java @@ -16,6 +16,8 @@ package org.springframework.sockjs; +import java.io.IOException; + /** @@ -25,7 +27,7 @@ package org.springframework.sockjs; */ public interface SockJsSession { - void sendMessage(String text) throws Exception; + void sendMessage(String text) throws IOException; void close(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java index b4624914bb..feb7599b80 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java @@ -17,6 +17,8 @@ package org.springframework.sockjs.server; import java.io.EOFException; +import java.io.IOException; +import java.net.SocketException; import java.util.Date; import java.util.concurrent.ScheduledFuture; @@ -54,12 +56,12 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { return this.sockJsConfig; } - public final synchronized void sendMessage(String message) { + public final synchronized void sendMessage(String message) throws IOException { Assert.isTrue(!isClosed(), "Cannot send a message, session has been closed"); sendMessageInternal(message); } - protected abstract void sendMessageInternal(String message); + protected abstract void sendMessageInternal(String message) throws IOException; public final synchronized void close() { if (!isClosed()) { @@ -67,7 +69,12 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { if (isActive()) { // deliver messages "in flight" before sending close frame - writeFrame(SockJsFrame.closeFrameGoAway()); + try { + writeFrame(SockJsFrame.closeFrameGoAway()); + } + catch (Exception e) { + // ignore + } } super.close(); @@ -83,26 +90,33 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { * For internal use within a TransportHandler and the (TransportHandler-specific) * session sub-class. The frame is written only if the connection is active. */ - protected void writeFrame(SockJsFrame frame) { + protected void writeFrame(SockJsFrame frame) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Preparing to write " + frame); } try { writeFrameInternal(frame); } - catch (EOFException ex) { - logger.warn("Client went away. Terminating connection abruptly"); + catch (IOException ex) { + if (ex instanceof EOFException || ex instanceof SocketException) { + logger.warn("Client went away. Terminating connection"); + } + else { + logger.warn("Failed to send message. Terminating connection: " + ex.getMessage()); + } deactivate(); close(); + throw ex; } catch (Throwable t) { - logger.warn("Failed to send message. Terminating connection abruptly: " + t.getMessage()); + logger.warn("Failed to send message. Terminating connection: " + t.getMessage()); deactivate(); close(); + throw new NestedSockJsRuntimeException("Failed to write frame " + frame, t); } } - protected abstract void writeFrameInternal(SockJsFrame frame) throws Exception; + protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException; /** * Some {@link TransportHandler} types cannot detect if a client connection is closed @@ -111,7 +125,7 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { */ protected abstract void deactivate(); - public synchronized void sendHeartbeat() { + public synchronized void sendHeartbeat() throws IOException { if (isActive()) { writeFrame(SockJsFrame.heartbeatFrame()); scheduleHeartbeat(); @@ -127,7 +141,12 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { Date time = new Date(System.currentTimeMillis() + getSockJsConfig().getHeartbeatTime()); this.heartbeatTask = getSockJsConfig().getHeartbeatScheduler().schedule(new Runnable() { public void run() { - sendHeartbeat(); + try { + sendHeartbeat(); + } + catch (IOException e) { + // ignore + } } }, time); if (logger.isTraceEnabled()) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 412704b335..bff4ccace3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -214,50 +214,57 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { request.getHeaders(); } catch (IllegalArgumentException ex) { - // Ignore invalid Content-Type (TODO!!) + // Ignore invalid Content-Type (TODO) } - if (sockJsPath.equals("") || sockJsPath.equals("/")) { - response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); - response.getBody().write("Welcome to SockJS!\n".getBytes("UTF-8")); - return; - } - else if (sockJsPath.equals("/info")) { - this.infoHandler.handle(request, response); - return; - } - else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) { - this.iframeHandler.handle(request, response); - return; - } - else if (sockJsPath.equals("/websocket")) { - handleRawWebSocket(request, response); - return; - } + try { + if (sockJsPath.equals("") || sockJsPath.equals("/")) { + response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); + response.getBody().write("Welcome to SockJS!\n".getBytes("UTF-8")); + return; + } + else if (sockJsPath.equals("/info")) { + this.infoHandler.handle(request, response); + return; + } + else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) { + this.iframeHandler.handle(request, response); + return; + } + else if (sockJsPath.equals("/websocket")) { + handleRawWebSocket(request, response); + return; + } - String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); - if (pathSegments.length != 3) { - logger.debug("Expected /{server}/{session}/{transport} but got " + sockJsPath); - response.setStatusCode(HttpStatus.NOT_FOUND); - return; + String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); + if (pathSegments.length != 3) { + logger.debug("Expected /{server}/{session}/{transport} but got " + sockJsPath); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + String serverId = pathSegments[0]; + String sessionId = pathSegments[1]; + String transport = pathSegments[2]; + + if (!validateRequest(serverId, sessionId, transport)) { + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport)); } - - String serverId = pathSegments[0]; - String sessionId = pathSegments[1]; - String transport = pathSegments[2]; - - if (!validateRequest(serverId, sessionId, transport)) { - response.setStatusCode(HttpStatus.NOT_FOUND); - return; + finally { + response.flush(); } - - handleRequestInternal(request, response, sessionId, TransportType.fromValue(transport)); - } protected abstract void handleRawWebSocket(ServerHttpRequest request, ServerHttpResponse response) throws Exception; + protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, + String sessionId, TransportType transportType) throws Exception; + protected boolean validateRequest(String serverId, String sessionId, String transport) { if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) { @@ -279,9 +286,6 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { return true; } - protected abstract void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType) throws Exception; - protected void addCorsHeaders(ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) { String origin = request.getHeaders().getFirst("origin"); @@ -316,7 +320,6 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { logger.debug("Sending Method Not Allowed (405)"); response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED); response.getHeaders().setAllow(new HashSet(httpMethods)); - response.getBody(); // ensure headers are flushed (TODO!) } @@ -350,8 +353,6 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { addCorsHeaders(request, response, HttpMethod.GET, HttpMethod.OPTIONS); addCacheHeaders(response); - - response.getBody(); // ensure headers are flushed (TODO!) } else { sendMethodNotAllowed(response, Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET)); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java new file mode 100644 index 0000000000..9f89c4a120 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java @@ -0,0 +1,39 @@ +/* + * 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.springframework.core.NestedRuntimeException; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +@SuppressWarnings("serial") +public class NestedSockJsRuntimeException extends NestedRuntimeException { + + + public NestedSockJsRuntimeException(String msg) { + super(msg); + } + + public NestedSockJsRuntimeException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java index 131354fbde..ded997e945 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java @@ -80,8 +80,11 @@ public class SockJsFrame { } public String toString() { - String quoted = this.content.replace("\n", "\\n").replace("\r", "\\r"); - return "SockJsFrame content='" + quoted + "'"; + String result = this.content; + if (result.length() > 80) { + result = result.substring(0, 80) + "...(truncated)"; + } + return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'"; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 69f325da2c..8a6ca6a564 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -175,7 +175,7 @@ public class DefaultSockJsService extends AbstractSockJsService } @Override - protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, String sessionId, TransportType transportType) throws Exception { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -192,7 +192,6 @@ public class DefaultSockJsService extends AbstractSockJsService response.setStatusCode(HttpStatus.NO_CONTENT); addCorsHeaders(request, response, supportedMethod, HttpMethod.OPTIONS); addCacheHeaders(response); - response.getBody(); // ensure headers are flushed (TODO!) } else { List supportedMethods = Arrays.asList(supportedMethod); @@ -214,7 +213,7 @@ public class DefaultSockJsService extends AbstractSockJsService if (isJsessionIdCookieNeeded()) { Cookie cookie = request.getCookies().getCookie("JSESSIONID"); String jsid = (cookie != null) ? cookie.getValue() : "dummy"; - // TODO: Jetty sets Expires header, so bypass Cookie object for now + // TODO: bypass use of Cookie object (causes Jetty to set Expires header) response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/"); // TODO } @@ -223,8 +222,6 @@ public class DefaultSockJsService extends AbstractSockJsService } transportHandler.handleRequest(request, response, session); - - response.close(); // ensure headers are flushed (TODO !!) } public SockJsSessionSupport getSockJsSession(String sessionId, TransportHandler transportHandler) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index dffa4389c0..8b43a6aff6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -78,7 +78,7 @@ public abstract class AbstractHttpSendingTransportHandler implements TransportHa } else if (httpServerSession.isActive()) { logger.debug("another " + getTransportType() + " connection still open: " + httpServerSession); - httpServerSession.writeFrame(response.getBody(), SockJsFrame.closeFrameAnotherConnectionOpen()); + httpServerSession.writeFrame(response, SockJsFrame.closeFrameAnotherConnectionOpen()); } else { logger.debug("starting " + getTransportType() + " async request"); @@ -91,7 +91,7 @@ public abstract class AbstractHttpSendingTransportHandler implements TransportHa logger.debug("Opening " + getTransportType() + " connection"); session.setFrameFormat(getFrameFormat(request)); - session.writeFrame(response.getBody(), SockJsFrame.openFrame()); + session.writeFrame(response, SockJsFrame.openFrame()); session.connectionInitialized(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java index 0c0d60ae66..5f657d9470 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java @@ -16,7 +16,6 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; -import java.io.OutputStream; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -44,7 +43,7 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { private AsyncServerHttpRequest asyncRequest; - private OutputStream outputStream; + private ServerHttpResponse response; public AbstractHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig) { @@ -60,7 +59,7 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { if (isClosed()) { logger.debug("connection already closed"); - writeFrame(response.getBody(), SockJsFrame.closeFrameGoAway()); + writeFrame(response, SockJsFrame.closeFrameGoAway()); return; } @@ -70,11 +69,11 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { this.asyncRequest.setTimeout(-1); this.asyncRequest.startAsync(); - this.outputStream = response.getBody(); + this.response = response; this.frameFormat = frameFormat; scheduleHeartbeat(); - tryFlush(); + tryFlushCache(); } public synchronized boolean isActive() { @@ -85,24 +84,28 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { return this.messageCache; } - protected final synchronized void sendMessageInternal(String message) { + protected ServerHttpResponse getResponse() { + return this.response; + } + + protected final synchronized void sendMessageInternal(String message) throws IOException { // assert close() was not called // threads: TH-Session-Endpoint or any other thread this.messageCache.add(message); - tryFlush(); + tryFlushCache(); } - private void tryFlush() { + private void tryFlushCache() throws IOException { if (isActive() && !getMessageCache().isEmpty()) { logger.trace("Flushing messages"); - flush(); + flushCache(); } } /** * Only called if the connection is currently active */ - protected abstract void flush(); + protected abstract void flushCache() throws IOException; protected void closeInternal() { resetRequest(); @@ -110,7 +113,7 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { protected synchronized void writeFrameInternal(SockJsFrame frame) throws IOException { if (isActive()) { - writeFrame(this.outputStream, frame); + writeFrame(this.response, frame); } } @@ -119,18 +122,18 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { * even when the connection is not active, as long as a valid OutputStream * is provided. */ - public void writeFrame(OutputStream outputStream, SockJsFrame frame) throws IOException { + public void writeFrame(ServerHttpResponse response, SockJsFrame frame) throws IOException { frame = this.frameFormat.format(frame); if (logger.isTraceEnabled()) { logger.trace("Writing " + frame); } - outputStream.write(frame.getContentBytes()); + response.getBody().write(frame.getContentBytes()); } @Override protected void deactivate() { - this.outputStream = null; this.asyncRequest = null; + this.response = null; updateLastActiveTime(); } @@ -138,8 +141,8 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { if (isActive()) { this.asyncRequest.completeAsync(); } - this.outputStream = null; this.asyncRequest = null; + this.response = null; updateLastActiveTime(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index 0725e6284c..235b6f5de1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -54,7 +54,7 @@ public class EventSourceTransportHandler extends AbstractStreamingTransportHandl protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { response.getBody().write('\r'); response.getBody().write('\n'); - response.getBody().flush(); + response.flush(); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index 188c59d9ab..35e48b238e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -102,7 +102,7 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler String html = String.format(PARTIAL_HTML_CONTENT, callback); response.getBody().write(html.getBytes("UTF-8")); - response.getBody().flush(); + response.flush(); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java index 3dd0e8d517..34302f610f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java @@ -15,6 +15,8 @@ */ package org.springframework.sockjs.server.transport; +import java.io.IOException; + import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -26,7 +28,7 @@ public class PollingHttpServerSession extends AbstractHttpServerSession { } @Override - protected void flush() { + protected void flushCache() throws IOException { cancelHeartbeat(); String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]); getMessageCache().clear(); @@ -34,7 +36,7 @@ public class PollingHttpServerSession extends AbstractHttpServerSession { } @Override - protected void writeFrame(SockJsFrame frame) { + protected void writeFrame(SockJsFrame frame) throws IOException { super.writeFrame(frame); resetRequest(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index db5da4c362..c19818e03d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -92,14 +92,14 @@ public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler { } @Override - public void sendMessageInternal(String message) { + public void sendMessageInternal(String message) throws IOException { cancelHeartbeat(); writeFrame(SockJsFrame.messageFrame(message)); scheduleHeartbeat(); } @Override - protected void writeFrameInternal(SockJsFrame frame) throws Exception { + protected void writeFrameInternal(SockJsFrame frame) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Write " + frame); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java index accff89ee6..94cf47cd9d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java @@ -16,8 +16,8 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; -import java.io.OutputStream; +import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -31,7 +31,7 @@ public class StreamingHttpServerSession extends AbstractHttpServerSession { super(sessionId, sockJsConfig); } - protected void flush() { + protected void flushCache() throws IOException { cancelHeartbeat(); @@ -64,9 +64,9 @@ public class StreamingHttpServerSession extends AbstractHttpServerSession { } @Override - public void writeFrame(OutputStream outputStream, SockJsFrame frame) throws IOException { - super.writeFrame(outputStream, frame); - outputStream.flush(); + public void writeFrame(ServerHttpResponse response, SockJsFrame frame) throws IOException { + super.writeFrame(response, frame); + response.flush(); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java index e617ab794f..82e94f6788 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java @@ -16,6 +16,8 @@ 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.SockJsConfiguration; @@ -60,7 +62,7 @@ public class WebSocketSockJsHandlerAdapter extends AbstractSockJsWebSocketHandle } @Override - public void sendMessage(String message) throws Exception { + public void sendMessage(String message) throws IOException { this.wsSession.sendText(message); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index 6a69747497..0f49b75de3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -56,7 +56,7 @@ public class XhrStreamingTransportHandler extends AbstractStreamingTransportHand response.getBody().write('h'); } response.getBody().write('\n'); - response.getBody().flush(); + response.flush(); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index d930dde9d0..2abe644a5a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -16,6 +16,8 @@ package org.springframework.websocket; +import java.io.IOException; + /** @@ -27,7 +29,7 @@ public interface WebSocketSession { boolean isOpen(); - void sendText(String text) throws Exception; + void sendText(String text) throws IOException; void close(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index 302a033392..dc4126b168 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -16,6 +16,8 @@ package org.springframework.websocket.endpoint; +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.websocket.WebSocketSession; @@ -44,7 +46,7 @@ public class StandardWebSocketSession implements WebSocketSession { } @Override - public void sendText(String text) throws Exception { + public void sendText(String text) throws IOException { logger.trace("Sending text message: " + text); // TODO: check closed this.session.getBasicRemote().sendText(text); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 9dfc509042..3c33a31017 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -29,6 +29,8 @@ import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; 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; @@ -54,6 +56,8 @@ import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; */ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAware { + private static Log logger = LogFactory.getLog(EndpointRegistration.class); + private final String path; private final Class endpointClass; @@ -130,8 +134,9 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw if (this.endpointClass != null) { WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); if (wac == null) { - throw new IllegalStateException("Failed to find WebApplicationContext. " - + "Was org.springframework.web.context.ContextLoader used to load the WebApplicationContext?"); + String message = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?"; + logger.error(message); + throw new IllegalStateException(); } return wac.getAutowireCapableBeanFactory().createBean(this.endpointClass); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java index a63033361f..089a9f8efd 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java @@ -26,12 +26,11 @@ import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.server.AbstractHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; /** * A {@link HandshakeHandler} for use with standard Java WebSocket runtimes. A - * container-specific {@link EndpointRequestUpgradeStrategy} is required since standard + * container-specific {@link RequestUpgradeStrategy} is required since standard * Java WebSocket currently does not provide any means of integrating a WebSocket * handshake into an HTTP request processing pipeline. Currently available are * implementations for Tomcat and Glassfish. @@ -41,48 +40,26 @@ import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrat */ public class EndpointHandshakeHandler extends AbstractHandshakeHandler { - private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( - "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); - - private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( - "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); - - private final EndpointRequestUpgradeStrategy upgradeStrategy; + private final RequestUpgradeStrategy upgradeStrategy; public EndpointHandshakeHandler(Endpoint endpoint) { super(endpoint); - this.upgradeStrategy = createUpgradeStrategy(); + this.upgradeStrategy = createRequestUpgradeStrategy(); } public EndpointHandshakeHandler(WebSocketHandler webSocketHandler) { super(webSocketHandler); - this.upgradeStrategy = createUpgradeStrategy(); + this.upgradeStrategy = createRequestUpgradeStrategy(); } public EndpointHandshakeHandler(Class handlerClass) { super(handlerClass); - this.upgradeStrategy = createUpgradeStrategy(); + this.upgradeStrategy = createRequestUpgradeStrategy(); } - private static EndpointRequestUpgradeStrategy createUpgradeStrategy() { - String className; - if (tomcatWebSocketPresent) { - className = "org.springframework.websocket.server.endpoint.support.TomcatRequestUpgradeStrategy"; - } - else if (glassfishWebSocketPresent) { - className = "org.springframework.websocket.server.endpoint.support.GlassfishRequestUpgradeStrategy"; - } - else { - throw new IllegalStateException("No suitable EndpointRequestUpgradeStrategy"); - } - try { - Class clazz = ClassUtils.forName(className, EndpointHandshakeHandler.class.getClassLoader()); - return (EndpointRequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor()); - } - catch (Throwable t) { - throw new IllegalStateException("Failed to instantiate " + className, t); - } + protected RequestUpgradeStrategy createRequestUpgradeStrategy() { + return new RequestUpgradeStrategyFactory().create(); } @Override @@ -113,4 +90,37 @@ public class EndpointHandshakeHandler extends AbstractHandshakeHandler { this.upgradeStrategy.upgrade(request, response, protocol, endpoint); } + + private static class RequestUpgradeStrategyFactory { + + private static final String packageName = EndpointHandshakeHandler.class.getPackage().getName(); + + private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( + "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); + + private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( + "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); + + + private RequestUpgradeStrategy create() { + String className; + if (tomcatWebSocketPresent) { + className = packageName + ".TomcatRequestUpgradeStrategy"; + } + else if (glassfishWebSocketPresent) { + className = packageName + ".GlassfishRequestUpgradeStrategy"; + } + else { + throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName()); + } + try { + Class clazz = ClassUtils.forName(className, EndpointHandshakeHandler.class.getClassLoader()); + return (RequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor()); + } + catch (Throwable t) { + throw new IllegalStateException("Failed to instantiate " + className, t); + } + } + } + } \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java index 52750dc4d1..6a23883fb5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java @@ -46,7 +46,6 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.websocket.server.endpoint.EndpointRegistration; -import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; /** * Glassfish support for upgrading an {@link HttpServletRequest} during a WebSocket @@ -55,7 +54,7 @@ import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrat * @author Rossen Stoyanchev * @since 4.0 */ -public class GlassfishRequestUpgradeStrategy implements EndpointRequestUpgradeStrategy { +public class GlassfishRequestUpgradeStrategy implements RequestUpgradeStrategy { private final static Random random = new Random(); @@ -77,7 +76,8 @@ public class GlassfishRequestUpgradeStrategy implements EndpointRequestUpgradeSt servletResponse = new AlreadyUpgradedResponseWrapper(servletResponse); TyrusEndpoint tyrusEndpoint = createTyrusEndpoint(servletRequest, endpoint); - WebSocketEngine.getEngine().register(tyrusEndpoint); + WebSocketEngine engine = WebSocketEngine.getEngine(); + engine.register(tyrusEndpoint); try { if (!performUpgrade(servletRequest, servletResponse, request.getHeaders(), tyrusEndpoint)) { @@ -85,7 +85,7 @@ public class GlassfishRequestUpgradeStrategy implements EndpointRequestUpgradeSt } } finally { - WebSocketEngine.getEngine().unregister(tyrusEndpoint); + engine.unregister(tyrusEndpoint); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/RequestUpgradeStrategy.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/RequestUpgradeStrategy.java index 2047243429..2dd09023b7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/RequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.websocket.server.endpoint.handshake; import javax.websocket.Endpoint; @@ -29,7 +29,7 @@ import org.springframework.http.server.ServerHttpResponse; * @author Rossen Stoyanchev * @since 4.0 */ -public interface EndpointRequestUpgradeStrategy { +public interface RequestUpgradeStrategy { String[] getSupportedVersions(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java index a44468bdab..b07cd2e0df 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java @@ -16,6 +16,7 @@ package org.springframework.websocket.server.endpoint.handshake; +import java.io.IOException; import java.lang.reflect.Method; import java.util.Collections; @@ -29,10 +30,10 @@ import org.apache.tomcat.websocket.server.WsServerContainer; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.sockjs.server.NestedSockJsRuntimeException; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.websocket.server.endpoint.EndpointRegistration; -import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrategy; /** @@ -41,7 +42,7 @@ import org.springframework.websocket.server.endpoint.EndpointRequestUpgradeStrat * @author Rossen Stoyanchev * @since 4.0 */ -public class TomcatRequestUpgradeStrategy implements EndpointRequestUpgradeStrategy { +public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override @@ -51,7 +52,7 @@ public class TomcatRequestUpgradeStrategy implements EndpointRequestUpgradeStrat @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, - Endpoint endpoint) throws Exception { + Endpoint endpoint) throws IOException { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -59,9 +60,14 @@ public class TomcatRequestUpgradeStrategy implements EndpointRequestUpgradeStrat WsHttpUpgradeHandler upgradeHandler = servletRequest.upgrade(WsHttpUpgradeHandler.class); WsHandshakeRequest webSocketRequest = new WsHandshakeRequest(servletRequest); - Method method = ReflectionUtils.findMethod(WsHandshakeRequest.class, "finished"); - ReflectionUtils.makeAccessible(method); - method.invoke(webSocketRequest); + try { + Method method = ReflectionUtils.findMethod(WsHandshakeRequest.class, "finished"); + ReflectionUtils.makeAccessible(method); + method.invoke(webSocketRequest); + } + catch (Exception ex) { + throw new NestedSockJsRuntimeException("Failed to upgrade HttpServletRequest", ex); + } // TODO: use ServletContext attribute when Tomcat is updated WsServerContainer serverContainer = WsServerContainer.getServerContainer(); From 71e82069cee16ecad1da49800c3673b85b70756e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 11 Apr 2013 21:50:57 -0400 Subject: [PATCH 15/51] Add SockJsSessionFactory --- .../sockjs/SockJsSessionFactory.java | 29 ++++++++++ .../sockjs/server/AbstractSockJsService.java | 14 ++--- .../sockjs/server/TransportHandler.java | 6 --- .../sockjs/server/TransportType.java | 44 +++++++++------ .../server/support/DefaultSockJsService.java | 54 ++++++++++--------- ...AbstractHttpReceivingTransportHandler.java | 29 +++++----- .../AbstractHttpSendingTransportHandler.java | 14 ++--- .../AbstractWebSocketTransportHandler.java | 15 ------ .../transport/JsonpTransportHandler.java | 2 +- 9 files changed, 108 insertions(+), 99 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java new file mode 100644 index 0000000000..f78c2ccf4b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -0,0 +1,29 @@ +/* + * 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; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface SockJsSessionFactory{ + + S createSession(String sessionId); + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index bff4ccace3..2a81ca6f8c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -58,7 +58,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { private int streamBytesLimit = 128 * 1024; - private boolean jsessionIdCookieNeeded = true; + private boolean jsessionIdCookieRequired = true; private long heartbeatTime = 25 * 1000; @@ -138,17 +138,17 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { * Set this option to indicate if a JSESSIONID cookie should be created. The * default value is "true". */ - public AbstractSockJsService setJsessionIdCookieNeeded(boolean jsessionIdCookieNeeded) { - this.jsessionIdCookieNeeded = jsessionIdCookieNeeded; + public AbstractSockJsService setJsessionIdCookieRequired(boolean jsessionIdCookieRequired) { + this.jsessionIdCookieRequired = jsessionIdCookieRequired; return this; } /** * Whether setting JSESSIONID cookie is necessary. - * @see #setJsessionIdCookieNeeded(boolean) + * @see #setJsessionIdCookieRequired(boolean) */ - public boolean isJsessionIdCookieNeeded() { - return this.jsessionIdCookieNeeded; + public boolean isJsessionIdCookieRequired() { + return this.jsessionIdCookieRequired; } public AbstractSockJsService setHeartbeatTime(long heartbeatTime) { @@ -344,7 +344,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { addCorsHeaders(request, response); addNoCacheHeaders(response); - String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieNeeded(), isWebSocketsEnabled()); + String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieRequired(), isWebSocketsEnabled()); response.getBody().write(content.getBytes()); } else if (HttpMethod.OPTIONS.equals(request.getMethod())) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 93ada27d1e..8f4087227c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -29,12 +29,6 @@ public interface TransportHandler { TransportType getTransportType(); - boolean canCreateSession(); - - SockJsSessionSupport createSession(String sessionId); - - boolean handleNoSession(ServerHttpRequest request, ServerHttpResponse response); - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java index 5373d3edc8..04936f7d56 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java @@ -15,6 +15,9 @@ */ package org.springframework.sockjs.server; +import java.util.Arrays; +import java.util.List; + import org.springframework.http.HttpMethod; @@ -25,30 +28,34 @@ import org.springframework.http.HttpMethod; */ public enum TransportType { - WEBSOCKET("websocket", HttpMethod.GET, false), + WEBSOCKET("websocket", HttpMethod.GET), - XHR("xhr", HttpMethod.POST, true), - XHR_SEND("xhr_send", HttpMethod.POST, true), + XHR("xhr", HttpMethod.POST, "cors", "jsessionid", "no_cache"), - JSONP("jsonp", HttpMethod.GET, false), - JSONP_SEND("jsonp_send", HttpMethod.POST, false), + XHR_SEND("xhr_send", HttpMethod.POST, "cors", "jsessionid", "no_cache"), - XHR_STREAMING("xhr_streaming", HttpMethod.POST, true), - EVENT_SOURCE("eventsource", HttpMethod.GET, false), - HTML_FILE("htmlfile", HttpMethod.GET, false); + JSONP("jsonp", HttpMethod.GET, "jsessionid", "no_cache"), + + JSONP_SEND("jsonp_send", HttpMethod.POST, "jsessionid", "no_cache"), + + XHR_STREAMING("xhr_streaming", HttpMethod.POST, "cors", "jsessionid", "no_cache"), + + EVENT_SOURCE("eventsource", HttpMethod.GET, "jsessionid", "no_cache"), + + HTML_FILE("htmlfile", HttpMethod.GET, "jsessionid", "no_cache"); private final String value; private final HttpMethod httpMethod; - private final boolean corsSupported; + private final List headerHints; - private TransportType(String value, HttpMethod httpMethod, boolean corsSupported) { + private TransportType(String value, HttpMethod httpMethod, String... headerHints) { this.value = value; this.httpMethod = httpMethod; - this.corsSupported = corsSupported; + this.headerHints = Arrays.asList(headerHints); } public String value() { @@ -62,11 +69,16 @@ public enum TransportType { return this.httpMethod; } - /** - * Are cross-domain requests (CORS) supported? - */ - public boolean isCorsSupported() { - return this.corsSupported; + public boolean setsNoCacheHeader() { + return this.headerHints.contains("no_cache"); + } + + public boolean supportsCors() { + return this.headerHints.contains("cors"); + } + + public boolean setsJsessionIdCookie() { + return this.headerHints.contains("jsessionid"); } public static TransportType fromValue(String transportValue) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 8a6ca6a564..4cd8af9f1b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -34,6 +34,7 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.sockjs.SockJsHandler; +import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.AbstractSockJsService; import org.springframework.sockjs.server.TransportHandler; @@ -188,14 +189,14 @@ public class DefaultSockJsService extends AbstractSockJsService HttpMethod supportedMethod = transportType.getHttpMethod(); if (!supportedMethod.equals(request.getMethod())) { - if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.isCorsSupported()) { + if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.supportsCors()) { response.setStatusCode(HttpStatus.NO_CONTENT); addCorsHeaders(request, response, supportedMethod, HttpMethod.OPTIONS); addCacheHeaders(response); } else { List supportedMethods = Arrays.asList(supportedMethod); - if (transportType.isCorsSupported()) { + if (transportType.supportsCors()) { supportedMethods.add(HttpMethod.OPTIONS); } sendMethodNotAllowed(response, supportedMethods); @@ -204,21 +205,22 @@ public class DefaultSockJsService extends AbstractSockJsService } SockJsSessionSupport session = getSockJsSession(sessionId, transportHandler); - if ((session == null) && !transportHandler.handleNoSession(request, response)) { - return; - } - addNoCacheHeaders(response); + if (session != null) { + if (transportType.setsNoCacheHeader()) { + addNoCacheHeaders(response); + } - if (isJsessionIdCookieNeeded()) { - Cookie cookie = request.getCookies().getCookie("JSESSIONID"); - String jsid = (cookie != null) ? cookie.getValue() : "dummy"; - // TODO: bypass use of Cookie object (causes Jetty to set Expires header) - response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/"); // TODO - } + if (transportType.setsJsessionIdCookie() && isJsessionIdCookieRequired()) { + Cookie cookie = request.getCookies().getCookie("JSESSIONID"); + String jsid = (cookie != null) ? cookie.getValue() : "dummy"; + // TODO: bypass use of Cookie object (causes Jetty to set Expires header) + response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/"); // TODO + } - if (transportType.isCorsSupported()) { - addCorsHeaders(request, response); + if (transportType.supportsCors()) { + addCorsHeaders(request, response); + } } transportHandler.handleRequest(request, response, session); @@ -231,22 +233,22 @@ public class DefaultSockJsService extends AbstractSockJsService return session; } - if (!transportHandler.canCreateSession()) { - return null; - } + if (transportHandler instanceof SockJsSessionFactory) { + SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; - synchronized (this.sessions) { - session = this.sessions.get(sessionId); - if (session != null) { + synchronized (this.sessions) { + session = this.sessions.get(sessionId); + if (session != null) { + return session; + } + logger.debug("Creating new session with session id \"" + sessionId + "\""); + session = (SockJsSessionSupport) sessionFactory.createSession(sessionId); + this.sessions.put(sessionId, session); return session; } - - logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = transportHandler.createSession(sessionId); - this.sessions.put(sessionId, session); - - return session; } + + return null; } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 0597e8621f..589e9770bd 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -51,25 +51,20 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport } @Override - 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 - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) throws Exception { + if (session == null) { + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + handleRequestInternal(request, response, session); + } + + protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + SockJsSessionSupport session) throws Exception { + String[] messages = null; try { messages = readMessages(request); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 8b43a6aff6..f8dbd59366 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -34,7 +35,8 @@ import org.springframework.sockjs.server.TransportHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractHttpSendingTransportHandler implements TransportHandler { +public abstract class AbstractHttpSendingTransportHandler + implements TransportHandler, SockJsSessionFactory { protected final Log logger = LogFactory.getLog(this.getClass()); @@ -49,16 +51,6 @@ public abstract class AbstractHttpSendingTransportHandler implements TransportHa 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, SockJsSessionSupport session) throws Exception { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java index 0221c33a77..ba0a224bf6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java @@ -50,21 +50,6 @@ public abstract class AbstractWebSocketTransportHandler implements TransportHand 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 { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index e839b77f4e..029e5c4ebb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -33,7 +33,7 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler } @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport sockJsSession) throws Exception { if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { From 177e0821721d57fa11d82988256e25a63fc3be85 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 12 Apr 2013 09:34:18 -0400 Subject: [PATCH 16/51] Refactor SockJS and WebSocket layer configuration Add HandlerProvider class Modify HandshakeHandler to accept + adapt WebSocketHandler at runtime Modify SockJsService to accept + adapt SockJsHandler at runtime --- .../sockjs/SockJsSessionFactory.java | 2 +- .../sockjs/SockJsSessionSupport.java | 6 +- .../sockjs/server/AbstractServerSession.java | 9 +- .../sockjs/server/AbstractSockJsService.java | 39 ++-- ...java => ConfigurableTransportHandler.java} | 18 +- .../sockjs/server/SockJsConfiguration.java | 20 --- .../server/SockJsService.java} | 23 ++- .../sockjs/server/TransportHandler.java | 7 +- .../server/TransportHandlerRegistry.java | 28 --- .../server/support/DefaultSockJsService.java | 167 +++++++++++------- .../DefaultTransportHandlerRegistrar.java | 77 -------- .../support/SockJsHttpRequestHandler.java | 105 +++++++++++ .../support/SockJsServiceHandlerMapping.java | 113 ------------ ...AbstractHttpReceivingTransportHandler.java | 5 +- .../AbstractHttpSendingTransportHandler.java | 20 ++- .../transport/AbstractHttpServerSession.java | 5 +- .../AbstractSockJsWebSocketHandler.java | 14 +- .../AbstractStreamingTransportHandler.java | 12 +- .../AbstractWebSocketTransportHandler.java | 65 ------- .../EndpointWebSocketTransportHandler.java | 42 ----- .../EventSourceTransportHandler.java | 7 +- .../transport/HtmlFileTransportHandler.java | 7 +- .../JsonpPollingTransportHandler.java | 16 +- .../transport/JsonpTransportHandler.java | 2 +- .../transport/PollingHttpServerSession.java | 5 +- .../transport/SockJsWebSocketHandler.java | 8 +- .../transport/StreamingHttpServerSession.java | 5 +- .../WebSocketSockJsHandlerAdapter.java | 11 +- .../transport/WebSocketTransportHandler.java | 125 +++++++++++++ .../transport/XhrPollingTransportHandler.java | 14 +- .../XhrStreamingTransportHandler.java | 7 +- .../websocket/HandlerProvider.java | 93 ++++++++++ .../AbstractEndpointConnectionManager.java | 52 +----- .../AnnotatedEndpointConnectionManager.java | 30 +++- .../client/EndpointConnectionManager.java | 33 +++- ...dler.java => DefaultHandshakeHandler.java} | 115 +++++++----- .../websocket/server/HandshakeHandler.java | 25 ++- .../server/RequestUpgradeStrategy.java | 57 ++++++ .../server/endpoint/EndpointRegistration.java | 77 +++----- .../server/endpoint/SpringConfigurator.java | 4 +- .../handshake/EndpointHandshakeHandler.java | 126 ------------- .../endpoint/handshake/package-info.java | 8 - .../server/endpoint/package-info.java | 2 +- .../AbstractEndpointUpgradeStrategy.java | 80 +++++++++ .../GlassfishRequestUpgradeStrategy.java | 8 +- .../TomcatRequestUpgradeStrategy.java | 8 +- ....java => WebSocketHttpRequestHandler.java} | 44 ++++- .../server/support/package-info.java | 2 +- 48 files changed, 919 insertions(+), 829 deletions(-) rename spring-websocket/src/main/java/org/springframework/sockjs/server/{TransportHandlerRegistrar.java => ConfigurableTransportHandler.java} (55%) rename spring-websocket/src/main/java/org/springframework/{websocket/server/endpoint/handshake/RequestUpgradeStrategy.java => sockjs/server/SockJsService.java} (53%) delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java rename spring-websocket/src/main/java/org/springframework/websocket/server/{AbstractHandshakeHandler.java => DefaultHandshakeHandler.java} (65%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java rename spring-websocket/src/main/java/org/springframework/websocket/server/{endpoint/handshake => support}/GlassfishRequestUpgradeStrategy.java (95%) rename spring-websocket/src/main/java/org/springframework/websocket/server/{endpoint/handshake => support}/TomcatRequestUpgradeStrategy.java (90%) rename spring-websocket/src/main/java/org/springframework/websocket/server/support/{HandshakeHttpRequestHandler.java => WebSocketHttpRequestHandler.java} (50%) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index f78c2ccf4b..b67dcefb2c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -24,6 +24,6 @@ package org.springframework.sockjs; */ public interface SockJsSessionFactory{ - S createSession(String sessionId); + S createSession(String sessionId, SockJsHandler sockJsHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java index 6e20a0b131..8c9747dc0a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java @@ -49,7 +49,7 @@ public abstract class SockJsSessionSupport implements SockJsSession { */ public SockJsSessionSupport(String sessionId, SockJsHandler sockJsHandler) { Assert.notNull(sessionId, "sessionId is required"); - Assert.notNull(sockJsHandler, "SockJsHandler is required"); + Assert.notNull(sockJsHandler, "sockJsHandler is required"); this.sessionId = sessionId; this.sockJsHandler = sockJsHandler; } @@ -58,10 +58,6 @@ public abstract class SockJsSessionSupport implements SockJsSession { return this.sessionId; } - public SockJsHandler getSockJsHandler() { - return this.sockJsHandler; - } - public boolean isNew() { return State.NEW.equals(this.state); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java index feb7599b80..caa436512b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java @@ -42,16 +42,11 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { private ScheduledFuture heartbeatTask; - public AbstractServerSession(String sessionId, SockJsConfiguration sockJsConfig) { - super(sessionId, getSockJsHandler(sockJsConfig)); + public AbstractServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + super(sessionId, sockJsHandler); this.sockJsConfig = sockJsConfig; } - private static SockJsHandler getSockJsHandler(SockJsConfiguration sockJsConfig) { - Assert.notNull(sockJsConfig, "sockJsConfig is required"); - return sockJsConfig.getSockJsHandler(); - } - protected SockJsConfiguration getSockJsConfig() { return this.sockJsConfig; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 2a81ca6f8c..e6823c0d4c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -32,11 +32,13 @@ 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.SockJsHandler; 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.web.util.UriUtils; /** @@ -45,7 +47,7 @@ import org.springframework.util.StringUtils; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractSockJsService implements SockJsConfiguration { +public abstract class AbstractSockJsService implements SockJsService, SockJsConfiguration { protected final Log logger = LogFactory.getLog(getClass()); @@ -169,10 +171,20 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { this.heartbeatScheduler = heartbeatScheduler; } + /** + * The amount of time in milliseconds before a client is considered + * disconnected after not having a receiving connection, i.e. an active + * connection over which the server can send data to the client. + *

+ * The default value is 5000. + */ public void setDisconnectDelay(long disconnectDelay) { this.disconnectDelay = disconnectDelay; } + /** + * Return the amount of time in milliseconds before a client is considered disconnected. + */ public long getDisconnectDelay() { return this.disconnectDelay; } @@ -191,7 +203,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { * Whether WebSocket transport is enabled. * @see #setWebSocketsEnabled(boolean) */ - public boolean isWebSocketsEnabled() { + public boolean isWebSocketEnabled() { return this.webSocketsEnabled; } @@ -205,8 +217,8 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { * * @throws Exception */ - public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath) - throws Exception { + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + String sockJsPath, SockJsHandler sockJsHandler) throws Exception { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -217,6 +229,10 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { // Ignore invalid Content-Type (TODO) } + String path = UriUtils.decode(request.getURI().getPath(), "URF-8"); + int index = path.indexOf(this.prefix); + sockJsPath = path.substring(index + this.prefix.length()); + try { if (sockJsPath.equals("") || sockJsPath.equals("/")) { response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); @@ -232,7 +248,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { return; } else if (sockJsPath.equals("/websocket")) { - handleRawWebSocket(request, response); + handleRawWebSocketRequest(request, response, sockJsHandler); return; } @@ -252,18 +268,19 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { return; } - handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport)); + handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), sockJsHandler); } finally { response.flush(); } } - protected abstract void handleRawWebSocket(ServerHttpRequest request, ServerHttpResponse response) - throws Exception; + protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsHandler sockJsHandler) throws Exception; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType) throws Exception; + String sessionId, TransportType transportType, SockJsHandler sockJsHandler) throws Exception; + protected boolean validateRequest(String serverId, String sessionId, String transport) { @@ -278,7 +295,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { return false; } - if (!isWebSocketsEnabled() && transport.equals(TransportType.WEBSOCKET.value())) { + if (!isWebSocketEnabled() && transport.equals(TransportType.WEBSOCKET.value())) { logger.debug("Websocket transport is disabled"); return false; } @@ -344,7 +361,7 @@ public abstract class AbstractSockJsService implements SockJsConfiguration { addCorsHeaders(request, response); addNoCacheHeaders(response); - String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieRequired(), isWebSocketsEnabled()); + String content = String.format(INFO_CONTENT, random.nextInt(), isJsessionIdCookieRequired(), isWebSocketEnabled()); response.getBody().write(content.getBytes()); } else if (HttpMethod.OPTIONS.equals(request.getMethod())) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java similarity index 55% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java index f89c2ea81b..8ad26ef6fa 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistrar.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java @@ -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,16 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server; +import java.util.Collection; + +import org.springframework.sockjs.SockJsHandler; +import org.springframework.websocket.WebSocketHandler; + /** * * @author Rossen Stoyanchev * @since 4.0 */ -public interface TransportHandlerRegistrar { +public interface ConfigurableTransportHandler extends TransportHandler { - void registerTransportHandlers(TransportHandlerRegistry registry, SockJsConfiguration config); + void setSockJsConfiguration(SockJsConfiguration sockJsConfig); + + /** + * Pre-register {@link SockJsHandler} instances so they can be adapted to + * {@link WebSocketHandler} and hence re-used at runtime. + */ + void registerSockJsHandlers(Collection sockJsHandlers); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java index a122a769e3..4bf1db79e8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java @@ -17,18 +17,14 @@ package org.springframework.sockjs.server; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.sockjs.SockJsHandler; - /** - * * * @author Rossen Stoyanchev * @since 4.0 */ public interface SockJsConfiguration { - /** * Streaming transports save responses on the client side and don't free * memory used by delivered messages. Such transports need to recycle the @@ -42,15 +38,6 @@ public interface SockJsConfiguration { */ public int getStreamBytesLimit(); - /** - * The amount of time in milliseconds before a client is considered - * disconnected after not having a receiving connection, i.e. an active - * connection over which the server can send data to the client. - *

- * The default value is 5000. - */ - public long getDisconnectDelay(); - /** * The amount of time in milliseconds when the server has not sent any * messages and after which the server should send a heartbeat frame to the @@ -67,11 +54,4 @@ 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(); - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java similarity index 53% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/RequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 2dd09023b7..dfb1e002d4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -14,29 +14,34 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint.handshake; +package org.springframework.sockjs.server; -import javax.websocket.Endpoint; +import java.util.Collection; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.sockjs.SockJsHandler; +import org.springframework.websocket.WebSocketHandler; /** - * A strategy for performing the container-specific steps for upgrading an HTTP request - * as part of a WebSocket handshake. * * @author Rossen Stoyanchev * @since 4.0 */ -public interface RequestUpgradeStrategy { +public interface SockJsService { - String[] getSupportedVersions(); + String getPrefix(); /** - * Invoked after the handshake checks have been performed and succeeded. + * Pre-register {@link SockJsHandler} instances so they can be adapted to + * {@link WebSocketHandler} and hence re-used at runtime when + * {@link #handleRequest(ServerHttpRequest, ServerHttpResponse, String, SockJsHandler) handleRequest} + * is called. */ - void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, Endpoint endpoint) - throws Exception; + void registerSockJsHandlers(Collection sockJsHandlers); + + void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, + SockJsHandler handler) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 8f4087227c..ea6208b836 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the toriginal author or authors. + * 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. @@ -17,6 +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; @@ -29,7 +30,7 @@ public interface TransportHandler { TransportType getTransportType(); - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) - throws Exception; + void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java deleted file mode 100644 index 0c4d232e9e..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandlerRegistry.java +++ /dev/null @@ -1,28 +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; - - -/** - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public interface TransportHandlerRegistry { - - void registerHandler(TransportHandler handler); - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 4cd8af9f1b..af0789aa02 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -16,16 +16,13 @@ package org.springframework.sockjs.server.support; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -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; @@ -37,11 +34,21 @@ import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.AbstractSockJsService; +import org.springframework.sockjs.server.ConfigurableTransportHandler; 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.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.WebSocketSockJsHandlerAdapter; +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.Assert; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; @@ -51,37 +58,22 @@ import org.springframework.websocket.server.HandshakeHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public class DefaultSockJsService extends AbstractSockJsService - implements TransportHandlerRegistry, BeanFactoryAware, InitializingBean { +public class DefaultSockJsService extends AbstractSockJsService implements InitializingBean { - private final Class sockJsHandlerClass; + private final Map transportHandlers = new HashMap(); - private final SockJsHandler sockJsHandler; + private final Map transportHandlerOverrides = new HashMap(); private TaskScheduler sessionTimeoutScheduler; private final Map sessions = new ConcurrentHashMap(); - private final Map transportHandlers = new HashMap(); - - private AutowireCapableBeanFactory beanFactory; + private final Map sockJsHandlers = new HashMap(); - public DefaultSockJsService(String prefix, Class sockJsHandlerClass) { - this(prefix, sockJsHandlerClass, null); - } - - public DefaultSockJsService(String prefix, SockJsHandler sockJsHandler) { - this(prefix, null, sockJsHandler); - } - - private DefaultSockJsService(String prefix, Class handlerClass, SockJsHandler handler) { + public DefaultSockJsService(String prefix) { super(prefix); - 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, this); } /** @@ -98,43 +90,62 @@ public class DefaultSockJsService extends AbstractSockJsService this.sessionTimeoutScheduler = sessionTimeoutScheduler; } - @Override - public void registerHandler(TransportHandler transportHandler) { - Assert.notNull(transportHandler, "transportHandler is required"); - this.transportHandlers.put(transportHandler.getTransportType(), transportHandler); - } - - public void setTransportHandlerRegistrar(TransportHandlerRegistrar registrar) { - Assert.notNull(registrar, "registrar is required"); + public void setTransportHandlers(TransportHandler... handlers) { this.transportHandlers.clear(); - registrar.registerTransportHandlers(this, this); - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (beanFactory instanceof AutowireCapableBeanFactory) { - this.beanFactory = (AutowireCapableBeanFactory) beanFactory; + for (TransportHandler handler : handlers) { + this.transportHandlers.put(handler.getTransportType(), handler); } } - @Override - public SockJsHandler getSockJsHandler() { - return (this.sockJsHandlerClass != null) ? - this.beanFactory.createBean(this.sockJsHandlerClass) : this.sockJsHandler; + public void setTransportHandlerOverrides(TransportHandler... handlers) { + this.transportHandlerOverrides.clear(); + for (TransportHandler handler : handlers) { + this.transportHandlerOverrides.put(handler.getTransportType(), handler); + } + } + + public void registerSockJsHandlers(Collection sockJsHandlers) { + for (SockJsHandler sockJsHandler : sockJsHandlers) { + if (!this.sockJsHandlers.containsKey(sockJsHandler)) { + this.sockJsHandlers.put(sockJsHandler, adaptSockJsHandler(sockJsHandler)); + } + } + configureTransportHandlers(); + } + + /** + * Adapt the {@link SockJsHandler} to the {@link WebSocketHandler} contract for + * raw WebSocket communication on SockJS path "/websocket". + */ + protected WebSocketSockJsHandlerAdapter adaptSockJsHandler(SockJsHandler sockJsHandler) { + return new WebSocketSockJsHandlerAdapter(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.isEmpty()) { + if (isWebSocketEnabled() && (this.transportHandlerOverrides.get(TransportType.WEBSOCKET) == null)) { + this.transportHandlers.put(TransportType.WEBSOCKET, + new WebSocketTransportHandler(new DefaultHandshakeHandler())); + } + this.transportHandlers.put(TransportType.XHR, new XhrPollingTransportHandler()); + this.transportHandlers.put(TransportType.XHR_SEND, new XhrTransportHandler()); + this.transportHandlers.put(TransportType.JSONP, new JsonpPollingTransportHandler()); + this.transportHandlers.put(TransportType.JSONP_SEND, new JsonpTransportHandler()); + this.transportHandlers.put(TransportType.XHR_STREAMING, new XhrStreamingTransportHandler()); + this.transportHandlers.put(TransportType.EVENT_SOURCE, new EventSourceTransportHandler()); + this.transportHandlers.put(TransportType.HTML_FILE, new HtmlFileTransportHandler()); } - if (this.transportHandlers.get(TransportType.WEBSOCKET) == null) { - logger.warn("No WebSocket transport handler was registered"); + if (!this.transportHandlerOverrides.isEmpty()) { + for (TransportHandler transportHandler : this.transportHandlerOverrides.values()) { + this.transportHandlers.put(transportHandler.getTransportType(), transportHandler); + } } + configureTransportHandlers(); + this.sessionTimeoutScheduler.scheduleAtFixedRate(new Runnable() { public void run() { try { @@ -162,22 +173,45 @@ public class DefaultSockJsService extends AbstractSockJsService }, 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); + + private void configureTransportHandlers() { + for (TransportHandler h : this.transportHandlers.values()) { + if (h instanceof ConfigurableTransportHandler) { + ((ConfigurableTransportHandler) h).setSockJsConfiguration(this); + if (!this.sockJsHandlers.isEmpty()) { + ((ConfigurableTransportHandler) h).registerSockJsHandlers(this.sockJsHandlers.keySet()); + if (h instanceof HandshakeHandler) { + ((HandshakeHandler) h).registerWebSocketHandlers(this.sockJsHandlers.values()); + } + } + } } } + @Override + protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsHandler sockJsHandler) throws Exception { + + if (isWebSocketEnabled()) { + TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); + if (transportHandler != null) { + if (transportHandler instanceof HandshakeHandler) { + WebSocketHandler webSocketHandler = this.sockJsHandlers.get(sockJsHandler); + if (webSocketHandler == null) { + webSocketHandler = adaptSockJsHandler(sockJsHandler); + } + ((HandshakeHandler) transportHandler).doHandshake(request, response, webSocketHandler); + return; + } + } + logger.warn("No handler for raw WebSocket messages"); + } + response.setStatusCode(HttpStatus.NOT_FOUND); + } + @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType) throws Exception { + String sessionId, TransportType transportType, SockJsHandler sockJsHandler) throws Exception { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -204,7 +238,7 @@ public class DefaultSockJsService extends AbstractSockJsService return; } - SockJsSessionSupport session = getSockJsSession(sessionId, transportHandler); + SockJsSessionSupport session = getSockJsSession(sessionId, sockJsHandler, transportHandler); if (session != null) { if (transportType.setsNoCacheHeader()) { @@ -215,7 +249,7 @@ public class DefaultSockJsService extends AbstractSockJsService Cookie cookie = request.getCookies().getCookie("JSESSIONID"); String jsid = (cookie != null) ? cookie.getValue() : "dummy"; // TODO: bypass use of Cookie object (causes Jetty to set Expires header) - response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/"); // TODO + response.getHeaders().set("Set-Cookie", "JSESSIONID=" + jsid + ";path=/"); } if (transportType.supportsCors()) { @@ -223,10 +257,11 @@ public class DefaultSockJsService extends AbstractSockJsService } } - transportHandler.handleRequest(request, response, session); + transportHandler.handleRequest(request, response, sockJsHandler, session); } - public SockJsSessionSupport getSockJsSession(String sessionId, TransportHandler transportHandler) { + public SockJsSessionSupport getSockJsSession(String sessionId, SockJsHandler sockJsHandler, + TransportHandler transportHandler) { SockJsSessionSupport session = this.sessions.get(sessionId); if (session != null) { @@ -242,7 +277,7 @@ public class DefaultSockJsService extends AbstractSockJsService return session; } logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = (SockJsSessionSupport) sessionFactory.createSession(sessionId); + session = (SockJsSessionSupport) sessionFactory.createSession(sessionId, sockJsHandler); this.sessions.put(sessionId, session); return session; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java deleted file mode 100644 index fbbeab48a4..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultTransportHandlerRegistrar.java +++ /dev/null @@ -1,77 +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.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.XhrPollingTransportHandler; -import org.springframework.sockjs.server.transport.XhrStreamingTransportHandler; -import org.springframework.sockjs.server.transport.XhrTransportHandler; -import org.springframework.util.ClassUtils; - - -/** - * TODO - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class DefaultTransportHandlerRegistrar implements TransportHandlerRegistrar { - - private static final boolean standardWebSocketApiPresent = ClassUtils.isPresent( - "javax.websocket.server.ServerEndpointConfig", DefaultTransportHandlerRegistrar.class.getClassLoader()); - - - 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(config)); - registry.registerHandler(new JsonpTransportHandler()); - - 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); - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java new file mode 100644 index 0000000000..977f9eb8b9 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -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.sockjs.server.support; + +import java.io.IOException; +import java.util.Collections; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.http.server.AsyncServletServerHttpRequest; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.sockjs.SockJsHandler; +import org.springframework.sockjs.server.SockJsService; +import org.springframework.util.Assert; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.util.NestedServletException; +import org.springframework.web.util.UrlPathHelper; +import org.springframework.websocket.HandlerProvider; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactoryAware { + + private final SockJsService sockJsService; + + private final HandlerProvider handlerProvider; + + private final UrlPathHelper urlPathHelper = new UrlPathHelper(); + + + public SockJsHttpRequestHandler(SockJsService sockJsService, SockJsHandler sockJsHandler) { + Assert.notNull(sockJsService, "sockJsService is required"); + Assert.notNull(sockJsHandler, "sockJsHandler is required"); + this.sockJsService = sockJsService; + this.sockJsService.registerSockJsHandlers(Collections.singleton(sockJsHandler)); + this.handlerProvider = new HandlerProvider(sockJsHandler); + } + + public SockJsHttpRequestHandler(SockJsService sockJsService, Class sockJsHandlerClass) { + Assert.notNull(sockJsService, "sockJsService is required"); + Assert.notNull(sockJsHandlerClass, "sockJsHandlerClass is required"); + this.sockJsService = sockJsService; + this.handlerProvider = new HandlerProvider(sockJsHandlerClass); + } + + public String getMappingPattern() { + return this.sockJsService.getPrefix() + "/**"; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.handlerProvider.setBeanFactory(beanFactory); + } + + @Override + public void handleRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); + String prefix = this.sockJsService.getPrefix(); + + Assert.isTrue(lookupPath.startsWith(prefix), + "Request path does not match the prefix of the SockJsService " + prefix); + + String sockJsPath = lookupPath.substring(prefix.length()); + + ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response); + ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + + try { + SockJsHandler sockJsHandler = this.handlerProvider.getHandler(); + this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, sockJsHandler); + } + catch (Exception ex) { + // TODO + throw new NestedServletException("SockJS service failure", ex); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java deleted file mode 100644 index 656324fe5b..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsServiceHandlerMapping.java +++ /dev/null @@ -1,113 +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.support; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.http.server.AsyncServletServerHttpRequest; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.sockjs.server.AbstractSockJsService; -import org.springframework.web.HttpRequestHandler; -import org.springframework.web.servlet.handler.AbstractHandlerMapping; -import org.springframework.web.util.NestedServletException; - -/** - * A Spring MVC HandlerMapping for matching requests to a SockJS services based on the - * {@link AbstractSockJsService#getPrefix() prefix} property of each service. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class SockJsServiceHandlerMapping extends AbstractHandlerMapping { - - private static Log logger = LogFactory.getLog(SockJsServiceHandlerMapping.class); - - private final List sockJsServices; - - - public SockJsServiceHandlerMapping(AbstractSockJsService... sockJsServices) { - this.sockJsServices = Arrays.asList(sockJsServices); - } - - @Override - protected Object getHandlerInternal(HttpServletRequest request) throws Exception { - - String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); - if (logger.isDebugEnabled()) { - logger.debug("Looking for SockJS service match to path " + lookupPath); - } - - for (AbstractSockJsService service : this.sockJsServices) { - if (lookupPath.startsWith(service.getPrefix())) { - if (logger.isDebugEnabled()) { - logger.debug("Matched to " + service); - } - String sockJsPath = lookupPath.substring(service.getPrefix().length()); - return new SockJsServiceHttpRequestHandler(service, sockJsPath); - } - } - - if (logger.isDebugEnabled()) { - logger.debug("Did not find a match"); - } - - return null; - } - - - /** - * {@link HttpRequestHandler} wrapping the invocation of the selected SockJS service. - */ - private static class SockJsServiceHttpRequestHandler implements HttpRequestHandler { - - private final String sockJsPath; - - private final AbstractSockJsService sockJsService; - - - public SockJsServiceHttpRequestHandler(AbstractSockJsService sockJsService, String sockJsPath) { - this.sockJsService = sockJsService; - this.sockJsPath = sockJsPath; - } - - @Override - public void handleRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - - ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response); - ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - - try { - this.sockJsService.handleRequest(httpRequest, httpResponse, this.sockJsPath); - } - catch (Exception ex) { - // TODO - throw new NestedServletException("SockJS service failure", ex); - } - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 589e9770bd..300adbd981 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -25,6 +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.TransportHandler; @@ -51,8 +52,8 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport } @Override - public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsSessionSupport session) - throws Exception { + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { if (session == null) { response.setStatusCode(HttpStatus.NOT_FOUND); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index f8dbd59366..aff6cea280 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -16,18 +16,20 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; +import java.util.Collection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; +import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportHandler; /** * TODO @@ -36,24 +38,30 @@ import org.springframework.sockjs.server.TransportHandler; * @since 4.0 */ public abstract class AbstractHttpSendingTransportHandler - implements TransportHandler, SockJsSessionFactory { + implements ConfigurableTransportHandler, SockJsSessionFactory { protected final Log logger = LogFactory.getLog(this.getClass()); - private final SockJsConfiguration sockJsConfig; + private SockJsConfiguration sockJsConfig; - public AbstractHttpSendingTransportHandler(SockJsConfiguration sockJsConfig) { + @Override + public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) { this.sockJsConfig = sockJsConfig; } - protected SockJsConfiguration getSockJsConfig() { + @Override + public void registerSockJsHandlers(Collection sockJsHandlers) { + // ignore + } + + public SockJsConfiguration getSockJsConfig() { return this.sockJsConfig; } @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsSessionSupport session) throws Exception { + SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { // Set content type before writing response.getHeaders().setContentType(getContentType()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java index 5f657d9470..e03fb4a4c9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java @@ -22,6 +22,7 @@ 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; @@ -46,8 +47,8 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { private ServerHttpResponse response; - public AbstractHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig) { - super(sessionId, sockJsConfig); + public AbstractHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + super(sessionId, sockJsConfig, sockJsHandler); } public void setFrameFormat(FrameFormat frameFormat) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java index 9f13cd10b1..2f723092fe 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java @@ -22,8 +22,10 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.util.Assert; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -39,18 +41,27 @@ public abstract class AbstractSockJsWebSocketHandler implements WebSocketHandler private final SockJsConfiguration sockJsConfig; + private final SockJsHandler sockJsHandler; + private final Map sessions = new ConcurrentHashMap(); - public AbstractSockJsWebSocketHandler(SockJsConfiguration sockJsConfig) { + public AbstractSockJsWebSocketHandler(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + Assert.notNull(sockJsConfig, "sockJsConfig is required"); + Assert.notNull(sockJsHandler, "sockJsHandler is required"); this.sockJsConfig = sockJsConfig; + this.sockJsHandler = sockJsHandler; } protected SockJsConfiguration getSockJsConfig() { return this.sockJsConfig; } + protected SockJsHandler getSockJsHandler() { + return this.sockJsHandler; + } + protected SockJsSessionSupport getSockJsSession(WebSocketSession wsSession) { return this.sessions.get(wsSession); } @@ -62,7 +73,6 @@ public abstract class AbstractSockJsWebSocketHandler implements WebSocketHandler } SockJsSessionSupport session = createSockJsSession(wsSession); this.sessions.put(wsSession, session); - session.connectionInitialized(); } protected abstract SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) throws Exception; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java index 89b076d93f..5aa8eb11be 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -19,7 +19,8 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.SockJsHandler; +import org.springframework.util.Assert; /** @@ -31,13 +32,10 @@ import org.springframework.sockjs.server.SockJsConfiguration; public abstract class AbstractStreamingTransportHandler extends AbstractHttpSendingTransportHandler { - public AbstractStreamingTransportHandler(SockJsConfiguration sockJsConfig) { - super(sockJsConfig); - } - @Override - public StreamingHttpServerSession createSession(String sessionId) { - return new StreamingHttpServerSession(sessionId, getSockJsConfig()); + public StreamingHttpServerSession createSession(String sessionId, SockJsHandler sockJsHandler) { + Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + return new StreamingHttpServerSession(sessionId, getSockJsConfig(), sockJsHandler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java deleted file mode 100644 index ba0a224bf6..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractWebSocketTransportHandler.java +++ /dev/null @@ -1,65 +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.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 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); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java deleted file mode 100644 index 8d41943c9a..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EndpointWebSocketTransportHandler.java +++ /dev/null @@ -1,42 +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.sockjs.server.SockJsConfiguration; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.server.endpoint.handshake.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); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index 235b6f5de1..ae8efbc667 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -21,10 +21,9 @@ 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.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.sockjs.server.TransportType; /** @@ -36,10 +35,6 @@ 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; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index 35e48b238e..d6568fa797 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -22,10 +22,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.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.sockjs.server.TransportType; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; @@ -67,10 +66,6 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler } - public HtmlFileTransportHandler(SockJsConfiguration sockJsConfig) { - super(sockJsConfig); - } - @Override public TransportType getTransportType() { return TransportType.HTML_FILE; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index 28fe888714..a00c5de6b7 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -21,10 +21,11 @@ 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.server.SockJsConfiguration; +import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.sockjs.server.TransportType; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.sockjs.server.TransportType; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; @@ -38,10 +39,6 @@ import org.springframework.web.util.JavaScriptUtils; public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHandler { - public JsonpPollingTransportHandler(SockJsConfiguration sockJsConfig) { - super(sockJsConfig); - } - @Override public TransportType getTransportType() { return TransportType.JSONP; @@ -53,8 +50,9 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingHttpServerSession createSession(String sessionId) { - return new PollingHttpServerSession(sessionId, getSockJsConfig()); + public PollingHttpServerSession createSession(String sessionId, SockJsHandler sockJsHandler) { + Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + return new PollingHttpServerSession(sessionId, getSockJsConfig(), sockJsHandler); } @Override @@ -67,7 +65,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); return; } - super.handleRequest(request, response, session); + super.handleRequestInternal(request, response, session); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index 029e5c4ebb..95e90dccc0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -44,7 +44,7 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler } } - super.handleRequest(request, response, sockJsSession); + super.handleRequestInternal(request, response, sockJsSession); response.getBody().write("ok".getBytes("UTF-8")); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java index 34302f610f..d48fa31a2c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java @@ -17,14 +17,15 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; +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, SockJsConfiguration sockJsConfig) { - super(sessionId, sockJsConfig); + public PollingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + super(sessionId, sockJsConfig, sockJsHandler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index c19818e03d..f90fbdb13e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -43,8 +43,8 @@ public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler { private final ObjectMapper objectMapper = new ObjectMapper(); - public SockJsWebSocketHandler(SockJsConfiguration config) { - super(config); + public SockJsWebSocketHandler(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + super(sockJsConfig, sockJsHandler); } @Override @@ -78,8 +78,8 @@ public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler { private WebSocketSession webSocketSession; - public WebSocketServerSession(WebSocketSession wsSession, SockJsConfiguration config) throws Exception { - super(String.valueOf(wsSession.hashCode()), config); + public WebSocketServerSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) throws Exception { + super(String.valueOf(wsSession.hashCode()), sockJsConfig, getSockJsHandler()); this.webSocketSession = wsSession; this.webSocketSession.sendText(SockJsFrame.openFrame().getContent()); scheduleHeartbeat(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java index 94cf47cd9d..71bdf2d9dd 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java @@ -18,6 +18,7 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -27,8 +28,8 @@ public class StreamingHttpServerSession extends AbstractHttpServerSession { private int byteCount; - public StreamingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig) { - super(sessionId, sockJsConfig); + public StreamingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + super(sessionId, sockJsConfig, sockJsHandler); } protected void flushCache() throws IOException { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java index 82e94f6788..9ec2a9e386 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java @@ -36,12 +36,12 @@ import org.springframework.websocket.WebSocketSession; public class WebSocketSockJsHandlerAdapter extends AbstractSockJsWebSocketHandler { - public WebSocketSockJsHandlerAdapter(SockJsConfiguration sockJsConfig) { - super(sockJsConfig); + public WebSocketSockJsHandlerAdapter(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + super(sockJsConfig, sockJsHandler); } @Override - protected SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) { + protected SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) throws Exception { return new WebSocketSessionAdapter(wsSession); } @@ -51,9 +51,10 @@ public class WebSocketSockJsHandlerAdapter extends AbstractSockJsWebSocketHandle private final WebSocketSession wsSession; - public WebSocketSessionAdapter(WebSocketSession wsSession) { - super(String.valueOf(wsSession.hashCode()), getSockJsConfig().getSockJsHandler()); + public WebSocketSessionAdapter(WebSocketSession wsSession) throws Exception { + super(String.valueOf(wsSession.hashCode()), getSockJsHandler()); this.wsSession = wsSession; + connectionInitialized(); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java new file mode 100644 index 0000000000..3fc0a7f9f3 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -0,0 +1,125 @@ +/* + * 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.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +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.ConfigurableTransportHandler; +import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.server.TransportHandler; +import org.springframework.sockjs.server.TransportType; +import org.springframework.util.Assert; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.server.HandshakeHandler; + + +/** + * A WebSocket {@link TransportHandler} that delegates to a {@link HandshakeHandler} + * passing a SockJS {@link WebSocketHandler}. Also implements {@link HandshakeHandler} + * directly in support for raw WebSocket communication at SockJS URL "/websocket". + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class WebSocketTransportHandler implements ConfigurableTransportHandler, HandshakeHandler { + + private final HandshakeHandler handshakeHandler; + + private SockJsConfiguration sockJsConfig; + + private final Map sockJsHandlers = new HashMap(); + + private final Collection rawWebSocketHandlers = new ArrayList(); + + + public WebSocketTransportHandler(HandshakeHandler handshakeHandler) { + Assert.notNull(handshakeHandler, "handshakeHandler is required"); + this.handshakeHandler = handshakeHandler; + } + + @Override + public TransportType getTransportType() { + return TransportType.WEBSOCKET; + } + + @Override + public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) { + this.sockJsConfig = sockJsConfig; + } + + @Override + public void registerSockJsHandlers(Collection sockJsHandlers) { + this.sockJsHandlers.clear(); + for (SockJsHandler sockJsHandler : sockJsHandlers) { + this.sockJsHandlers.put(sockJsHandler, adaptSockJsHandler(sockJsHandler)); + } + this.handshakeHandler.registerWebSocketHandlers(getAllWebSocketHandlers()); + } + + /** + * Adapt the {@link SockJsHandler} to the {@link WebSocketHandler} contract for + * exchanging SockJS message over WebSocket. + */ + protected WebSocketHandler adaptSockJsHandler(SockJsHandler sockJsHandler) { + return new SockJsWebSocketHandler(this.sockJsConfig, sockJsHandler); + } + + private Collection getAllWebSocketHandlers() { + Set handlers = new HashSet(); + handlers.addAll(this.sockJsHandlers.values()); + handlers.addAll(this.rawWebSocketHandlers); + return handlers; + } + + @Override + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { + + WebSocketHandler webSocketHandler = this.sockJsHandlers.get(sockJsHandler); + if (webSocketHandler == null) { + webSocketHandler = adaptSockJsHandler(sockJsHandler); + } + + this.handshakeHandler.doHandshake(request, response, webSocketHandler); + } + + // HandshakeHandler methods + + @Override + public void registerWebSocketHandlers(Collection webSocketHandlers) { + this.rawWebSocketHandlers.clear(); + this.rawWebSocketHandlers.addAll(webSocketHandlers); + this.handshakeHandler.registerWebSocketHandlers(getAllWebSocketHandlers()); + } + + @Override + public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler webSocketHandler) throws Exception { + + return this.handshakeHandler.doHandshake(request, response, webSocketHandler); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index 78733099e0..7655e42948 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -19,10 +19,11 @@ import java.nio.charset.Charset; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.TransportType; +import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.sockjs.server.TransportType; +import org.springframework.util.Assert; /** @@ -34,10 +35,6 @@ 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; @@ -53,8 +50,9 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingHttpServerSession createSession(String sessionId) { - return new PollingHttpServerSession(sessionId, getSockJsConfig()); + public PollingHttpServerSession createSession(String sessionId, SockJsHandler sockJsHandler) { + Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + return new PollingHttpServerSession(sessionId, getSockJsConfig(), sockJsHandler); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index 0f49b75de3..cf40744284 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -21,10 +21,9 @@ 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.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.sockjs.server.TransportType; /** @@ -36,10 +35,6 @@ 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; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java new file mode 100644 index 0000000000..f8d2d90bd2 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java @@ -0,0 +1,93 @@ +/* + * 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 org.apache.commons.logging.Log; +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.util.Assert; +import org.springframework.util.ClassUtils; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class HandlerProvider implements BeanFactoryAware { + + private final T handlerBean; + + private final Class handlerClass; + + private AutowireCapableBeanFactory beanFactory; + + private Log logger; + + + public HandlerProvider(T handlerBean) { + Assert.notNull(handlerBean, "handlerBean is required"); + this.handlerBean = handlerBean; + this.handlerClass = null; + } + + public HandlerProvider(Class handlerClass) { + Assert.notNull(handlerClass, "handlerClass is required"); + this.handlerBean = null; + this.handlerClass = handlerClass; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof AutowireCapableBeanFactory) { + this.beanFactory = (AutowireCapableBeanFactory) beanFactory; + } + } + + public void setLogger(Log logger) { + this.logger = logger; + } + + public boolean isSingleton() { + return (this.handlerBean != null); + } + + @SuppressWarnings("unchecked") + public Class getHandlerType() { + if (this.handlerClass != null) { + return this.handlerClass; + } + return (Class) ClassUtils.getUserClass(this.handlerBean.getClass()); + } + + public T getHandler() { + if (this.handlerBean != null) { + if (logger != null && logger.isTraceEnabled()) { + logger.trace("Returning handler singleton " + this.handlerBean); + } + return this.handlerBean; + } + Assert.isTrue(this.beanFactory != null, "BeanFactory is required to initialize handler instances."); + if (logger != null && logger.isTraceEnabled()) { + logger.trace("Creating handler of type " + this.handlerClass); + } + return this.beanFactory.createBean(this.handlerClass); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java index 8e5d3f754a..9a24e04af5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java @@ -26,13 +26,9 @@ import javax.websocket.WebSocketContainer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; -import org.springframework.util.Assert; import org.springframework.web.util.UriComponentsBuilder; @@ -41,14 +37,10 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractEndpointConnectionManager implements ApplicationContextAware, SmartLifecycle { +public abstract class AbstractEndpointConnectionManager implements SmartLifecycle { protected final Log logger = LogFactory.getLog(getClass()); - private final Class endpointClass; - - private final Object endpointBean; - private final URI uri; private boolean autoStartup = false; @@ -59,29 +51,13 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo private Session session; - private ApplicationContext applicationContext; - private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-"); private final Object lifecycleMonitor = new Object(); - public AbstractEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { - Assert.notNull(endpointClass, "endpointClass is required"); - this.endpointClass = endpointClass; - this.endpointBean = null; - this.uri = initUri(uriTemplate, uriVariables); - } - - public AbstractEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { - Assert.notNull(endpointBean, "endpointBean is required"); - this.endpointClass = null; - this.endpointBean = endpointBean; - this.uri = initUri(uriTemplate, uriVariables); - } - - private static URI initUri(String uri, Object... uriVariables) { - return UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables).encode().toUri(); + public AbstractEndpointConnectionManager(String uriTemplate, Object... uriVariables) { + this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); } public void setAsyncSendTimeout(long timeoutInMillis) { @@ -137,11 +113,6 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo return this.phase; } - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - protected URI getUri() { return this.uri; } @@ -150,17 +121,6 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo return this.webSocketContainer; } - 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 { - return this.endpointBean; - } - } - /** * Auto-connects to the configured {@link #setDefaultUri(URI) default URI}. */ @@ -173,10 +133,10 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo synchronized (lifecycleMonitor) { try { logger.info("Connecting to endpoint at URI " + uri); - session = connect(getEndpoint()); + session = connect(); logger.info("Successfully connected"); } - catch (Exception ex) { + catch (Throwable ex) { logger.error("Failed to connect to endpoint at " + uri, ex); } } @@ -186,7 +146,7 @@ public abstract class AbstractEndpointConnectionManager implements ApplicationCo } } - protected abstract Session connect(Object endpoint) throws DeploymentException, IOException; + protected abstract Session connect() throws DeploymentException, IOException; /** * Deactivates the configured message endpoint. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java index ccc4c3f4a0..a1005e3507 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java @@ -21,25 +21,47 @@ 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; +import org.springframework.websocket.HandlerProvider; + /** * * @author Rossen Stoyanchev * @since 4.0 */ -public class AnnotatedEndpointConnectionManager extends AbstractEndpointConnectionManager { +public class AnnotatedEndpointConnectionManager extends AbstractEndpointConnectionManager + implements BeanFactoryAware { + + private static Log logger = LogFactory.getLog(AnnotatedEndpointConnectionManager.class); + + private final HandlerProvider endpointProvider; public AnnotatedEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { - super(endpointClass, uriTemplate, uriVariables); + super(uriTemplate, uriVariables); + this.endpointProvider = new HandlerProvider(endpointClass); + this.endpointProvider.setLogger(logger); } public AnnotatedEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { - super(endpointBean, uriTemplate, uriVariables); + super(uriTemplate, uriVariables); + this.endpointProvider = new HandlerProvider(endpointBean); + this.endpointProvider.setLogger(logger); } @Override - protected Session connect(Object endpoint) throws DeploymentException, IOException { + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.endpointProvider.setBeanFactory(beanFactory); + } + + @Override + protected Session connect() throws DeploymentException, IOException { + Object endpoint = this.endpointProvider.getHandler(); return getWebSocketContainer().connectToServer(endpoint, getUri()); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java index 472a9df942..9ca8653bbf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java @@ -29,23 +29,41 @@ 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; +import org.springframework.util.Assert; +import org.springframework.websocket.HandlerProvider; + /** * * @author Rossen Stoyanchev * @since 4.0 */ -public class EndpointConnectionManager extends AbstractEndpointConnectionManager { +public class EndpointConnectionManager extends AbstractEndpointConnectionManager implements BeanFactoryAware { + + private static Log logger = LogFactory.getLog(EndpointConnectionManager.class); private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); + private final HandlerProvider endpointProvider; + public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { - super(endpointClass, uriTemplate, uriVariables); + super(uriTemplate, uriVariables); + Assert.notNull(endpointClass, "endpointClass is required"); + this.endpointProvider = new HandlerProvider(endpointClass); + this.endpointProvider.setLogger(logger); } public EndpointConnectionManager(Endpoint endpointBean, String uriTemplate, Object... uriVariables) { - super(endpointBean, uriTemplate, uriVariables); + super(uriTemplate, uriVariables); + Assert.notNull(endpointBean, "endpointBean is required"); + this.endpointProvider = new HandlerProvider(endpointBean); + this.endpointProvider.setLogger(logger); } public void setSubProtocols(String... subprotocols) { @@ -69,8 +87,13 @@ public class EndpointConnectionManager extends AbstractEndpointConnectionManager } @Override - protected Session connect(Object endpoint) throws DeploymentException, IOException { - Endpoint typedEndpoint = (Endpoint) endpoint; + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.endpointProvider.setBeanFactory(beanFactory); + } + + @Override + protected Session connect() throws DeploymentException, IOException { + Endpoint typedEndpoint = this.endpointProvider.getHandler(); ClientEndpointConfig endpointConfig = this.configBuilder.build(); return getWebSocketContainer().connectToServer(typedEndpoint, endpointConfig, getUri()); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java similarity index 65% rename from spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 7ff08b8097..cf4e4046f6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/AbstractHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -21,6 +21,7 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -28,48 +29,53 @@ 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.beans.BeanUtils; 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.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.websocket.WebSocketHandler; /** + * TODO + *

+ * A container-specific {@link RequestUpgradeStrategy} is required since standard Java + * WebSocket currently does not provide a way to initiate a WebSocket handshake. + * Currently available are implementations for Tomcat and Glassfish. * * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractHandshakeHandler implements HandshakeHandler, BeanFactoryAware { +public class DefaultHandshakeHandler implements HandshakeHandler { private static final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; protected Log logger = LogFactory.getLog(getClass()); - private final Object webSocketHandler; - - private final Class webSocketHandlerClass; - private List supportedProtocols; - private AutowireCapableBeanFactory beanFactory; + private RequestUpgradeStrategy requestUpgradeStrategy; - public AbstractHandshakeHandler(Object handler) { - Assert.notNull(handler, "webSocketHandler is required"); - this.webSocketHandler = handler; - this.webSocketHandlerClass = null; + /** + * Default constructor that auto-detects and instantiates a + * {@link RequestUpgradeStrategy} suitable for the runtime container. + * + * @throws IllegalStateException if no {@link RequestUpgradeStrategy} can be found. + */ + public DefaultHandshakeHandler() { + this.requestUpgradeStrategy = new RequestUpgradeStrategyFactory().create(); } - public AbstractHandshakeHandler(Class handlerClass) { - Assert.notNull((handlerClass), "handlerClass is required"); - this.webSocketHandler = null; - this.webSocketHandlerClass = handlerClass; + /** + * A constructor that accepts a runtime specific {@link RequestUpgradeStrategy}. + * @param upgradeStrategy the upgrade strategy + */ + public DefaultHandshakeHandler(RequestUpgradeStrategy upgradeStrategy) { + this.requestUpgradeStrategy = upgradeStrategy; } public void setSupportedProtocols(String... protocols) { @@ -81,24 +87,13 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean } @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (beanFactory instanceof AutowireCapableBeanFactory) { - this.beanFactory = (AutowireCapableBeanFactory) beanFactory; - } - } - - protected Object getWebSocketHandler() { - if (this.webSocketHandlerClass != null) { - Assert.notNull(this.beanFactory, "BeanFactory is required for WebSocket handler instances per request."); - return this.beanFactory.createBean(this.webSocketHandlerClass); - } - else { - return this.webSocketHandler; - } + public void registerWebSocketHandlers(Collection handlers) { + this.requestUpgradeStrategy.registerWebSocketHandlers(handlers); } @Override - public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception { + public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler webSocketHandler) throws Exception { logger.debug("Starting handshake for " + request.getURI()); @@ -131,30 +126,29 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean return false; } - String protocol = selectProtocol(request.getHeaders().getSecWebSocketProtocol()); + String selectedProtocol = selectProtocol(request.getHeaders().getSecWebSocketProtocol()); // TODO: select extensions + logger.debug("Upgrading HTTP request"); + response.setStatusCode(HttpStatus.SWITCHING_PROTOCOLS); response.getHeaders().setUpgrade("WebSocket"); response.getHeaders().setConnection("Upgrade"); - response.getHeaders().setSecWebSocketProtocol(protocol); + response.getHeaders().setSecWebSocketProtocol(selectedProtocol); response.getHeaders().setSecWebSocketAccept(getWebSocketKeyHash(wsKey)); // TODO: response.getHeaders().setSecWebSocketExtensions(extensions); - logger.debug("Successfully negotiated WebSocket handshake"); + response.flush(); - // TODO: surely there is a better way to flush headers - response.getBody(); + if (logger.isTraceEnabled()) { + logger.trace("Upgrading with " + webSocketHandler); + } - doHandshakeInternal(request, response, protocol); + this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, webSocketHandler); return true; } - protected abstract void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, - String protocol) throws Exception; - - protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { logger.debug("Invalid Upgrade header " + request.getHeaders().getUpgrade()); response.setStatusCode(HttpStatus.BAD_REQUEST); @@ -178,7 +172,7 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean } protected String[] getSupportedVerions() { - return new String[] { "13" }; + return this.requestUpgradeStrategy.getSupportedVersions(); } protected void handleWebSocketVersionNotSupported(ServerHttpRequest request, ServerHttpResponse response) { @@ -216,4 +210,35 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Bean return DatatypeConverter.printBase64Binary(bytes); } + + private static class RequestUpgradeStrategyFactory { + + private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( + "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", DefaultHandshakeHandler.class.getClassLoader()); + + private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( + "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", DefaultHandshakeHandler.class.getClassLoader()); + + + private RequestUpgradeStrategy create() { + String className; + if (tomcatWebSocketPresent) { + className = "org.springframework.websocket.server.support.TomcatRequestUpgradeStrategy"; + } + else if (glassfishWebSocketPresent) { + className = "org.springframework.websocket.server.support.GlassfishRequestUpgradeStrategy"; + } + else { + throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName()); + } + try { + Class clazz = ClassUtils.forName(className, DefaultHandshakeHandler.class.getClassLoader()); + return (RequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor()); + } + catch (Throwable t) { + throw new IllegalStateException("Failed to instantiate " + className, t); + } + } + } + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index a8da4981ac..8222e7258e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -16,18 +16,39 @@ package org.springframework.websocket.server; +import java.util.Collection; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.WebSocketHandler; /** - * Abstraction for integrating a WebSocket implementation some HTTP processing pipeline. + * Contract for processing a WebSocket handshake request. * * @author Rossen Stoyanchev * @since 4.0 */ public interface HandshakeHandler { - boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response) throws Exception; + /** + * Pre-register {@link WebSocketHandler} instances so they can be adapted to the + * underlying runtime and hence re-used at runtime when + * {@link #doHandshake(ServerHttpRequest, ServerHttpResponse, WebSocketHandler) doHandshake} + * is called. + */ + void registerWebSocketHandlers(Collection webSocketHandlers); + + /** + * + * @param request the HTTP request + * @param response the HTTP response + * @param webSocketMessageHandler the handler to process WebSocket messages with + * @return a boolean indicating whether the handshake negotiation was successful + * + * @throws Exception + */ + boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) + throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java new file mode 100644 index 0000000000..d0cf29fa17 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -0,0 +1,57 @@ +/* + * 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; + +import java.util.Collection; + +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.WebSocketHandler; + + +/** + * A strategy for performing container-specific steps to upgrade an HTTP request during a + * WebSocket handshake. Intended for use within {@link HandshakeHandler} implementations. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface RequestUpgradeStrategy { + + /** + * Return the supported WebSocket protocol versions. + */ + String[] getSupportedVersions(); + + /** + * Pre-register {@link WebSocketHandler} instances so they can be adapted to the + * underlying runtime and hence re-used at runtime when + * {@link #upgrade(ServerHttpRequest, ServerHttpResponse, String, WebSocketHandler) + * upgrade} is called. + */ + void registerWebSocketHandlers(Collection webSocketHandlers); + + /** + * Perform runtime specific steps to complete the upgrade. + * Invoked only if the handshake is successful. + * + * @param webSocketHandler the handler for WebSocket messages + */ + void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, + WebSocketHandler webSocketHandler) throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 3c33a31017..d9e36a61fb 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -35,9 +35,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; 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.HandlerProvider; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; @@ -60,9 +58,7 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final String path; - private final Class endpointClass; - - private final Object endpointBean; + private final HandlerProvider endpointProvider; private List> encoders = new ArrayList>(); @@ -76,8 +72,6 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private Configurator configurator = new Configurator() {}; - private BeanFactory beanFactory; - /** * Class constructor with the {@code javax.webscoket.Endpoint} class. @@ -87,23 +81,19 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw * @param endpointClass */ public EndpointRegistration(String path, Class endpointClass) { - this(path, endpointClass, null); - } - - public EndpointRegistration(String path, Object bean) { - this(path, null, bean); - } - - public EndpointRegistration(String path, String beanName) { - this(path, null, beanName); - } - - private EndpointRegistration(String path, Class endpointClass, Object bean) { Assert.hasText(path, "path must not be empty"); - Assert.isTrue((endpointClass != null || bean != null), "Neither endpoint class nor endpoint bean provided"); + Assert.notNull(endpointClass, "endpointClass is required"); this.path = path; - this.endpointClass = endpointClass; - this.endpointBean = bean; + this.endpointProvider = new HandlerProvider(endpointClass); + this.endpointProvider.setLogger(logger); + } + + public EndpointRegistration(String path, Endpoint endpointBean) { + Assert.hasText(path, "path must not be empty"); + Assert.notNull(endpointBean, "endpointBean is required"); + this.path = path; + this.endpointProvider = new HandlerProvider(endpointBean); + this.endpointProvider.setLogger(logger); } @Override @@ -111,40 +101,13 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw return this.path; } - @SuppressWarnings("unchecked") @Override public Class getEndpointClass() { - if (this.endpointClass != null) { - return this.endpointClass; - } - Class beanClass = this.endpointBean.getClass(); - if (beanClass.equals(String.class)) { - beanClass = this.beanFactory.getType((String) this.endpointBean); - } - beanClass = ClassUtils.getUserClass(beanClass); - if (Endpoint.class.isAssignableFrom(beanClass)) { - return (Class) beanClass; - } - else { - throw new IllegalStateException("Invalid endpoint bean: must be of type ... TODO "); - } + return this.endpointProvider.getHandlerType(); } public Endpoint getEndpoint() { - if (this.endpointClass != null) { - WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); - if (wac == null) { - String message = "Failed to find the root WebApplicationContext. Was ContextLoaderListener not used?"; - logger.error(message); - throw new IllegalStateException(); - } - return wac.getAutowireCapableBeanFactory().createBean(this.endpointClass); - } - Object bean = this.endpointBean; - if (this.endpointBean instanceof String) { - bean = this.beanFactory.getBean((String) this.endpointBean); - } - return (Endpoint) bean; + return this.endpointProvider.getHandler(); } public void setSubprotocols(List subprotocols) { @@ -194,11 +157,6 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw return this.decoders; } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - /** * The {@link Configurator#getEndpointInstance(Class)} method is always ignored. */ @@ -233,4 +191,9 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw }; } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.endpointProvider.setBeanFactory(beanFactory); + } + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java index 6ed65012e6..1129cf6e66 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java @@ -64,10 +64,10 @@ public class SpringConfigurator extends Configurator { return beans.values().iterator().next(); } else { - // This should never happen .. + // Should not happen .. String message = "Found more than one matching @ServerEndpoint beans of type " + endpointClass; logger.error(message); - throw new IllegalStateException("Found more than one matching beans of type " + endpointClass); + throw new IllegalStateException(message); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java deleted file mode 100644 index 089a9f8efd..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/EndpointHandshakeHandler.java +++ /dev/null @@ -1,126 +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.handshake; - -import javax.websocket.Endpoint; - -import org.springframework.beans.BeanUtils; -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.AbstractHandshakeHandler; -import org.springframework.websocket.server.HandshakeHandler; - - -/** - * A {@link HandshakeHandler} for use with standard Java WebSocket runtimes. A - * container-specific {@link RequestUpgradeStrategy} is required since standard - * Java WebSocket currently does not provide any means of integrating a WebSocket - * handshake into an HTTP request processing pipeline. Currently available are - * implementations for Tomcat and Glassfish. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class EndpointHandshakeHandler extends AbstractHandshakeHandler { - - private final RequestUpgradeStrategy upgradeStrategy; - - - public EndpointHandshakeHandler(Endpoint endpoint) { - super(endpoint); - this.upgradeStrategy = createRequestUpgradeStrategy(); - } - - public EndpointHandshakeHandler(WebSocketHandler webSocketHandler) { - super(webSocketHandler); - this.upgradeStrategy = createRequestUpgradeStrategy(); - } - - public EndpointHandshakeHandler(Class handlerClass) { - super(handlerClass); - this.upgradeStrategy = createRequestUpgradeStrategy(); - } - - protected RequestUpgradeStrategy createRequestUpgradeStrategy() { - return new RequestUpgradeStrategyFactory().create(); - } - - @Override - protected String[] getSupportedVerions() { - return this.upgradeStrategy.getSupportedVersions(); - } - - @Override - public void doHandshakeInternal(ServerHttpRequest request, ServerHttpResponse response, String protocol) - throws Exception { - - logger.debug("Upgrading HTTP request"); - - Object webSocketHandler = getWebSocketHandler(); - - Endpoint endpoint; - if (webSocketHandler instanceof Endpoint) { - endpoint = (Endpoint) webSocketHandler; - } - else if (webSocketHandler instanceof WebSocketHandler) { - endpoint = new WebSocketHandlerEndpoint((WebSocketHandler) webSocketHandler); - } - else { - String className = webSocketHandler.getClass().getName(); - throw new IllegalArgumentException("Unexpected WebSocket handler type: " + className); - } - - this.upgradeStrategy.upgrade(request, response, protocol, endpoint); - } - - - private static class RequestUpgradeStrategyFactory { - - private static final String packageName = EndpointHandshakeHandler.class.getPackage().getName(); - - private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( - "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); - - private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( - "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", EndpointHandshakeHandler.class.getClassLoader()); - - - private RequestUpgradeStrategy create() { - String className; - if (tomcatWebSocketPresent) { - className = packageName + ".TomcatRequestUpgradeStrategy"; - } - else if (glassfishWebSocketPresent) { - className = packageName + ".GlassfishRequestUpgradeStrategy"; - } - else { - throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName()); - } - try { - Class clazz = ClassUtils.forName(className, EndpointHandshakeHandler.class.getClassLoader()); - return (RequestUpgradeStrategy) BeanUtils.instantiateClass(clazz.getConstructor()); - } - catch (Throwable t) { - throw new IllegalStateException("Failed to instantiate " + className, t); - } - } - } - -} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java deleted file mode 100644 index 8adf2395cb..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ - -/** - * WebSocket handshake support for use with standard Java WebSocket runtimes including - * container-specific strategies for upgrading the HttpServletRequest. - * - */ -package org.springframework.websocket.server.endpoint.handshake; - diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java index ce327f239e..34a043af9c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java @@ -6,7 +6,7 @@ * registering type-based endpoints, * {@link org.springframework.websocket.server.endpoint.SpringConfigurator} for * instantiating annotated endpoints through Spring, and - * {@link org.springframework.websocket.server.endpoint.handshake.EndpointHandshakeHandler} + * {@link org.springframework.websocket.server.support.EndpointHandshakeHandler} * for integrating endpoints into HTTP request processing. */ package org.springframework.websocket.server.endpoint; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java new file mode 100644 index 0000000000..1e7f1a2794 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -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.websocket.server.support; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.websocket.Endpoint; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; +import org.springframework.websocket.server.RequestUpgradeStrategy; + + +/** + * A {@link RequestUpgradeStrategy} that supports WebSocket handlers of type + * {@link WebSocketHandler} as well as {@link javax.websocket.Endpoint}. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeStrategy { + + protected final Log logger = LogFactory.getLog(getClass()); + + private final Map webSocketHandlers = new HashMap(); + + + @Override + public void registerWebSocketHandlers(Collection webSocketHandlers) { + for (WebSocketHandler webSocketHandler : webSocketHandlers) { + if (!this.webSocketHandlers.containsKey(webSocketHandler)) { + this.webSocketHandlers.put(webSocketHandler, adaptWebSocketHandler(webSocketHandler)); + } + } + } + + protected Endpoint adaptWebSocketHandler(WebSocketHandler handler) { + return new WebSocketHandlerEndpoint(handler); + } + + @Override + public void upgrade(ServerHttpRequest request, ServerHttpResponse response, + String protocol, WebSocketHandler webSocketHandler) throws Exception { + + Endpoint endpoint = this.webSocketHandlers.get(webSocketHandler); + if (endpoint == null) { + endpoint = adaptWebSocketHandler(webSocketHandler); + } + + if (logger.isTraceEnabled()) { + logger.trace("Upgrading with " + endpoint); + } + + upgradeInternal(request, response, protocol, endpoint); + } + + protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, + String protocol, Endpoint endpoint) throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java index 6a23883fb5..1a7619af45 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint.handshake; +package org.springframework.websocket.server.support; import java.lang.reflect.Constructor; import java.net.URI; @@ -54,7 +54,7 @@ import org.springframework.websocket.server.endpoint.EndpointRegistration; * @author Rossen Stoyanchev * @since 4.0 */ -public class GlassfishRequestUpgradeStrategy implements RequestUpgradeStrategy { +public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStrategy { private final static Random random = new Random(); @@ -65,8 +65,8 @@ public class GlassfishRequestUpgradeStrategy implements RequestUpgradeStrategy { } @Override - public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, - Endpoint endpoint) throws Exception { + public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, + String protocol, Endpoint endpoint) throws Exception { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java index b07cd2e0df..ac74c9e5f1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/handshake/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint.handshake; +package org.springframework.websocket.server.support; import java.io.IOException; import java.lang.reflect.Method; @@ -42,7 +42,7 @@ import org.springframework.websocket.server.endpoint.EndpointRegistration; * @author Rossen Stoyanchev * @since 4.0 */ -public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy { +public class TomcatRequestUpgradeStrategy extends AbstractEndpointUpgradeStrategy { @Override @@ -51,8 +51,8 @@ public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy { } @Override - public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String protocol, - Endpoint endpoint) throws IOException { + public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, + String protocol, Endpoint endpoint) throws IOException { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/HandshakeHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java similarity index 50% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/HandshakeHttpRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 26f5d59ae1..6e641eaba0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/HandshakeHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -17,11 +17,15 @@ package org.springframework.websocket.server.support; import java.io.IOException; +import java.util.Collections; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; @@ -29,24 +33,48 @@ import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.HandshakeHandler; +import org.springframework.websocket.server.DefaultHandshakeHandler; /** - * A Spring MVC {@link HttpRequestHandler} wrapping the invocation of a WebSocket - * {@link HandshakeHandler}; + * An {@link HttpRequestHandler} that wraps the invocation of a {@link HandshakeHandler}. * * @author Rossen Stoyanchev * @since 4.0 */ -public class HandshakeHttpRequestHandler implements HttpRequestHandler { +public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFactoryAware { - private final HandshakeHandler handshakeHandler; + private HandshakeHandler handshakeHandler; + + private final HandlerProvider handlerProvider; - public HandshakeHttpRequestHandler(HandshakeHandler handshakeHandler) { + public WebSocketHttpRequestHandler(WebSocketHandler webSocketHandler) { + Assert.notNull(webSocketHandler, "webSocketHandler is required"); + this.handlerProvider = new HandlerProvider(webSocketHandler); + this.handshakeHandler = new DefaultHandshakeHandler(); + this.handshakeHandler.registerWebSocketHandlers(Collections.singleton(webSocketHandler)); + } + + public WebSocketHttpRequestHandler( Class webSocketHandlerClass) { + Assert.notNull(webSocketHandlerClass, "webSocketHandlerClass is required"); + this.handlerProvider = new HandlerProvider(webSocketHandlerClass); + } + + public void setHandshakeHandler(HandshakeHandler handshakeHandler) { Assert.notNull(handshakeHandler, "handshakeHandler is required"); this.handshakeHandler = handshakeHandler; + if (this.handlerProvider.isSingleton()) { + this.handshakeHandler.registerWebSocketHandlers(Collections.singleton(this.handlerProvider.getHandler())); + } + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.handlerProvider.setBeanFactory(beanFactory); } @Override @@ -57,12 +85,16 @@ public class HandshakeHttpRequestHandler implements HttpRequestHandler { ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - this.handshakeHandler.doHandshake(httpRequest, httpResponse); + WebSocketHandler webSocketHandler = this.handlerProvider.getHandler(); + this.handshakeHandler.doHandshake(httpRequest, httpResponse, webSocketHandler); } catch (Exception e) { // TODO throw new NestedServletException("HandshakeHandler failure", e); } + finally { + httpResponse.flush(); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java index c76d943d9f..ac054317dd 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java @@ -1,6 +1,6 @@ /** - * Server-side support classes for WebSocket applications. + * Server-side support classes including container-specific strategies for upgrading a request. * */ package org.springframework.websocket.server.support; From f056f7e2adfcb8c8ad36c20a6b7744d86aa13d7e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 15 Apr 2013 15:28:06 -0400 Subject: [PATCH 17/51] Init and destroy internally created SockJS schedulers --- .../sockjs/server/AbstractSockJsService.java | 79 +++++++++++++++---- .../server/support/DefaultSockJsService.java | 30 +++---- .../support/TomcatRequestUpgradeStrategy.java | 3 +- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index e6823c0d4c..16740af556 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -25,6 +25,8 @@ import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -47,7 +49,8 @@ import org.springframework.web.util.UriUtils; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractSockJsService implements SockJsService, SockJsConfiguration { +public abstract class AbstractSockJsService + implements SockJsService, SockJsConfiguration, InitializingBean, DisposableBean { protected final Log logger = LogFactory.getLog(getClass()); @@ -64,12 +67,12 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf private long heartbeatTime = 25 * 1000; - private TaskScheduler heartbeatScheduler; - private long disconnectDelay = 5 * 1000; private boolean webSocketsEnabled = true; + private final TaskSchedulerHolder heartbeatSchedulerHolder; + /** * Class constructor... @@ -81,14 +84,14 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf public AbstractSockJsService(String prefix) { Assert.hasText(prefix, "prefix is required"); this.prefix = prefix; - this.heartbeatScheduler = createScheduler("SockJs-heartbeat-"); + this.heartbeatSchedulerHolder = new TaskSchedulerHolder("SockJs-heartbeat-"); } - protected TaskScheduler createScheduler(String threadNamePrefix) { - ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setThreadNamePrefix(threadNamePrefix); - scheduler.afterPropertiesSet(); - return scheduler; + public AbstractSockJsService(String prefix, TaskScheduler heartbeatScheduler) { + Assert.hasText(prefix, "prefix is required"); + Assert.notNull(heartbeatScheduler, "heartbeatScheduler is required"); + this.prefix = prefix; + this.heartbeatSchedulerHolder = new TaskSchedulerHolder(heartbeatScheduler); } /** @@ -163,12 +166,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf } public TaskScheduler getHeartbeatScheduler() { - return this.heartbeatScheduler; - } - - public void setHeartbeatScheduler(TaskScheduler heartbeatScheduler) { - Assert.notNull(heartbeatScheduler, "heartbeatScheduler is required"); - this.heartbeatScheduler = heartbeatScheduler; + return this.heartbeatSchedulerHolder.getScheduler(); } /** @@ -207,6 +205,15 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf return this.webSocketsEnabled; } + @Override + public void afterPropertiesSet() throws Exception { + this.heartbeatSchedulerHolder.initialize(); + } + + @Override + public void destroy() throws Exception { + this.heartbeatSchedulerHolder.destroy(); + } /** * TODO @@ -426,4 +433,46 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf } }; + + /** + * Holds an externally provided or an internally managed TaskScheduler. Provides + * initialize and destroy methods have no effect if the scheduler is externally + * managed. + */ + protected static class TaskSchedulerHolder { + + private final TaskScheduler taskScheduler; + + private final boolean isDefaultTaskScheduler; + + public TaskSchedulerHolder(TaskScheduler taskScheduler) { + Assert.notNull(taskScheduler, "taskScheduler is required"); + this.taskScheduler = taskScheduler; + this.isDefaultTaskScheduler = false; + } + + public TaskSchedulerHolder(String threadNamePrefix) { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setThreadNamePrefix(threadNamePrefix); + this.taskScheduler = scheduler; + this.isDefaultTaskScheduler = true; + } + + public TaskScheduler getScheduler() { + return this.taskScheduler; + } + + public void initialize() { + if (this.isDefaultTaskScheduler) { + ((ThreadPoolTaskScheduler) this.taskScheduler).afterPropertiesSet(); + } + } + + public void destroy() { + if (this.isDefaultTaskScheduler) { + ((ThreadPoolTaskScheduler) this.taskScheduler).shutdown(); + } + } + } + } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index af0789aa02..b0f311ec64 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -29,7 +29,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.SockJsSessionSupport; @@ -64,7 +63,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi private final Map transportHandlerOverrides = new HashMap(); - private TaskScheduler sessionTimeoutScheduler; + private TaskSchedulerHolder sessionTimeoutSchedulerHolder; private final Map sessions = new ConcurrentHashMap(); @@ -73,21 +72,13 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi public DefaultSockJsService(String prefix) { super(prefix); - this.sessionTimeoutScheduler = createScheduler("SockJs-sessionTimeout-"); + this.sessionTimeoutSchedulerHolder = new TaskSchedulerHolder("SockJs-sessionTimeout-"); } - /** - * A scheduler instance to use for scheduling periodic expires session cleanup. - *

- * By default a {@link ThreadPoolTaskScheduler} with default settings is used. - */ - public TaskScheduler getSessionTimeoutScheduler() { - return this.sessionTimeoutScheduler; - } - - public void setSessionTimeoutScheduler(TaskScheduler sessionTimeoutScheduler) { + public DefaultSockJsService(String prefix, TaskScheduler heartbeatScheduler, TaskScheduler sessionTimeoutScheduler) { + super(prefix, heartbeatScheduler); Assert.notNull(sessionTimeoutScheduler, "sessionTimeoutScheduler is required"); - this.sessionTimeoutScheduler = sessionTimeoutScheduler; + this.sessionTimeoutSchedulerHolder = new TaskSchedulerHolder(sessionTimeoutScheduler); } public void setTransportHandlers(TransportHandler... handlers) { @@ -124,6 +115,8 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi @Override public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + if (this.transportHandlers.isEmpty()) { if (isWebSocketEnabled() && (this.transportHandlerOverrides.get(TransportType.WEBSOCKET) == null)) { this.transportHandlers.put(TransportType.WEBSOCKET, @@ -146,7 +139,9 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi configureTransportHandlers(); - this.sessionTimeoutScheduler.scheduleAtFixedRate(new Runnable() { + this.sessionTimeoutSchedulerHolder.initialize(); + + this.sessionTimeoutSchedulerHolder.getScheduler().scheduleAtFixedRate(new Runnable() { public void run() { try { int count = sessions.size(); @@ -173,6 +168,11 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi }, getDisconnectDelay()); } + @Override + public void destroy() throws Exception { + super.destroy(); + this.sessionTimeoutSchedulerHolder.destroy(); + } private void configureTransportHandlers() { for (TransportHandler h : this.transportHandlers.values()) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java index ac74c9e5f1..5b2f3a2575 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java @@ -30,7 +30,6 @@ import org.apache.tomcat.websocket.server.WsServerContainer; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.sockjs.server.NestedSockJsRuntimeException; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.websocket.server.endpoint.EndpointRegistration; @@ -66,7 +65,7 @@ public class TomcatRequestUpgradeStrategy extends AbstractEndpointUpgradeStrateg method.invoke(webSocketRequest); } catch (Exception ex) { - throw new NestedSockJsRuntimeException("Failed to upgrade HttpServletRequest", ex); + throw new IllegalStateException("Failed to upgrade HttpServletRequest", ex); } // TODO: use ServletContext attribute when Tomcat is updated From 2794224b28430190c53c7cd284ccfe33c8eb5d1c Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 17 Apr 2013 11:19:28 -0400 Subject: [PATCH 18/51] Add onClosed to SockJsSessionSupport sub-classes As opposed to close(), which actively closes the session, the onClosed method is called when the underlying connection has been closed or disconnected. --- .../springframework/sockjs/SockJsSession.java | 2 + .../sockjs/SockJsSessionSupport.java | 5 + .../sockjs/server/AbstractServerSession.java | 12 +- .../sockjs/server/AbstractSockJsService.java | 33 ++---- .../sockjs/server/SockJsService.java | 2 - .../server/support/DefaultSockJsService.java | 14 +-- .../support/SockJsHttpRequestHandler.java | 42 +++++-- .../transport/AbstractHttpServerSession.java | 7 ++ .../AbstractSockJsWebSocketHandler.java | 108 ------------------ .../transport/SockJsWebSocketHandler.java | 75 ++++++++++-- .../WebSocketSockJsHandlerAdapter.java | 81 ++++++++++++- .../websocket/WebSocketSession.java | 2 + .../endpoint/StandardWebSocketSession.java | 5 + .../endpoint/WebSocketHandlerEndpoint.java | 47 ++++---- 14 files changed, 253 insertions(+), 182 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java index 0dab8e17f5..e7cd75bf4b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java @@ -27,6 +27,8 @@ import java.io.IOException; */ public interface SockJsSession { + String getId(); + void sendMessage(String text) throws IOException; void close(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java index 8c9747dc0a..0dac36118d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java @@ -114,6 +114,11 @@ public abstract class SockJsSessionSupport implements SockJsSession { this.sockJsHandler.handleException(this, ex); } + public void connectionClosed() { + this.state = State.CLOSED; + this.sockJsHandler.sessionClosed(this); + } + public void close() { this.state = State.CLOSED; this.sockJsHandler.sessionClosed(this); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java index caa436512b..43473eb997 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java @@ -58,10 +58,18 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { protected abstract void sendMessageInternal(String message) throws IOException; + + @Override + public void connectionClosed() { + logger.debug("Session closed"); + super.close(); + cancelHeartbeat(); + } + + @Override public final synchronized void close() { if (!isClosed()) { logger.debug("Closing session"); - if (isActive()) { // deliver messages "in flight" before sending close frame try { @@ -71,9 +79,7 @@ public abstract class AbstractServerSession extends SockJsSessionSupport { // ignore } } - super.close(); - cancelHeartbeat(); closeInternal(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 16740af556..399bf5d5d6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -40,7 +40,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.DigestUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import org.springframework.web.util.UriUtils; /** @@ -57,7 +56,7 @@ public abstract class AbstractSockJsService private static final int ONE_YEAR = 365 * 24 * 60 * 60; - private final String prefix; + private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js"; @@ -74,31 +73,25 @@ public abstract class AbstractSockJsService private final TaskSchedulerHolder heartbeatSchedulerHolder; - /** - * Class constructor... - * - * @param prefix the path prefix for the SockJS service. All requests with a path - * that begins with the specified prefix will be handled by this service. In a - * Servlet container this is the path within the current servlet mapping. - */ - public AbstractSockJsService(String prefix) { - Assert.hasText(prefix, "prefix is required"); - this.prefix = prefix; + + public AbstractSockJsService() { this.heartbeatSchedulerHolder = new TaskSchedulerHolder("SockJs-heartbeat-"); } - public AbstractSockJsService(String prefix, TaskScheduler heartbeatScheduler) { - Assert.hasText(prefix, "prefix is required"); + public AbstractSockJsService(TaskScheduler heartbeatScheduler) { Assert.notNull(heartbeatScheduler, "heartbeatScheduler is required"); - this.prefix = prefix; this.heartbeatSchedulerHolder = new TaskSchedulerHolder(heartbeatScheduler); } /** - * The path prefix to which the SockJS service is mapped. + * A unique name for the service mainly for logging purposes. */ - public String getPrefix() { - return this.prefix; + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; } /** @@ -236,10 +229,6 @@ public abstract class AbstractSockJsService // Ignore invalid Content-Type (TODO) } - String path = UriUtils.decode(request.getURI().getPath(), "URF-8"); - int index = path.indexOf(this.prefix); - sockJsPath = path.substring(index + this.prefix.length()); - try { if (sockJsPath.equals("") || sockJsPath.equals("/")) { response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index dfb1e002d4..23c79be7d2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -31,8 +31,6 @@ import org.springframework.websocket.WebSocketHandler; */ public interface SockJsService { - String getPrefix(); - /** * Pre-register {@link SockJsHandler} instances so they can be adapted to * {@link WebSocketHandler} and hence re-used at runtime when diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index b0f311ec64..60f8143f70 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -70,13 +70,11 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi private final Map sockJsHandlers = new HashMap(); - public DefaultSockJsService(String prefix) { - super(prefix); + public DefaultSockJsService() { this.sessionTimeoutSchedulerHolder = new TaskSchedulerHolder("SockJs-sessionTimeout-"); } - public DefaultSockJsService(String prefix, TaskScheduler heartbeatScheduler, TaskScheduler sessionTimeoutScheduler) { - super(prefix, heartbeatScheduler); + public DefaultSockJsService(TaskScheduler heartbeatScheduler, TaskScheduler sessionTimeoutScheduler) { Assert.notNull(sessionTimeoutScheduler, "sessionTimeoutScheduler is required"); this.sessionTimeoutSchedulerHolder = new TaskSchedulerHolder(sessionTimeoutScheduler); } @@ -146,23 +144,23 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi try { int count = sessions.size(); if (logger.isTraceEnabled() && (count != 0)) { - logger.trace("Checking " + count + " session(s) for timeouts [" + getPrefix() + "]"); + logger.trace("Checking " + count + " session(s) for timeouts [" + getName() + "]"); } for (SockJsSessionSupport session : sessions.values()) { if (session.getTimeSinceLastActive() > getDisconnectDelay()) { if (logger.isTraceEnabled()) { - logger.trace("Removing " + session + " for [" + getPrefix() + "]"); + logger.trace("Removing " + session + " for [" + getName() + "]"); } session.close(); sessions.remove(session.getId()); } } if (logger.isTraceEnabled() && (count != 0)) { - logger.trace(sessions.size() + " remaining session(s) [" + getPrefix() + "]"); + logger.trace(sessions.size() + " remaining session(s) [" + getName() + "]"); } } catch (Throwable t) { - logger.error("Failed to complete session timeout checks for [" + getPrefix() + "]", t); + logger.error("Failed to complete session timeout checks for [" + getName() + "]", t); } } }, getDisconnectDelay()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index 977f9eb8b9..22d058e0eb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -46,6 +46,8 @@ import org.springframework.websocket.HandlerProvider; */ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactoryAware { + private final String prefix; + private final SockJsService sockJsService; private final HandlerProvider handlerProvider; @@ -53,23 +55,50 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory private final UrlPathHelper urlPathHelper = new UrlPathHelper(); - public SockJsHttpRequestHandler(SockJsService sockJsService, SockJsHandler sockJsHandler) { + /** + * Class constructor with {@link SockJsHandler} instance ... + * + * @param prefix the path prefix for the SockJS service. All requests with a path + * that begins with the specified prefix will be handled by this service. In a + * Servlet container this is the path within the current servlet mapping. + */ + public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, SockJsHandler sockJsHandler) { + + Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); Assert.notNull(sockJsHandler, "sockJsHandler is required"); + + this.prefix = prefix; this.sockJsService = sockJsService; this.sockJsService.registerSockJsHandlers(Collections.singleton(sockJsHandler)); this.handlerProvider = new HandlerProvider(sockJsHandler); } - public SockJsHttpRequestHandler(SockJsService sockJsService, Class sockJsHandlerClass) { + /** + * Class constructor with {@link SockJsHandler} type (per request) ... + * + * @param prefix the path prefix for the SockJS service. All requests with a path + * that begins with the specified prefix will be handled by this service. In a + * Servlet container this is the path within the current servlet mapping. + */ + public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, + Class sockJsHandlerClass) { + + Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); Assert.notNull(sockJsHandlerClass, "sockJsHandlerClass is required"); + + this.prefix = prefix; this.sockJsService = sockJsService; this.handlerProvider = new HandlerProvider(sockJsHandlerClass); } - public String getMappingPattern() { - return this.sockJsService.getPrefix() + "/**"; + public String getPrefix() { + return this.prefix; + } + + public String getPattern() { + return this.prefix + "/**"; } @Override @@ -82,10 +111,9 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory throws ServletException, IOException { String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); - String prefix = this.sockJsService.getPrefix(); - Assert.isTrue(lookupPath.startsWith(prefix), - "Request path does not match the prefix of the SockJsService " + prefix); + Assert.isTrue(lookupPath.startsWith(this.prefix), + "Request path does not match the prefix of the SockJsService " + this.prefix); String sockJsPath = lookupPath.substring(prefix.length()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java index e03fb4a4c9..47efdea55b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java @@ -108,6 +108,13 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { */ protected abstract void flushCache() throws IOException; + @Override + public void connectionClosed() { + super.connectionClosed(); + resetRequest(); + } + + @Override protected void closeInternal() { resetRequest(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java deleted file mode 100644 index 2f723092fe..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractSockJsWebSocketHandler.java +++ /dev/null @@ -1,108 +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 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.SockJsHandler; -import org.springframework.sockjs.SockJsSessionSupport; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.util.Assert; -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 SockJsHandler sockJsHandler; - - private final Map sessions = - new ConcurrentHashMap(); - - - public AbstractSockJsWebSocketHandler(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - Assert.notNull(sockJsConfig, "sockJsConfig is required"); - Assert.notNull(sockJsHandler, "sockJsHandler is required"); - this.sockJsConfig = sockJsConfig; - this.sockJsHandler = sockJsHandler; - } - - protected SockJsConfiguration getSockJsConfig() { - return this.sockJsConfig; - } - - protected SockJsHandler getSockJsHandler() { - return this.sockJsHandler; - } - - 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); - } - - 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(); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index f90fbdb13e..2da57cbf35 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -17,12 +17,18 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; +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.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.Assert; import org.springframework.util.StringUtils; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -37,19 +43,47 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler { +public class SockJsWebSocketHandler implements WebSocketHandler { + + private static final Log logger = LogFactory.getLog(SockJsWebSocketHandler.class); + + private final SockJsConfiguration sockJsConfig; + + private final SockJsHandler sockJsHandler; + + private final Map sessions = + new ConcurrentHashMap(); // TODO: JSON library used must be configurable private final ObjectMapper objectMapper = new ObjectMapper(); public SockJsWebSocketHandler(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - super(sockJsConfig, sockJsHandler); + Assert.notNull(sockJsConfig, "sockJsConfig is required"); + Assert.notNull(sockJsHandler, "sockJsHandler is required"); + this.sockJsConfig = sockJsConfig; + this.sockJsHandler = sockJsHandler; + } + + protected SockJsConfiguration getSockJsConfig() { + return this.sockJsConfig; + } + + protected SockJsHandler getSockJsHandler() { + return this.sockJsHandler; + } + + protected SockJsSessionSupport getSockJsSession(WebSocketSession wsSession) { + return this.sessions.get(wsSession); } @Override - protected SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) throws Exception { - return new WebSocketServerSession(wsSession, getSockJsConfig()); + public void newSession(WebSocketSession wsSession) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("New session: " + wsSession); + } + SockJsSessionSupport session = new WebSocketServerSession(wsSession, getSockJsConfig()); + this.sessions.put(wsSession, session); } @Override @@ -72,6 +106,25 @@ public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler { } } + @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 session closed " + webSocketSession); + SockJsSessionSupport session = this.sessions.remove(webSocketSession); + session.connectionClosed(); + } + private class WebSocketServerSession extends AbstractServerSession { @@ -107,15 +160,23 @@ public class SockJsWebSocketHandler extends AbstractSockJsWebSocketHandler { } @Override - public void closeInternal() { - this.webSocketSession.close(); + public void connectionClosed() { + super.connectionClosed(); this.webSocketSession = null; + } + + @Override + public void closeInternal() { + deactivate(); updateLastActiveTime(); } @Override protected void deactivate() { - this.webSocketSession.close(); + if (this.webSocketSession != null) { + this.webSocketSession.close(); + this.webSocketSession = null; + } } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java index 9ec2a9e386..1c266860e0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java @@ -17,10 +17,16 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; +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.SockJsHandler; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.util.Assert; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -33,22 +39,78 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketSockJsHandlerAdapter extends AbstractSockJsWebSocketHandler { +public class WebSocketSockJsHandlerAdapter implements WebSocketHandler { + + private static final Log logger = LogFactory.getLog(WebSocketSockJsHandlerAdapter.class); + + private final SockJsConfiguration sockJsConfig; + + private final SockJsHandler sockJsHandler; + + private final Map sessions = + new ConcurrentHashMap(); public WebSocketSockJsHandlerAdapter(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - super(sockJsConfig, sockJsHandler); + Assert.notNull(sockJsConfig, "sockJsConfig is required"); + Assert.notNull(sockJsHandler, "sockJsHandler is required"); + this.sockJsConfig = sockJsConfig; + this.sockJsHandler = sockJsHandler; + } + + protected SockJsConfiguration getSockJsConfig() { + return this.sockJsConfig; + } + + protected SockJsHandler getSockJsHandler() { + return this.sockJsHandler; + } + + protected SockJsSessionSupport getSockJsSession(WebSocketSession wsSession) { + return this.sessions.get(wsSession); } @Override - protected SockJsSessionSupport createSockJsSession(WebSocketSession wsSession) throws Exception { - return new WebSocketSessionAdapter(wsSession); + public void newSession(WebSocketSession wsSession) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("New session: " + wsSession); + } + SockJsSessionSupport session = new WebSocketSessionAdapter(wsSession); + this.sessions.put(wsSession, session); + } + + @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 session closed " + webSocketSession); + SockJsSessionSupport session = this.sessions.remove(webSocketSession); + session.connectionClosed(); } private class WebSocketSessionAdapter extends SockJsSessionSupport { - private final WebSocketSession wsSession; + private WebSocketSession wsSession; public WebSocketSessionAdapter(WebSocketSession wsSession) throws Exception { @@ -67,11 +129,20 @@ public class WebSocketSockJsHandlerAdapter extends AbstractSockJsWebSocketHandle this.wsSession.sendText(message); } + @Override + public void connectionClosed() { + logger.debug("Session closed"); + super.connectionClosed(); + this.wsSession = null; + } + + @Override public void close() { if (!isClosed()) { logger.debug("Closing session"); super.close(); this.wsSession.close(); + this.wsSession = null; } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index 2abe644a5a..a9af7d7d7b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -27,6 +27,8 @@ import java.io.IOException; */ public interface WebSocketSession { + String getId(); + boolean isOpen(); void sendText(String text) throws IOException; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index dc4126b168..546f16c96e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -40,6 +40,11 @@ public class StandardWebSocketSession implements WebSocketSession { this.session = session; } + @Override + public String getId() { + return this.session.getId(); + } + @Override public boolean isOpen() { return ((this.session != null) && this.session.isOpen()); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index b70076b3d8..7680f24ef3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -58,7 +58,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { try { WebSocketSession webSocketSession = new StandardWebSocketSession(session); this.sessions.put(session.getId(), webSocketSession); - session.addMessageHandler(new StandardMessageHandler(session.getId())); + session.addMessageHandler(new StandardMessageHandler(session)); this.webSocketHandler.newSession(webSocketSession); } catch (Throwable ex) { @@ -69,16 +69,19 @@ public class WebSocketHandlerEndpoint extends Endpoint { @Override public void onClose(javax.websocket.Session session, CloseReason closeReason) { - String id = session.getId(); if (logger.isDebugEnabled()) { - logger.debug("Closing session: " + session + ", " + closeReason); + logger.debug("Session closed: " + session + ", " + closeReason); } try { - WebSocketSession webSocketSession = getSession(id); - this.sessions.remove(id); - int code = closeReason.getCloseCode().getCode(); - String reason = closeReason.getReasonPhrase(); - this.webSocketHandler.sessionClosed(webSocketSession, code, reason); + WebSocketSession wsSession = this.sessions.remove(session.getId()); + if (wsSession != null) { + int code = closeReason.getCloseCode().getCode(); + String reason = closeReason.getReasonPhrase(); + this.webSocketHandler.sessionClosed(wsSession, code, reason); + } + else { + Assert.notNull(wsSession, "No WebSocket session"); + } } catch (Throwable ex) { // TODO @@ -90,8 +93,13 @@ public class WebSocketHandlerEndpoint extends Endpoint { public void onError(javax.websocket.Session session, Throwable exception) { logger.error("Error for WebSocket session: " + session.getId(), exception); try { - WebSocketSession webSocketSession = getSession(session.getId()); - this.webSocketHandler.handleException(webSocketSession, exception); + WebSocketSession wsSession = getWebSocketSession(session); + if (wsSession != null) { + this.webSocketHandler.handleException(wsSession, exception); + } + else { + logger.warn("WebSocketSession not found. Perhaps onError was called after onClose?"); + } } catch (Throwable ex) { // TODO @@ -99,29 +107,28 @@ public class WebSocketHandlerEndpoint extends Endpoint { } } - private WebSocketSession getSession(String sourceSessionId) { - WebSocketSession webSocketSession = this.sessions.get(sourceSessionId); - Assert.notNull(webSocketSession, "No session"); - return webSocketSession; + private WebSocketSession getWebSocketSession(javax.websocket.Session session) { + return this.sessions.get(session.getId()); } private class StandardMessageHandler implements MessageHandler.Whole { - private final String sessionId; + private final javax.websocket.Session session; - public StandardMessageHandler(String sessionId) { - this.sessionId = sessionId; + public StandardMessageHandler(javax.websocket.Session session) { + this.session = session; } @Override public void onMessage(String message) { if (logger.isTraceEnabled()) { - logger.trace("Message for session [" + this.sessionId + "]: " + message); + logger.trace("Message for session [" + this.session + "]: " + message); } + WebSocketSession wsSession = getWebSocketSession(this.session); + Assert.notNull(wsSession, "WebSocketSession not found"); try { - WebSocketSession session = getSession(this.sessionId); - WebSocketHandlerEndpoint.this.webSocketHandler.handleTextMessage(session, message); + WebSocketHandlerEndpoint.this.webSocketHandler.handleTextMessage(wsSession, message); } catch (Throwable ex) { // TODO From 6273fc41f1426a398fdcd039dc6e59113536876b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 17 Apr 2013 11:26:20 -0400 Subject: [PATCH 19/51] Rename SockJS session type to include "SockJs" --- ...erverSession.java => AbstractServerSockJsSession.java} | 4 ++-- .../transport/AbstractHttpSendingTransportHandler.java | 6 +++--- ...rSession.java => AbstractHttpServerSockJsSession.java} | 6 +++--- .../transport/AbstractStreamingTransportHandler.java | 8 ++++---- .../sockjs/server/transport/HtmlFileTransportHandler.java | 2 +- .../server/transport/JsonpPollingTransportHandler.java | 6 +++--- ...ServerSession.java => PollingServerSockJsSession.java} | 4 ++-- .../sockjs/server/transport/SockJsWebSocketHandler.java | 8 ++++---- ...rverSession.java => StreamingServerSockJsSession.java} | 4 ++-- .../server/transport/WebSocketSockJsHandlerAdapter.java | 6 +++--- .../server/transport/XhrPollingTransportHandler.java | 4 ++-- 11 files changed, 29 insertions(+), 29 deletions(-) rename spring-websocket/src/main/java/org/springframework/sockjs/server/{AbstractServerSession.java => AbstractServerSockJsSession.java} (95%) rename spring-websocket/src/main/java/org/springframework/sockjs/server/transport/{AbstractHttpServerSession.java => AbstractHttpServerSockJsSession.java} (93%) rename spring-websocket/src/main/java/org/springframework/sockjs/server/transport/{PollingHttpServerSession.java => PollingServerSockJsSession.java} (86%) rename spring-websocket/src/main/java/org/springframework/sockjs/server/transport/{StreamingHttpServerSession.java => StreamingServerSockJsSession.java} (90%) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 43473eb997..54be439dde 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -35,14 +35,14 @@ import org.springframework.util.Assert; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractServerSession extends SockJsSessionSupport { +public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { private final SockJsConfiguration sockJsConfig; private ScheduledFuture heartbeatTask; - public AbstractServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + public AbstractServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { super(sessionId, sockJsHandler); this.sockJsConfig = sockJsConfig; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index aff6cea280..d917ef035b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -66,12 +66,12 @@ public abstract class AbstractHttpSendingTransportHandler // Set content type before writing response.getHeaders().setContentType(getContentType()); - AbstractHttpServerSession httpServerSession = (AbstractHttpServerSession) session; + AbstractHttpServerSockJsSession httpServerSession = (AbstractHttpServerSockJsSession) session; handleRequestInternal(request, response, httpServerSession); } protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSession httpServerSession) throws Exception, IOException { + AbstractHttpServerSockJsSession httpServerSession) throws Exception, IOException { if (httpServerSession.isNew()) { handleNewSession(request, response, httpServerSession); @@ -87,7 +87,7 @@ public abstract class AbstractHttpSendingTransportHandler } protected void handleNewSession(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSession session) throws Exception { + AbstractHttpServerSockJsSession session) throws Exception { logger.debug("Opening " + getTransportType() + " connection"); session.setFrameFormat(getFrameFormat(request)); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 47efdea55b..b71b4287a9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -23,7 +23,7 @@ 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.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; @@ -36,7 +36,7 @@ import org.springframework.util.Assert; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractHttpServerSession extends AbstractServerSession { +public abstract class AbstractHttpServerSockJsSession extends AbstractServerSockJsSession { private FrameFormat frameFormat; @@ -47,7 +47,7 @@ public abstract class AbstractHttpServerSession extends AbstractServerSession { private ServerHttpResponse response; - public AbstractHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { super(sessionId, sockJsConfig, sockJsHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java index 5aa8eb11be..f8dc637c16 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -33,14 +33,14 @@ public abstract class AbstractStreamingTransportHandler extends AbstractHttpSend @Override - public StreamingHttpServerSession createSession(String sessionId, SockJsHandler sockJsHandler) { + public StreamingServerSockJsSession createSession(String sessionId, SockJsHandler sockJsHandler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new StreamingHttpServerSession(sessionId, getSockJsConfig(), sockJsHandler); + return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), sockJsHandler); } @Override public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSession session) throws Exception { + AbstractHttpServerSockJsSession session) throws Exception { writePrelude(request, response); super.handleRequestInternal(request, response, session); @@ -51,7 +51,7 @@ public abstract class AbstractStreamingTransportHandler extends AbstractHttpSend @Override protected void handleNewSession(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSession session) throws IOException, Exception { + AbstractHttpServerSockJsSession session) throws IOException, Exception { super.handleNewSession(request, response, session); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index d6568fa797..eb752cd302 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -78,7 +78,7 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler @Override public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSession session) throws Exception { + AbstractHttpServerSockJsSession session) throws Exception { String callback = request.getQueryParams().getFirst("c"); if (! StringUtils.hasText(callback)) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index a00c5de6b7..23c9f07efb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -50,14 +50,14 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingHttpServerSession createSession(String sessionId, SockJsHandler sockJsHandler) { + public PollingServerSockJsSession createSession(String sessionId, SockJsHandler sockJsHandler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new PollingHttpServerSession(sessionId, getSockJsConfig(), sockJsHandler); + return new PollingServerSockJsSession(sessionId, getSockJsConfig(), sockJsHandler); } @Override public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSession session) throws Exception { + AbstractHttpServerSockJsSession session) throws Exception { String callback = request.getQueryParams().getFirst("c"); if (! StringUtils.hasText(callback)) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java similarity index 86% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index d48fa31a2c..baac23d587 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -22,9 +22,9 @@ import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; -public class PollingHttpServerSession extends AbstractHttpServerSession { +public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { - public PollingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + public PollingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { super(sessionId, sockJsConfig, sockJsHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 2da57cbf35..c23863f901 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -25,7 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSessionSupport; -import org.springframework.sockjs.server.AbstractServerSession; +import org.springframework.sockjs.server.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.Assert; @@ -82,7 +82,7 @@ public class SockJsWebSocketHandler implements WebSocketHandler { if (logger.isDebugEnabled()) { logger.debug("New session: " + wsSession); } - SockJsSessionSupport session = new WebSocketServerSession(wsSession, getSockJsConfig()); + SockJsSessionSupport session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); this.sessions.put(wsSession, session); } @@ -126,12 +126,12 @@ public class SockJsWebSocketHandler implements WebSocketHandler { } - private class WebSocketServerSession extends AbstractServerSession { + private class WebSocketServerSockJsSession extends AbstractServerSockJsSession { private WebSocketSession webSocketSession; - public WebSocketServerSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) throws Exception { + public WebSocketServerSockJsSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) throws Exception { super(String.valueOf(wsSession.hashCode()), sockJsConfig, getSockJsHandler()); this.webSocketSession = wsSession; this.webSocketSession.sendText(SockJsFrame.openFrame().getContent()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index 71bdf2d9dd..e0293a7372 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingHttpServerSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -23,12 +23,12 @@ import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; -public class StreamingHttpServerSession extends AbstractHttpServerSession { +public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSession { private int byteCount; - public StreamingHttpServerSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + public StreamingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { super(sessionId, sockJsConfig, sockJsHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java index 1c266860e0..560335693c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java @@ -75,7 +75,7 @@ public class WebSocketSockJsHandlerAdapter implements WebSocketHandler { if (logger.isDebugEnabled()) { logger.debug("New session: " + wsSession); } - SockJsSessionSupport session = new WebSocketSessionAdapter(wsSession); + SockJsSessionSupport session = new SockJsWebSocketSessionAdapter(wsSession); this.sessions.put(wsSession, session); } @@ -108,12 +108,12 @@ public class WebSocketSockJsHandlerAdapter implements WebSocketHandler { } - private class WebSocketSessionAdapter extends SockJsSessionSupport { + private class SockJsWebSocketSessionAdapter extends SockJsSessionSupport { private WebSocketSession wsSession; - public WebSocketSessionAdapter(WebSocketSession wsSession) throws Exception { + public SockJsWebSocketSessionAdapter(WebSocketSession wsSession) throws Exception { super(String.valueOf(wsSession.hashCode()), getSockJsHandler()); this.wsSession = wsSession; connectionInitialized(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index 7655e42948..e424bac91a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -50,9 +50,9 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingHttpServerSession createSession(String sessionId, SockJsHandler sockJsHandler) { + public PollingServerSockJsSession createSession(String sessionId, SockJsHandler sockJsHandler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new PollingHttpServerSession(sessionId, getSockJsConfig(), sockJsHandler); + return new PollingServerSockJsSession(sessionId, getSockJsConfig(), sockJsHandler); } } From d475e166047c01e78297a522100be58f027659b2 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 17 Apr 2013 14:34:33 -0400 Subject: [PATCH 20/51] Modify build version for branch CI snapshots --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2db7ae75f2..712344802e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=4.0.0.BUILD-SNAPSHOT +version=4.0.0.WEBSOCKET-SNAPSHOT From 967eae0efb148a6d499f639ea182f83058f08ec8 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 17 Apr 2013 16:36:51 -0400 Subject: [PATCH 21/51] Add README-WEBSOCKET.md --- README-WEBSOCKET.md | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 README-WEBSOCKET.md diff --git a/README-WEBSOCKET.md b/README-WEBSOCKET.md new file mode 100644 index 0000000000..3ccfdb599c --- /dev/null +++ b/README-WEBSOCKET.md @@ -0,0 +1,72 @@ + +## Maven Snapshots + +Maven snapshots of this branch are available through the Spring snapshot repository: + + + spring-snapshots + http://repo.springsource.org/snapshot + true + false + + +Use version `4.0.0.WEBSOCKET-SNAPSHOT`, for example: + + + org.springframework + spring-context + 4.0.0.WEBSOCKET-SNAPSHOT + + + org.springframework + spring-web + 4.0.0.WEBSOCKET-SNAPSHOT + + + org.springframework + spring-websocket + 4.0.0.WEBSOCKET-SNAPSHOT + + + + +### Tomcat + +Tomcat provides early JSR-356 support. You'll need to build the latest source, which is relatively easy to do. + +Check out Tomcat trunk: + mkdir tomcat + cd tomcat + svn co http://svn.apache.org/repos/asf/tomcat/trunk/ + cd trunk + +Create `build.properties` in the trunk directory with content similar to the one below: + # ----- Default Base Path for Dependent Packages ----- + # Replace this path with the path where dependencies binaries should be downloaded + base.path=~/dev/sources/apache/tomcat/download + +Run the ant build: + ant clean + ant + +A usable Tomcat installation can be found in `output/build` + +### Glassfish + +Glassfish also provides JSR-356 support based on Tyrus (the reference implementation). + +Download a [Glassfish 4 build](http://dlc.sun.com.edgesuite.net/glassfish/4.0/) (e.g. glassfish-4.0-b84.zip from the promoted builds) + +Unzip the downloaded file. + +Start the server: + cd /glassfish4 + bin/asadmin start-domain + +Deploy a WAR file. Here is [a sample script](https://github.com/rstoyanchev/spring-websocket-test/blob/master/redeploy-glassfish.sh). + +Watch the logs: + cd /glassfish4 + less `glassfish/domains/domain1/logs/server.log` + + From c810d9531606b30ac14fe4ab4a89e7aa819764ea Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 17 Apr 2013 16:39:44 -0400 Subject: [PATCH 22/51] Update README-WEBSOCKET.md --- README-WEBSOCKET.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README-WEBSOCKET.md b/README-WEBSOCKET.md index 3ccfdb599c..5eb04d08cb 100644 --- a/README-WEBSOCKET.md +++ b/README-WEBSOCKET.md @@ -35,17 +35,20 @@ Use version `4.0.0.WEBSOCKET-SNAPSHOT`, for example: Tomcat provides early JSR-356 support. You'll need to build the latest source, which is relatively easy to do. Check out Tomcat trunk: + mkdir tomcat cd tomcat svn co http://svn.apache.org/repos/asf/tomcat/trunk/ cd trunk Create `build.properties` in the trunk directory with content similar to the one below: + # ----- Default Base Path for Dependent Packages ----- # Replace this path with the path where dependencies binaries should be downloaded base.path=~/dev/sources/apache/tomcat/download Run the ant build: + ant clean ant From 20a9df772a2d4bac003264636ec936f82888a5f2 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 17 Apr 2013 16:40:45 -0400 Subject: [PATCH 23/51] Update README-WEBSOCKET.md --- README-WEBSOCKET.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README-WEBSOCKET.md b/README-WEBSOCKET.md index 5eb04d08cb..cd9a707771 100644 --- a/README-WEBSOCKET.md +++ b/README-WEBSOCKET.md @@ -41,7 +41,7 @@ Check out Tomcat trunk: svn co http://svn.apache.org/repos/asf/tomcat/trunk/ cd trunk -Create `build.properties` in the trunk directory with content similar to the one below: +Create `build.properties` in the trunk directory with similar content: # ----- Default Base Path for Dependent Packages ----- # Replace this path with the path where dependencies binaries should be downloaded @@ -63,12 +63,14 @@ Download a [Glassfish 4 build](http://dlc.sun.com.edgesuite.net/glassfish/4.0/) Unzip the downloaded file. Start the server: + cd /glassfish4 bin/asadmin start-domain Deploy a WAR file. Here is [a sample script](https://github.com/rstoyanchev/spring-websocket-test/blob/master/redeploy-glassfish.sh). Watch the logs: + cd /glassfish4 less `glassfish/domains/domain1/logs/server.log` From ab5d60d343209832d3b27195fae8385b472a181b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 18 Apr 2013 22:04:18 -0400 Subject: [PATCH 24/51] Improve APIs for WebSocket and SockJS messages --- .../springframework/sockjs/SockJsHandler.java | 23 ++- .../sockjs/SockJsHandlerAdapter.java | 10 +- .../springframework/sockjs/SockJsSession.java | 28 ++- .../sockjs/SockJsSessionFactory.java | 1 + .../sockjs/SockJsSessionSupport.java | 74 +++++-- .../server/AbstractServerSockJsSession.java | 58 +++--- .../sockjs/server/AbstractSockJsService.java | 2 +- .../AbstractHttpSendingTransportHandler.java | 2 +- .../AbstractHttpServerSockJsSession.java | 35 ++-- .../transport/SockJsWebSocketHandler.java | 78 +++---- .../WebSocketSockJsHandlerAdapter.java | 66 +++--- .../transport/WebSocketTransportHandler.java | 18 +- .../websocket/CloseStatus.java | 192 ++++++++++++++++++ .../websocket/WebSocketHandler.java | 27 ++- .../websocket/WebSocketHandlerAdapter.java | 11 +- .../websocket/WebSocketSession.java | 42 +++- .../endpoint/StandardWebSocketSession.java | 59 +++++- .../endpoint/WebSocketHandlerEndpoint.java | 23 ++- .../AbstractEndpointUpgradeStrategy.java | 2 +- .../GlassfishRequestUpgradeStrategy.java | 8 +- .../support/TomcatRequestUpgradeStrategy.java | 4 +- 21 files changed, 545 insertions(+), 218 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java index 00b4935c56..e8c20e573f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java @@ -16,21 +16,36 @@ package org.springframework.sockjs; +import org.springframework.websocket.CloseStatus; + /** + * A handler for SockJS messages. * * @author Rossen Stoyanchev * @since 4.0 */ public interface SockJsHandler { - void newSession(SockJsSession session) throws Exception; + /** + * A new connection was opened and is ready for use. + */ + void afterConnectionEstablished(SockJsSession session) throws Exception; - void handleMessage(SockJsSession session, String message) throws Exception; + /** + * Handle an incoming message. + */ + void handleMessage(String message, SockJsSession session) throws Exception; - void handleException(SockJsSession session, Throwable exception); + /** + * TODO + */ + void handleError(Throwable exception, SockJsSession session); - void sessionClosed(SockJsSession session); + /** + * A connection has been closed. + */ + void afterConnectionClosed(CloseStatus status, SockJsSession session); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java index 08b93d1296..3523f6c0dc 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java @@ -16,6 +16,8 @@ package org.springframework.sockjs; +import org.springframework.websocket.CloseStatus; + /** * @@ -25,19 +27,19 @@ package org.springframework.sockjs; public class SockJsHandlerAdapter implements SockJsHandler { @Override - public void newSession(SockJsSession session) throws Exception { + public void afterConnectionEstablished(SockJsSession session) throws Exception { } @Override - public void handleMessage(SockJsSession session, String message) throws Exception { + public void handleMessage(String message, SockJsSession session) throws Exception { } @Override - public void handleException(SockJsSession session, Throwable exception) { + public void handleError(Throwable exception, SockJsSession session) { } @Override - public void sessionClosed(SockJsSession session) { + public void afterConnectionClosed(CloseStatus status, SockJsSession session) { } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java index e7cd75bf4b..e1e46155cd 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java @@ -18,19 +18,45 @@ package org.springframework.sockjs; import java.io.IOException; +import org.springframework.websocket.CloseStatus; + /** + * Allows sending SockJS messages as well as closing the underlying connection. * * @author Rossen Stoyanchev * @since 4.0 */ public interface SockJsSession { + /** + * Return a unique SockJS session identifier. + */ String getId(); + /** + * Return whether the connection is still open. + */ + boolean isOpen(); + + /** + * Send a message. + */ void sendMessage(String text) throws IOException; - void close(); + /** + * Close the underlying connection with status 1000, i.e. equivalent to: + *

+	 * session.close(CloseStatus.NORMAL);
+	 * 
+ */ + void close() throws IOException; + + /** + * Close the underlying connection with the given close status. + */ + void close(CloseStatus status) throws IOException; + } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index b67dcefb2c..4e4950181d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -18,6 +18,7 @@ package org.springframework.sockjs; /** + * A factory for creating a SockJS session. * * @author Rossen Stoyanchev * @since 4.0 diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java index 0dac36118d..3cf7e61fc1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java @@ -16,9 +16,12 @@ package org.springframework.sockjs; +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; /** @@ -99,33 +102,78 @@ public abstract class SockJsSessionSupport implements SockJsSession { this.timeLastActive = System.currentTimeMillis(); } - public void connectionInitialized() throws Exception { + public void delegateConnectionEstablished() throws Exception { this.state = State.OPEN; - this.sockJsHandler.newSession(this); + this.sockJsHandler.afterConnectionEstablished(this); } - public void delegateMessages(String... messages) throws Exception { + public void delegateMessages(String[] messages) throws Exception { for (String message : messages) { - this.sockJsHandler.handleMessage(this, message); + this.sockJsHandler.handleMessage(message, this); } } - public void delegateException(Throwable ex) { - this.sockJsHandler.handleException(this, ex); + public void delegateError(Throwable ex) { + this.sockJsHandler.handleError(ex, this); } - public void connectionClosed() { - this.state = State.CLOSED; - this.sockJsHandler.sessionClosed(this); + /** + * Invoked in reaction to the underlying connection being closed by the remote side + * (or the WebSocket container) in order to perform cleanup and notify the + * {@link SockJsHandler}. This is in contrast to {@link #close()} that pro-actively + * closes the connection. + */ + public final void delegateConnectionClosed(CloseStatus status) { + if (!isClosed()) { + if (logger.isDebugEnabled()) { + logger.debug(this + " was closed, " + status); + } + try { + connectionClosedInternal(status); + } + finally { + this.state = State.CLOSED; + this.sockJsHandler.afterConnectionClosed(status, this); + } + } } - public void close() { - this.state = State.CLOSED; - this.sockJsHandler.sessionClosed(this); + protected void connectionClosedInternal(CloseStatus status) { } + /** + * {@inheritDoc} + *

Performs cleanup and notifies the {@link SockJsHandler}. + */ + public final void close() throws IOException { + close(CloseStatus.NORMAL); + } + + /** + * {@inheritDoc} + *

Performs cleanup and notifies the {@link SockJsHandler}. + */ + public final void close(CloseStatus status) throws IOException { + if (!isClosed()) { + if (logger.isDebugEnabled()) { + logger.debug("Closing " + this + ", " + status); + } + try { + closeInternal(status); + } + finally { + this.state = State.CLOSED; + this.sockJsHandler.afterConnectionClosed(status, this); + } + } + } + + protected abstract void closeInternal(CloseStatus status) throws IOException; + + + @Override public String toString() { - return getClass().getSimpleName() + " [id=" + sessionId + "]"; + return "SockJS session id=" + this.sessionId; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 54be439dde..04fc33409d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -26,6 +26,7 @@ import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSession; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; /** @@ -42,9 +43,9 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { private ScheduledFuture heartbeatTask; - public AbstractServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, SockJsHandler sockJsHandler) { super(sessionId, sockJsHandler); - this.sockJsConfig = sockJsConfig; + this.sockJsConfig = config; } protected SockJsConfiguration getSockJsConfig() { @@ -60,36 +61,34 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { @Override - public void connectionClosed() { - logger.debug("Session closed"); - super.close(); + public void connectionClosedInternal(CloseStatus status) { + updateLastActiveTime(); cancelHeartbeat(); } @Override - public final synchronized void close() { - if (!isClosed()) { - logger.debug("Closing session"); - if (isActive()) { - // deliver messages "in flight" before sending close frame - try { - writeFrame(SockJsFrame.closeFrameGoAway()); - } - catch (Exception e) { - // ignore - } + public final synchronized void closeInternal(CloseStatus status) throws IOException { + if (isActive()) { + // TODO: deliver messages "in flight" before sending close frame + try { + // bypass writeFrame + writeFrameInternal(SockJsFrame.closeFrame(status.getCode(), status.getReason())); + } + catch (Throwable ex) { + logger.warn("Failed to send SockJS close frame: " + ex.getMessage()); } - super.close(); - cancelHeartbeat(); - closeInternal(); } + updateLastActiveTime(); + cancelHeartbeat(); + disconnect(status); } - protected abstract void closeInternal(); + // TODO: close status/reason + protected abstract void disconnect(CloseStatus status) throws IOException; /** * For internal use within a TransportHandler and the (TransportHandler-specific) - * session sub-class. The frame is written only if the connection is active. + * session sub-class. */ protected void writeFrame(SockJsFrame frame) throws IOException { if (logger.isTraceEnabled()) { @@ -103,29 +102,20 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { logger.warn("Client went away. Terminating connection"); } else { - logger.warn("Failed to send message. Terminating connection: " + ex.getMessage()); + logger.warn("Terminating connection due to failure to send message: " + ex.getMessage()); } - deactivate(); close(); throw ex; } - catch (Throwable t) { - logger.warn("Failed to send message. Terminating connection: " + t.getMessage()); - deactivate(); + catch (Throwable ex) { + logger.warn("Terminating connection due to failure to send message: " + ex.getMessage()); close(); - throw new NestedSockJsRuntimeException("Failed to write frame " + frame, t); + throw new NestedSockJsRuntimeException("Failed to write frame " + frame, ex); } } protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException; - /** - * Some {@link TransportHandler} types cannot detect if a client connection is closed - * or lost and will eventually fail to send messages. When that happens, we need a way - * to disconnect the underlying connection before calling {@link #close()}. - */ - protected abstract void deactivate(); - public synchronized void sendHeartbeat() throws IOException { if (isActive()) { writeFrame(SockJsFrame.heartbeatFrame()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 399bf5d5d6..6d84713727 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -56,7 +56,7 @@ public abstract class AbstractSockJsService private static final int ONE_YEAR = 365 * 24 * 60 * 60; - private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); + private String name = getClass().getSimpleName() + "@" + ObjectUtils.getIdentityHexString(this); private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js"; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index d917ef035b..726fa76c87 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -92,7 +92,7 @@ public abstract class AbstractHttpSendingTransportHandler logger.debug("Opening " + getTransportType() + " connection"); session.setFrameFormat(getFrameFormat(request)); session.writeFrame(response, SockJsFrame.openFrame()); - session.connectionInitialized(); + session.delegateConnectionEstablished(); } protected abstract MediaType getContentType(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index b71b4287a9..0c9d624f56 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -29,6 +29,7 @@ import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportHandler; import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; /** * An abstract base class for use with HTTP-based transports. @@ -109,14 +110,22 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock protected abstract void flushCache() throws IOException; @Override - public void connectionClosed() { - super.connectionClosed(); + protected void disconnect(CloseStatus status) { resetRequest(); } - @Override - protected void closeInternal() { - resetRequest(); + protected synchronized void resetRequest() { + updateLastActiveTime(); + if (isActive()) { + try { + this.asyncRequest.completeAsync(); + } + catch (Throwable ex) { + logger.warn("Failed to complete async request: " + ex.getMessage()); + } + } + this.asyncRequest = null; + this.response = null; } protected synchronized void writeFrameInternal(SockJsFrame frame) throws IOException { @@ -138,20 +147,4 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock response.getBody().write(frame.getContentBytes()); } - @Override - protected void deactivate() { - this.asyncRequest = null; - this.response = null; - updateLastActiveTime(); - } - - protected synchronized void resetRequest() { - if (isActive()) { - this.asyncRequest.completeAsync(); - } - this.asyncRequest = null; - this.response = null; - updateLastActiveTime(); - } - } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index c23863f901..e616455a2c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -17,7 +17,6 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; -import java.io.InputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +29,7 @@ import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.websocket.CloseStatus; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -78,21 +78,15 @@ public class SockJsWebSocketHandler implements WebSocketHandler { } @Override - public void newSession(WebSocketSession wsSession) throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("New session: " + wsSession); - } + public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { SockJsSessionSupport session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); this.sessions.put(wsSession, session); } @Override - public void handleTextMessage(WebSocketSession wsSession, String message) throws Exception { - if (logger.isTraceEnabled()) { - logger.trace("Received payload " + message + " for " + wsSession); - } + public void handleTextMessage(String message, WebSocketSession wsSession) throws Exception { if (StringUtils.isEmpty(message)) { - logger.trace("Ignoring empty payload"); + logger.trace("Ignoring empty message"); return; } try { @@ -107,41 +101,50 @@ public class SockJsWebSocketHandler implements WebSocketHandler { } @Override - public void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception { - // should not happen - throw new UnsupportedOperationException(); + public void handleBinaryMessage(byte[] message, WebSocketSession session) throws Exception { + logger.warn("Unexpected binary message for " + session); + session.close(CloseStatus.NOT_ACCEPTABLE); } @Override - public void handleException(WebSocketSession webSocketSession, Throwable exception) { + public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) throws Exception { + SockJsSessionSupport session = this.sessions.remove(wsSession); + session.delegateConnectionClosed(status); + } + + @Override + public void handleError(Throwable exception, WebSocketSession webSocketSession) { SockJsSessionSupport session = getSockJsSession(webSocketSession); - session.delegateException(exception); + session.delegateError(exception); } - @Override - public void sessionClosed(WebSocketSession webSocketSession, int statusCode, String reason) throws Exception { - logger.debug("WebSocket session closed " + webSocketSession); - SockJsSessionSupport session = this.sessions.remove(webSocketSession); - session.connectionClosed(); + private static String getSockJsSessionId(WebSocketSession wsSession) { + Assert.notNull(wsSession, "wsSession is required"); + String path = wsSession.getURI().getPath(); + String[] segments = StringUtils.tokenizeToStringArray(path, "/"); + Assert.isTrue(segments.length > 3, "SockJS request should have at least 3 patgh segments: " + path); + return segments[segments.length-2]; } private class WebSocketServerSockJsSession extends AbstractServerSockJsSession { - private WebSocketSession webSocketSession; + private final WebSocketSession wsSession; - public WebSocketServerSockJsSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) throws Exception { - super(String.valueOf(wsSession.hashCode()), sockJsConfig, getSockJsHandler()); - this.webSocketSession = wsSession; - this.webSocketSession.sendText(SockJsFrame.openFrame().getContent()); + public WebSocketServerSockJsSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) + throws Exception { + + super(getSockJsSessionId(wsSession), sockJsConfig, getSockJsHandler()); + this.wsSession = wsSession; + this.wsSession.sendTextMessage(SockJsFrame.openFrame().getContent()); scheduleHeartbeat(); - connectionInitialized(); + delegateConnectionEstablished(); } @Override public boolean isActive() { - return ((this.webSocketSession != null) && this.webSocketSession.isOpen()); + return this.wsSession.isOpen(); } @Override @@ -156,27 +159,12 @@ public class SockJsWebSocketHandler implements WebSocketHandler { if (logger.isTraceEnabled()) { logger.trace("Write " + frame); } - this.webSocketSession.sendText(frame.getContent()); + this.wsSession.sendTextMessage(frame.getContent()); } @Override - public void connectionClosed() { - super.connectionClosed(); - this.webSocketSession = null; - } - - @Override - public void closeInternal() { - deactivate(); - updateLastActiveTime(); - } - - @Override - protected void deactivate() { - if (this.webSocketSession != null) { - this.webSocketSession.close(); - this.webSocketSession = null; - } + protected void disconnect(CloseStatus status) throws IOException { + this.wsSession.close(status); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java index 560335693c..12a5bcd282 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java @@ -17,7 +17,6 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; -import java.io.InputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -27,14 +26,15 @@ import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; 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". + * A plain {@link WebSocketHandler} to {@link SockJsHandler} adapter that merely delegates + * without any additional SockJS message framing. Used for raw WebSocket communication at + * SockJS path "/websocket". * * @author Rossen Stoyanchev * @since 4.0 @@ -71,79 +71,61 @@ public class WebSocketSockJsHandlerAdapter implements WebSocketHandler { } @Override - public void newSession(WebSocketSession wsSession) throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("New session: " + wsSession); - } + public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { SockJsSessionSupport session = new SockJsWebSocketSessionAdapter(wsSession); this.sessions.put(wsSession, session); } @Override - public void handleTextMessage(WebSocketSession wsSession, String message) throws Exception { - if (logger.isTraceEnabled()) { - logger.trace("Received payload " + message); - } + public void handleTextMessage(String message, WebSocketSession wsSession) throws Exception { SockJsSessionSupport session = getSockJsSession(wsSession); - session.delegateMessages(message); + session.delegateMessages(new String[] { message }); } @Override - public void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception { - // should not happen - throw new UnsupportedOperationException(); + public void handleBinaryMessage(byte[] message, WebSocketSession session) throws Exception { + logger.warn("Unexpected binary message for " + session); + session.close(CloseStatus.NOT_ACCEPTABLE); } @Override - public void handleException(WebSocketSession webSocketSession, Throwable exception) { - SockJsSessionSupport session = getSockJsSession(webSocketSession); - session.delegateException(exception); + public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) throws Exception { + SockJsSessionSupport session = this.sessions.remove(wsSession); + session.delegateConnectionClosed(status); } @Override - public void sessionClosed(WebSocketSession webSocketSession, int statusCode, String reason) throws Exception { - logger.debug("WebSocket session closed " + webSocketSession); - SockJsSessionSupport session = this.sessions.remove(webSocketSession); - session.connectionClosed(); + public void handleError(Throwable exception, WebSocketSession wsSession) { + logger.error("Error for " + wsSession); + SockJsSessionSupport session = getSockJsSession(wsSession); + session.delegateError(exception); } private class SockJsWebSocketSessionAdapter extends SockJsSessionSupport { - private WebSocketSession wsSession; + private final WebSocketSession wsSession; public SockJsWebSocketSessionAdapter(WebSocketSession wsSession) throws Exception { - super(String.valueOf(wsSession.hashCode()), getSockJsHandler()); + super(wsSession.getId(), getSockJsHandler()); this.wsSession = wsSession; - connectionInitialized(); + delegateConnectionEstablished(); } @Override public boolean isActive() { - return (!isClosed() && this.wsSession.isOpen()); + return this.wsSession.isOpen(); } @Override public void sendMessage(String message) throws IOException { - this.wsSession.sendText(message); + this.wsSession.sendTextMessage(message); } @Override - public void connectionClosed() { - logger.debug("Session closed"); - super.connectionClosed(); - this.wsSession = null; - } - - @Override - public void close() { - if (!isClosed()) { - logger.debug("Closing session"); - super.close(); - this.wsSession.close(); - this.wsSession = null; - } + public void closeInternal(CloseStatus status) throws IOException { + this.wsSession.close(status); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 3fc0a7f9f3..32702d00d9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -50,9 +50,9 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, private SockJsConfiguration sockJsConfig; - private final Map sockJsHandlers = new HashMap(); + private final Map sockJsHandlerCache = new HashMap(); - private final Collection rawWebSocketHandlers = new ArrayList(); + private final Collection rawWebSocketHandlerCache = new ArrayList(); public WebSocketTransportHandler(HandshakeHandler handshakeHandler) { @@ -72,9 +72,9 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void registerSockJsHandlers(Collection sockJsHandlers) { - this.sockJsHandlers.clear(); + this.sockJsHandlerCache.clear(); for (SockJsHandler sockJsHandler : sockJsHandlers) { - this.sockJsHandlers.put(sockJsHandler, adaptSockJsHandler(sockJsHandler)); + this.sockJsHandlerCache.put(sockJsHandler, adaptSockJsHandler(sockJsHandler)); } this.handshakeHandler.registerWebSocketHandlers(getAllWebSocketHandlers()); } @@ -89,8 +89,8 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, private Collection getAllWebSocketHandlers() { Set handlers = new HashSet(); - handlers.addAll(this.sockJsHandlers.values()); - handlers.addAll(this.rawWebSocketHandlers); + handlers.addAll(this.sockJsHandlerCache.values()); + handlers.addAll(this.rawWebSocketHandlerCache); return handlers; } @@ -98,7 +98,7 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { - WebSocketHandler webSocketHandler = this.sockJsHandlers.get(sockJsHandler); + WebSocketHandler webSocketHandler = this.sockJsHandlerCache.get(sockJsHandler); if (webSocketHandler == null) { webSocketHandler = adaptSockJsHandler(sockJsHandler); } @@ -110,8 +110,8 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void registerWebSocketHandlers(Collection webSocketHandlers) { - this.rawWebSocketHandlers.clear(); - this.rawWebSocketHandlers.addAll(webSocketHandlers); + this.rawWebSocketHandlerCache.clear(); + this.rawWebSocketHandlerCache.addAll(webSocketHandlers); this.handshakeHandler.registerWebSocketHandlers(getAllWebSocketHandlers()); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java new file mode 100644 index 0000000000..979ec4090f --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java @@ -0,0 +1,192 @@ +/* + * 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 org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + + +/** + * Represents a WebSocket close status code and reason. Status codes in the 1xxx range are + * pre-defined by the protocol. Optionally, a status code may be sent with a reason. + *

+ * See RFC 6455, Section 7.4.1 + * "Defined Status Codes". + * + * @author Rossen Stoyanchev + * @since 4.0 + * + */ +public final class CloseStatus { + + /** + * "1000 indicates a normal closure, meaning that the purpose for which the connection + * was established has been fulfilled." + */ + public static final CloseStatus NORMAL = new CloseStatus(1000); + + /** + * "1001 indicates that an endpoint is "going away", such as a server going down or a + * browser having navigated away from a page." + */ + public static final CloseStatus GOING_AWAY = new CloseStatus(1001); + + /** + * "1002 indicates that an endpoint is terminating the connection due to a protocol + * error." + */ + public static final CloseStatus PROTOCOL_ERROR = new CloseStatus(1002); + + /** + * "1003 indicates that an endpoint is terminating the connection because it has + * received a type of data it cannot accept (e.g., an endpoint that understands only + * text data MAY send this if it receives a binary message)." + */ + public static final CloseStatus NOT_ACCEPTABLE = new CloseStatus(1003); + + // 10004: Reserved. + // The specific meaning might be defined in the future. + + /** + * "1005 is a reserved value and MUST NOT be set as a status code in a Close control + * frame by an endpoint. It is designated for use in applications expecting a status + * code to indicate that no status code was actually present." + */ + public static final CloseStatus NO_STATUS_CODE = new CloseStatus(1005); + + /** + * "1006 is a reserved value and MUST NOT be set as a status code in a Close control + * frame by an endpoint. It is designated for use in applications expecting a status + * code to indicate that the connection was closed abnormally, e.g., without sending + * or receiving a Close control frame." + */ + public static final CloseStatus NO_CLOSE_FRAME = new CloseStatus(1006); + + /** + * "1007 indicates that an endpoint is terminating the connection because it has + * received data within a message that was not consistent with the type of the message + * (e.g., non-UTF-8 [RFC3629] data within a text message)." + */ + public static final CloseStatus BAD_DATA = new CloseStatus(1007); + + /** + * "1008 indicates that an endpoint is terminating the connection because it has + * received a message that violates its policy. This is a generic status code that can + * be returned when there is no other more suitable status code (e.g., 1003 or 1009) + * or if there is a need to hide specific details about the policy." + */ + public static final CloseStatus POLICY_VIOLATION = new CloseStatus(1008); + + /** + * "1009 indicates that an endpoint is terminating the connection because it has + * received a message that is too big for it to process." + */ + public static final CloseStatus TOO_BIG_TO_PROCESS = new CloseStatus(1009); + + /** + * "1010 indicates that an endpoint (client) is terminating the connection because it + * has expected the server to negotiate one or more extension, but the server didn't + * return them in the response message of the WebSocket handshake. The list of + * extensions that are needed SHOULD appear in the /reason/ part of the Close frame. + * Note that this status code is not used by the server, because it can fail the + * WebSocket handshake instead." + */ + public static final CloseStatus REQUIRED_EXTENSION = new CloseStatus(1010); + + /** + * "1011 indicates that a server is terminating the connection because it encountered + * an unexpected condition that prevented it from fulfilling the request." + */ + public static final CloseStatus SERVER_ERROR = new CloseStatus(1011); + + /** + * 1012 indicates that the service is restarted. A client may reconnect, and if it + * choses to do, should reconnect using a randomized delay of 5 - 30s. + *

See + * [hybi] Additional WebSocket Close Error Codes + */ + public static final CloseStatus SERVICE_RESTARTED = new CloseStatus(1012); + + /** + * 1013 indicates that the service is experiencing overload. A client should only + * connect to a different IP (when there are multiple for the target) or reconnect to + * the same IP upon user action. + *

See + * [hybi] Additional WebSocket Close Error Codes + */ + public static final CloseStatus SERVICE_OVERLOAD = new CloseStatus(1013); + + /** + * "1015 is a reserved value and MUST NOT be set as a status code in a Close control + * frame by an endpoint. It is designated for use in applications expecting a status + * code to indicate that the connection was closed due to a failure to perform a TLS + * handshake (e.g., the server certificate can't be verified)." + */ + public static final CloseStatus TLS_HANDSHAKE_FAILURE = new CloseStatus(1015); + + + + private final int code; + + private final String reason; + + + public CloseStatus(int code) { + this(code, null); + } + + public CloseStatus(int code, String reason) { + Assert.isTrue((code >= 1000 && code < 5000), "Invalid code"); + this.code = code; + this.reason = reason; + } + + public int getCode() { + return this.code; + } + + public String getReason() { + return this.reason; + } + + public CloseStatus withReason(String reason) { + Assert.hasText(reason, "Expected non-empty reason"); + return new CloseStatus(this.code, reason); + } + + @Override + public int hashCode() { + return this.code * 29 + ObjectUtils.nullSafeHashCode(this.reason); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof CloseStatus)) { + return false; + } + CloseStatus otherStatus = (CloseStatus) other; + return (this.code == otherStatus.code && ObjectUtils.nullSafeEquals(this.reason, otherStatus.reason)); + } + + @Override + public String toString() { + return "CloseStatus [code=" + this.code + ", reason=" + this.reason + "]"; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 5877a5b452..783e9b66d1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -16,24 +16,39 @@ package org.springframework.websocket; -import java.io.InputStream; /** + * A handler for WebSocket messages. * * @author Rossen Stoyanchev * @since 4.0 */ public interface WebSocketHandler { - void newSession(WebSocketSession session) throws Exception; + /** + * A new WebSocket connection has been opened and is ready to be used. + */ + void afterConnectionEstablished(WebSocketSession session) throws Exception; - void handleTextMessage(WebSocketSession session, String message) throws Exception; + /** + * Handle an incoming text message. + */ + void handleTextMessage(String message, WebSocketSession session) throws Exception; - void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception; + /** + * Handle an incoming binary message. + */ + void handleBinaryMessage(byte[] bytes, WebSocketSession session) throws Exception; - void handleException(WebSocketSession session, Throwable exception); + /** + * TODO + */ + void handleError(Throwable exception, WebSocketSession session); - void sessionClosed(WebSocketSession session, int statusCode, String reason) throws Exception; + /** + * A WebSocket connection has been closed. + */ + void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index 261aff0451..af03f63b1c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -16,7 +16,6 @@ package org.springframework.websocket; -import java.io.InputStream; /** * @@ -26,23 +25,23 @@ import java.io.InputStream; public class WebSocketHandlerAdapter implements WebSocketHandler { @Override - public void newSession(WebSocketSession session) throws Exception { + public void afterConnectionEstablished(WebSocketSession session) throws Exception { } @Override - public void handleTextMessage(WebSocketSession session, String message) throws Exception { + public void handleTextMessage(String message, WebSocketSession session) throws Exception { } @Override - public void handleBinaryMessage(WebSocketSession session, InputStream message) throws Exception { + public void handleBinaryMessage(byte[] message, WebSocketSession session) throws Exception { } @Override - public void handleException(WebSocketSession session, Throwable exception) { + public void handleError(Throwable exception, WebSocketSession session) { } @Override - public void sessionClosed(WebSocketSession session, int statusCode, String reason) throws Exception { + public void afterConnectionClosed(CloseStatus status, WebSocketSession session) throws Exception { } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index a9af7d7d7b..d1d3c27a35 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -17,24 +17,60 @@ package org.springframework.websocket; import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; /** + * Allows sending messages over a WebSocket connection as well as closing it. * * @author Rossen Stoyanchev * @since 4.0 */ public interface WebSocketSession { + /** + * Return a unique session identifier. + */ String getId(); + /** + * Return whether the connection is still open. + */ boolean isOpen(); - void sendText(String text) throws IOException; + /** + * Return whether the underlying socket is using a secure transport. + */ + boolean isSecure(); - void close(); + /** + * Return the URI used to open the WebSocket connection. + */ + URI getURI(); - void close(int code, String reason); + /** + * Send a text message. + */ + void sendTextMessage(String message) throws IOException; + + /** + * Send a binary message. + */ + void sendBinaryMessage(ByteBuffer message) throws IOException; + + /** + * Close the WebSocket connection with status 1000, i.e. equivalent to: + *

+	 * session.close(CloseStatus.NORMAL);
+	 * 
+ */ + void close() throws IOException; + + /** + * Close the WebSocket connection with the given close status. + */ + void close(CloseStatus status) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index 546f16c96e..64f4d4d7fd 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -17,14 +17,22 @@ package org.springframework.websocket.endpoint; import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; + +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; import org.springframework.websocket.WebSocketSession; /** - * A {@link WebSocketSession} that delegates to a {@link javax.websocket.Session}. + * A standard Java implementation of {@link WebSocketSession} that delegates to + * {@link javax.websocket.Session}. * * @author Rossen Stoyanchev * @since 4.0 @@ -33,10 +41,11 @@ public class StandardWebSocketSession implements WebSocketSession { private static Log logger = LogFactory.getLog(StandardWebSocketSession.class); - private javax.websocket.Session session; + private final javax.websocket.Session session; public StandardWebSocketSession(javax.websocket.Session session) { + Assert.notNull(session, "session is required"); this.session = session; } @@ -47,25 +56,53 @@ public class StandardWebSocketSession implements WebSocketSession { @Override public boolean isOpen() { - return ((this.session != null) && this.session.isOpen()); + return this.session.isOpen(); } @Override - public void sendText(String text) throws IOException { - logger.trace("Sending text message: " + text); - // TODO: check closed + public boolean isSecure() { + return this.session.isSecure(); + } + + @Override + public URI getURI() { + return this.session.getRequestURI(); + } + + @Override + public void sendTextMessage(String text) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Sending text message: " + text + ", " + this); + } + Assert.isTrue(isOpen(), "Cannot send message after connection closed."); this.session.getBasicRemote().sendText(text); } @Override - public void close() { - // TODO: delegate with code and reason - this.session = null; + public void sendBinaryMessage(ByteBuffer message) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Sending binary message, " + this); + } + Assert.isTrue(isOpen(), "Cannot send message after connection closed."); + this.session.getBasicRemote().sendBinary(message); } @Override - public void close(int code, String reason) { - this.session = null; + public void close() throws IOException { + close(CloseStatus.NORMAL); + } + + @Override + public void close(CloseStatus status) throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("Closing " + this); + } + this.session.close(new CloseReason(CloseCodes.getCloseCode(status.getCode()), status.getReason())); + } + + @Override + public String toString() { + return "WebSocket session id=" + getId(); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 7680f24ef3..73840558ac 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -27,6 +27,7 @@ import javax.websocket.MessageHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.WebSocketHandler; @@ -53,13 +54,14 @@ public class WebSocketHandlerEndpoint extends Endpoint { @Override public void onOpen(javax.websocket.Session session, EndpointConfig config) { if (logger.isDebugEnabled()) { - logger.debug("New session: " + session); + logger.debug("Client connected, WebSocket session id=" + session.getId() + + ", path=" + session.getRequestURI().getPath()); } try { WebSocketSession webSocketSession = new StandardWebSocketSession(session); this.sessions.put(session.getId(), webSocketSession); session.addMessageHandler(new StandardMessageHandler(session)); - this.webSocketHandler.newSession(webSocketSession); + this.webSocketHandler.afterConnectionEstablished(webSocketSession); } catch (Throwable ex) { // TODO @@ -68,16 +70,15 @@ public class WebSocketHandlerEndpoint extends Endpoint { } @Override - public void onClose(javax.websocket.Session session, CloseReason closeReason) { + public void onClose(javax.websocket.Session session, CloseReason reason) { if (logger.isDebugEnabled()) { - logger.debug("Session closed: " + session + ", " + closeReason); + logger.debug("Client disconnected, WebSocket session id=" + session.getId() + ", " + reason); } try { WebSocketSession wsSession = this.sessions.remove(session.getId()); if (wsSession != null) { - int code = closeReason.getCloseCode().getCode(); - String reason = closeReason.getReasonPhrase(); - this.webSocketHandler.sessionClosed(wsSession, code, reason); + CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); + this.webSocketHandler.afterConnectionClosed(closeStatus, wsSession); } else { Assert.notNull(wsSession, "No WebSocket session"); @@ -91,11 +92,11 @@ public class WebSocketHandlerEndpoint extends Endpoint { @Override public void onError(javax.websocket.Session session, Throwable exception) { - logger.error("Error for WebSocket session: " + session.getId(), exception); + logger.error("Error for WebSocket session id=" + session.getId(), exception); try { WebSocketSession wsSession = getWebSocketSession(session); if (wsSession != null) { - this.webSocketHandler.handleException(wsSession, exception); + this.webSocketHandler.handleError(exception, wsSession); } else { logger.warn("WebSocketSession not found. Perhaps onError was called after onClose?"); @@ -123,12 +124,12 @@ public class WebSocketHandlerEndpoint extends Endpoint { @Override public void onMessage(String message) { if (logger.isTraceEnabled()) { - logger.trace("Message for session [" + this.session + "]: " + message); + logger.trace("Received message for WebSocket session id=" + this.session.getId() + ": " + message); } WebSocketSession wsSession = getWebSocketSession(this.session); Assert.notNull(wsSession, "WebSocketSession not found"); try { - WebSocketHandlerEndpoint.this.webSocketHandler.handleTextMessage(wsSession, message); + WebSocketHandlerEndpoint.this.webSocketHandler.handleTextMessage(message, wsSession); } catch (Throwable ex) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index 1e7f1a2794..29b2372f91 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -75,6 +75,6 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String protocol, Endpoint endpoint) throws Exception; + String selectedProtocol, Endpoint endpoint) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java index 1a7619af45..e462e65dd7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java @@ -18,6 +18,7 @@ package org.springframework.websocket.server.support; import java.lang.reflect.Constructor; import java.net.URI; +import java.util.Arrays; import java.util.Random; import javax.servlet.http.HttpServletRequest; @@ -66,7 +67,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String protocol, Endpoint endpoint) throws Exception { + String selectedProtocol, Endpoint endpoint) throws Exception { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -75,7 +76,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); servletResponse = new AlreadyUpgradedResponseWrapper(servletResponse); - TyrusEndpoint tyrusEndpoint = createTyrusEndpoint(servletRequest, endpoint); + TyrusEndpoint tyrusEndpoint = createTyrusEndpoint(servletRequest, endpoint, selectedProtocol); WebSocketEngine engine = WebSocketEngine.getEngine(); engine.register(tyrusEndpoint); @@ -112,7 +113,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra }); } - private TyrusEndpoint createTyrusEndpoint(HttpServletRequest request, Endpoint endpoint) { + private TyrusEndpoint createTyrusEndpoint(HttpServletRequest request, Endpoint endpoint, String selectedProtocol) { // Use randomized path String requestUri = request.getRequestURI(); @@ -120,6 +121,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra String endpointPath = requestUri.endsWith("/") ? requestUri + randomValue : requestUri + "/" + randomValue; EndpointRegistration endpointConfig = new EndpointRegistration(endpointPath, endpoint); + endpointConfig.setSubprotocols(Arrays.asList(selectedProtocol)); return new TyrusEndpoint(new EndpointWrapper(endpoint, endpointConfig, ComponentProviderService.create(), null, "/", new ErrorCollector(), diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java index 5b2f3a2575..d90156b97f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java @@ -51,7 +51,7 @@ public class TomcatRequestUpgradeStrategy extends AbstractEndpointUpgradeStrateg @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String protocol, Endpoint endpoint) throws IOException { + String selectedProtocol, Endpoint endpoint) throws IOException { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -74,7 +74,7 @@ public class TomcatRequestUpgradeStrategy extends AbstractEndpointUpgradeStrateg ServerEndpointConfig endpointConfig = new EndpointRegistration("/shouldntmatter", endpoint); upgradeHandler.preInit(endpoint, endpointConfig, serverContainer, webSocketRequest, - protocol, Collections. emptyMap(), servletRequest.isSecure()); + selectedProtocol, Collections. emptyMap(), servletRequest.isSecure()); } } From 2046629945d288944b13401958f5d27e04bbe835 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 21 Apr 2013 10:04:44 -0400 Subject: [PATCH 25/51] Add WebSocketClient and WebSocketConnectionManager This change adds a WebSocketClient abstraction and enables the use of WebSocketHandler on the client side. --- .../websocket/HandlerProvider.java | 5 +- .../websocket/WebSocketHandshakeRequest.java | 48 ++++++ ...> AbstractWebSocketConnectionManager.java} | 71 +++------ .../websocket/client/WebSocketClient.java | 48 ++++++ .../WebSocketConnectFailureException.java | 38 +++++ .../client/WebSocketConnectionManager.java | 76 ++++++++++ .../AnnotatedEndpointConnectionManager.java | 19 +-- .../EndpointConnectionManager.java | 31 ++-- .../EndpointConnectionManagerSupport.java | 71 +++++++++ .../endpoint/StandardWebSocketClient.java | 105 +++++++++++++ .../WebSocketContainerFactoryBean.java | 84 +++++++++++ .../client/endpoint/package-info.java | 8 + .../websocket/client/package-info.java | 3 +- .../endpoint/WebSocketHandlerEndpoint.java | 3 +- .../server/endpoint/EndpointExporter.java | 141 +++++++----------- .../endpoint/ServletEndpointExporter.java | 60 -------- .../ServletServerContainerFactoryBean.java | 133 +++++++++++++++++ .../AbstractEndpointContainerFactoryBean.java | 88 +++++++++++ 18 files changed, 797 insertions(+), 235 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java rename spring-websocket/src/main/java/org/springframework/websocket/client/{AbstractEndpointConnectionManager.java => AbstractWebSocketConnectionManager.java} (65%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java rename spring-websocket/src/main/java/org/springframework/websocket/client/{ => endpoint}/AnnotatedEndpointConnectionManager.java (73%) rename spring-websocket/src/main/java/org/springframework/websocket/client/{ => endpoint}/EndpointConnectionManager.java (77%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java index f8d2d90bd2..135c674d8b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java @@ -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 implements BeanFactoryAware { + private Log logger = LogFactory.getLog(this.getClass()); + private final T handlerBean; private final Class handlerClass; private AutowireCapableBeanFactory beanFactory; - private Log logger; - public HandlerProvider(T handlerBean) { Assert.notNull(handlerBean, "handlerBean is required"); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java new file mode 100644 index 0000000000..762cc2fe8b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java @@ -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; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java similarity index 65% rename from spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java index 9a24e04af5..7839c7c2a3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java @@ -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(); + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java new file mode 100644 index 0000000000..ae82de0dc0 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -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. + * + *

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; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java new file mode 100644 index 0000000000..553423cb92 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java @@ -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); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java new file mode 100644 index 0000000000..cb16e5be44 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -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 webSocketHandlerProvider; + + private WebSocketSession webSocketSession; + + private List subProtocols; + + + public WebSocketConnectionManager(WebSocketClient webSocketClient, + WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) { + + super(uriTemplate, uriVariables); + this.webSocketHandlerProvider = new HandlerProvider(webSocketHandler); + this.client = webSocketClient; + } + + public void setSubProtocols(List subProtocols) { + this.subProtocols = subProtocols; + } + + public List 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())); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java similarity index 73% rename from spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java index a1005e3507..75433591ca 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java @@ -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 endpointProvider; public AnnotatedEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); this.endpointProvider = new HandlerProvider(endpointClass); - this.endpointProvider.setLogger(logger); } public AnnotatedEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); this.endpointProvider = new HandlerProvider(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); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java similarity index 77% rename from spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java index 9ca8653bbf..b7006ab2fc 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java @@ -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 endpointProvider; - public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { - super(uriTemplate, uriVariables); - Assert.notNull(endpointClass, "endpointClass is required"); - this.endpointProvider = new HandlerProvider(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(endpointBean); - this.endpointProvider.setLogger(logger); + } + + public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVars) { + super(uriTemplate, uriVars); + Assert.notNull(endpointClass, "endpointClass is required"); + this.endpointProvider = new HandlerProvider(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); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java new file mode 100644 index 0000000000..ba4fdbcdfb --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java @@ -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()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java new file mode 100644 index 0000000000..522bfcdf1e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -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 EXCLUDED_HEADERS = new HashSet( + 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 protocols = httpHeaders.getSecWebSocketProtocol(); + if (!protocols.isEmpty()) { + configBuidler.preferredSubprotocols(protocols); + } + configBuidler.configurator(new Configurator() { + @Override + public void beforeRequest(Map> 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); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java new file mode 100644 index 0000000000..a6d9d53ebf --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java @@ -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 { + + 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; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java new file mode 100644 index 0000000000..777e2ae7da --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java @@ -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; + diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java index 28e7e9eb86..817c86c58d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java @@ -1,6 +1,5 @@ - /** - * Client-side support for WebSocket applications. + * Server-side abstractions for WebSocket applications. * */ package org.springframework.websocket.client; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 73840558ac..7593adbfd0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -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); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java index 76194c4ecd..1d09241846 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java @@ -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> annotatedEndpointClasses = new ArrayList>(); - private Long maxSessionIdleTimeout; + private final List> annotatedEndpointBeanTypes = new ArrayList>(); - 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 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 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> allClasses = new ArrayList>(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); } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java deleted file mode 100644 index b5b216fc8b..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletEndpointExporter.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java new file mode 100644 index 0000000000..427ca9fcbd --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java @@ -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, 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); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java new file mode 100644 index 0000000000..ee6211341b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java @@ -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, 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; + } + +} From f9078c947fb5ef1fb4627015f2f5e355294c085e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 22 Apr 2013 17:59:19 -0400 Subject: [PATCH 26/51] Add WebSocketMessage and WebSocketHandler sub-interfcs There is now a WebSocketMessage type with TextMessage and BinaryMessage sub-types. WebSocketHandler is also sub-divided into TextMessageHandler and BinaryMessageHandler, so that applications can choose to handle text, binary, or both. Also in this commit, the SockJsHandler and SockJsSession interfaces have been removed. SockJsService now accepts WebSocketHandler. --- ...upport.java => AbstractSockJsSession.java} | 51 ++++--- .../springframework/sockjs/SockJsHandler.java | 51 ------- .../springframework/sockjs/SockJsSession.java | 62 -------- .../sockjs/SockJsSessionFactory.java | 7 +- .../server/AbstractServerSockJsSession.java | 34 +++-- .../sockjs/server/AbstractSockJsService.java | 12 +- .../server/ConfigurableTransportHandler.java | 11 -- .../sockjs/server/SockJsService.java | 12 +- .../sockjs/server/TransportHandler.java | 6 +- .../server/support/DefaultSockJsService.java | 65 ++------- .../support/SockJsHttpRequestHandler.java | 22 ++- ...AbstractHttpReceivingTransportHandler.java | 8 +- .../AbstractHttpSendingTransportHandler.java | 14 +- .../AbstractHttpServerSockJsSession.java | 16 ++- .../AbstractStreamingTransportHandler.java | 6 +- .../JsonpPollingTransportHandler.java | 6 +- .../transport/JsonpTransportHandler.java | 4 +- .../transport/PollingServerSockJsSession.java | 14 +- .../transport/SockJsWebSocketHandler.java | 60 ++++---- .../StreamingServerSockJsSession.java | 10 +- .../WebSocketSockJsHandlerAdapter.java | 132 ------------------ .../transport/WebSocketTransportHandler.java | 54 +------ .../transport/XhrPollingTransportHandler.java | 6 +- .../websocket/BinaryMessage.java | 85 +++++++++++ .../BinaryMessageHandler.java} | 28 ++-- .../websocket/PartialMessageHandler.java | 28 ++++ ...HandshakeRequest.java => TextMessage.java} | 25 ++-- .../websocket/TextMessageHandler.java | 40 ++++++ .../websocket/WebSocketHandler.java | 16 +-- .../websocket/WebSocketHandlerAdapter.java | 20 ++- .../websocket/WebSocketMessage.java | 61 ++++++++ .../websocket/WebSocketSession.java | 16 +-- .../endpoint/StandardWebSocketSession.java | 27 ++-- .../endpoint/WebSocketHandlerEndpoint.java | 105 +++++++++----- .../server/DefaultHandshakeHandler.java | 6 - .../websocket/server/HandshakeHandler.java | 10 -- .../server/RequestUpgradeStrategy.java | 10 -- .../AbstractEndpointUpgradeStrategy.java | 32 +---- .../support/WebSocketHttpRequestHandler.java | 7 +- 39 files changed, 513 insertions(+), 666 deletions(-) rename spring-websocket/src/main/java/org/springframework/sockjs/{SockJsSessionSupport.java => AbstractSockJsSession.java} (74%) delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java rename spring-websocket/src/main/java/org/springframework/{sockjs/SockJsHandlerAdapter.java => websocket/BinaryMessageHandler.java} (52%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java rename spring-websocket/src/main/java/org/springframework/websocket/{WebSocketHandshakeRequest.java => TextMessage.java} (63%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java similarity index 74% rename from spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java rename to spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 3cf7e61fc1..1f3e82541e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionSupport.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -16,12 +16,16 @@ package org.springframework.sockjs; -import java.io.IOException; +import java.net.URI; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.TextMessageHandler; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; /** @@ -30,13 +34,13 @@ import org.springframework.websocket.CloseStatus; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class SockJsSessionSupport implements SockJsSession { +public abstract class AbstractSockJsSession implements WebSocketSession { protected Log logger = LogFactory.getLog(this.getClass()); private final String sessionId; - private final SockJsHandler sockJsHandler; + private final TextMessageHandler handler; private State state = State.NEW; @@ -48,19 +52,32 @@ public abstract class SockJsSessionSupport implements SockJsSession { /** * * @param sessionId - * @param sockJsHandler the recipient of SockJS messages + * @param handler the recipient of SockJS messages */ - public SockJsSessionSupport(String sessionId, SockJsHandler sockJsHandler) { + public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(sessionId, "sessionId is required"); - Assert.notNull(sockJsHandler, "sockJsHandler is required"); + Assert.notNull(webSocketHandler, "webSocketHandler is required"); + Assert.isInstanceOf(TextMessageHandler.class, webSocketHandler, "Expected a TextMessageHandler"); this.sessionId = sessionId; - this.sockJsHandler = sockJsHandler; + this.handler = (TextMessageHandler) webSocketHandler; } public String getId() { return this.sessionId; } + @Override + public boolean isSecure() { + // TODO + return false; + } + + @Override + public URI getURI() { + // TODO + return null; + } + public boolean isNew() { return State.NEW.equals(this.state); } @@ -104,26 +121,26 @@ public abstract class SockJsSessionSupport implements SockJsSession { public void delegateConnectionEstablished() throws Exception { this.state = State.OPEN; - this.sockJsHandler.afterConnectionEstablished(this); + this.handler.afterConnectionEstablished(this); } public void delegateMessages(String[] messages) throws Exception { for (String message : messages) { - this.sockJsHandler.handleMessage(message, this); + this.handler.handleTextMessage(new TextMessage(message), this); } } public void delegateError(Throwable ex) { - this.sockJsHandler.handleError(ex, this); + this.handler.handleError(ex, this); } /** * Invoked in reaction to the underlying connection being closed by the remote side * (or the WebSocket container) in order to perform cleanup and notify the - * {@link SockJsHandler}. This is in contrast to {@link #close()} that pro-actively + * {@link TextMessageHandler}. This is in contrast to {@link #close()} that pro-actively * closes the connection. */ - public final void delegateConnectionClosed(CloseStatus status) { + public final void delegateConnectionClosed(CloseStatus status) throws Exception { if (!isClosed()) { if (logger.isDebugEnabled()) { logger.debug(this + " was closed, " + status); @@ -133,7 +150,7 @@ public abstract class SockJsSessionSupport implements SockJsSession { } finally { this.state = State.CLOSED; - this.sockJsHandler.afterConnectionClosed(status, this); + this.handler.afterConnectionClosed(status, this); } } } @@ -145,7 +162,7 @@ public abstract class SockJsSessionSupport implements SockJsSession { * {@inheritDoc} *

Performs cleanup and notifies the {@link SockJsHandler}. */ - public final void close() throws IOException { + public final void close() throws Exception { close(CloseStatus.NORMAL); } @@ -153,7 +170,7 @@ public abstract class SockJsSessionSupport implements SockJsSession { * {@inheritDoc} *

Performs cleanup and notifies the {@link SockJsHandler}. */ - public final void close(CloseStatus status) throws IOException { + public final void close(CloseStatus status) throws Exception { if (!isClosed()) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this + ", " + status); @@ -163,12 +180,12 @@ public abstract class SockJsSessionSupport implements SockJsSession { } finally { this.state = State.CLOSED; - this.sockJsHandler.afterConnectionClosed(status, this); + this.handler.afterConnectionClosed(status, this); } } } - protected abstract void closeInternal(CloseStatus status) throws IOException; + protected abstract void closeInternal(CloseStatus status) throws Exception; @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java deleted file mode 100644 index e8c20e573f..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandler.java +++ /dev/null @@ -1,51 +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; - -import org.springframework.websocket.CloseStatus; - - - -/** - * A handler for SockJS messages. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public interface SockJsHandler { - - /** - * A new connection was opened and is ready for use. - */ - void afterConnectionEstablished(SockJsSession session) throws Exception; - - /** - * Handle an incoming message. - */ - void handleMessage(String message, SockJsSession session) throws Exception; - - /** - * TODO - */ - void handleError(Throwable exception, SockJsSession session); - - /** - * A connection has been closed. - */ - void afterConnectionClosed(CloseStatus status, SockJsSession session); - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java deleted file mode 100644 index e1e46155cd..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSession.java +++ /dev/null @@ -1,62 +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; - -import java.io.IOException; - -import org.springframework.websocket.CloseStatus; - - - -/** - * Allows sending SockJS messages as well as closing the underlying connection. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public interface SockJsSession { - - /** - * Return a unique SockJS session identifier. - */ - String getId(); - - /** - * Return whether the connection is still open. - */ - boolean isOpen(); - - /** - * Send a message. - */ - void sendMessage(String text) throws IOException; - - /** - * Close the underlying connection with status 1000, i.e. equivalent to: - *

-	 * session.close(CloseStatus.NORMAL);
-	 * 
- */ - void close() throws IOException; - - /** - * Close the underlying connection with the given close status. - */ - void close(CloseStatus status) throws IOException; - - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index 4e4950181d..c87ff5a7b9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -16,6 +16,9 @@ package org.springframework.sockjs; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; + /** * A factory for creating a SockJS session. @@ -23,8 +26,8 @@ package org.springframework.sockjs; * @author Rossen Stoyanchev * @since 4.0 */ -public interface SockJsSessionFactory{ +public interface SockJsSessionFactory{ - S createSession(String sessionId, SockJsHandler sockJsHandler); + S createSession(String sessionId, WebSocketHandler webSocketHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 04fc33409d..51004660a6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -22,11 +22,12 @@ import java.net.SocketException; import java.util.Date; import java.util.concurrent.ScheduledFuture; -import org.springframework.sockjs.SockJsHandler; -import org.springframework.sockjs.SockJsSession; -import org.springframework.sockjs.SockJsSessionSupport; +import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; /** @@ -36,15 +37,17 @@ import org.springframework.websocket.CloseStatus; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { +public abstract class AbstractServerSockJsSession extends AbstractSockJsSession { private final SockJsConfiguration sockJsConfig; private ScheduledFuture heartbeatTask; - public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, SockJsHandler sockJsHandler) { - super(sessionId, sockJsHandler); + public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, + WebSocketHandler webSocketHandler) { + + super(sessionId, webSocketHandler); this.sockJsConfig = config; } @@ -52,12 +55,13 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { return this.sockJsConfig; } - public final synchronized void sendMessage(String message) throws IOException { + public final synchronized void sendMessage(WebSocketMessage message) throws Exception { Assert.isTrue(!isClosed(), "Cannot send a message, session has been closed"); - sendMessageInternal(message); + Assert.isInstanceOf(TextMessage.class, message, "Expected text message: " + message); + sendMessageInternal(((TextMessage) message).getPayload()); } - protected abstract void sendMessageInternal(String message) throws IOException; + protected abstract void sendMessageInternal(String message) throws Exception; @Override @@ -67,7 +71,7 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { } @Override - public final synchronized void closeInternal(CloseStatus status) throws IOException { + public final synchronized void closeInternal(CloseStatus status) throws Exception { if (isActive()) { // TODO: deliver messages "in flight" before sending close frame try { @@ -84,13 +88,13 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { } // TODO: close status/reason - protected abstract void disconnect(CloseStatus status) throws IOException; + protected abstract void disconnect(CloseStatus status) throws Exception; /** * For internal use within a TransportHandler and the (TransportHandler-specific) * session sub-class. */ - protected void writeFrame(SockJsFrame frame) throws IOException { + protected void writeFrame(SockJsFrame frame) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Preparing to write " + frame); } @@ -114,9 +118,9 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { } } - protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException; + protected abstract void writeFrameInternal(SockJsFrame frame) throws Exception; - public synchronized void sendHeartbeat() throws IOException { + public synchronized void sendHeartbeat() throws Exception { if (isActive()) { writeFrame(SockJsFrame.heartbeatFrame()); scheduleHeartbeat(); @@ -135,7 +139,7 @@ public abstract class AbstractServerSockJsSession extends SockJsSessionSupport { try { sendHeartbeat(); } - catch (IOException e) { + catch (Exception e) { // ignore } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 6d84713727..24c3dacb06 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -34,12 +34,12 @@ 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.SockJsHandler; 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.WebSocketHandler; /** @@ -218,7 +218,7 @@ public abstract class AbstractSockJsService * @throws Exception */ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, SockJsHandler sockJsHandler) throws Exception { + String sockJsPath, WebSocketHandler webSocketHandler) throws Exception { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -244,7 +244,7 @@ public abstract class AbstractSockJsService return; } else if (sockJsPath.equals("/websocket")) { - handleRawWebSocketRequest(request, response, sockJsHandler); + handleRawWebSocketRequest(request, response, webSocketHandler); return; } @@ -264,7 +264,7 @@ public abstract class AbstractSockJsService return; } - handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), sockJsHandler); + handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), webSocketHandler); } finally { response.flush(); @@ -272,10 +272,10 @@ public abstract class AbstractSockJsService } protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsHandler sockJsHandler) throws Exception; + WebSocketHandler webSocketHandler) throws Exception; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, SockJsHandler sockJsHandler) throws Exception; + String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws Exception; protected boolean validateRequest(String serverId, String sessionId, String transport) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java index 8ad26ef6fa..799ba50d89 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java @@ -16,11 +16,6 @@ package org.springframework.sockjs.server; -import java.util.Collection; - -import org.springframework.sockjs.SockJsHandler; -import org.springframework.websocket.WebSocketHandler; - /** * @@ -31,10 +26,4 @@ public interface ConfigurableTransportHandler extends TransportHandler { void setSockJsConfiguration(SockJsConfiguration sockJsConfig); - /** - * Pre-register {@link SockJsHandler} instances so they can be adapted to - * {@link WebSocketHandler} and hence re-used at runtime. - */ - void registerSockJsHandlers(Collection sockJsHandlers); - } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 23c79be7d2..604ddcbca1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -16,11 +16,8 @@ package org.springframework.sockjs.server; -import java.util.Collection; - import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.SockJsHandler; import org.springframework.websocket.WebSocketHandler; @@ -31,15 +28,8 @@ import org.springframework.websocket.WebSocketHandler; */ public interface SockJsService { - /** - * Pre-register {@link SockJsHandler} instances so they can be adapted to - * {@link WebSocketHandler} and hence re-used at runtime when - * {@link #handleRequest(ServerHttpRequest, ServerHttpResponse, String, SockJsHandler) handleRequest} - * is called. - */ - void registerSockJsHandlers(Collection sockJsHandlers); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, - SockJsHandler handler) throws Exception; + WebSocketHandler webSocketHandler) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index ea6208b836..8c662127d6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -17,8 +17,8 @@ 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.AbstractSockJsSession; +import org.springframework.websocket.WebSocketHandler; /** @@ -31,6 +31,6 @@ public interface TransportHandler { TransportType getTransportType(); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception; + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 60f8143f70..aa35115fe6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -16,7 +16,6 @@ package org.springframework.sockjs.server.support; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,9 +28,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.scheduling.TaskScheduler; -import org.springframework.sockjs.SockJsHandler; +import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.SockJsSessionFactory; -import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.AbstractSockJsService; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.TransportHandler; @@ -40,7 +38,6 @@ 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.WebSocketSockJsHandlerAdapter; import org.springframework.sockjs.server.transport.WebSocketTransportHandler; import org.springframework.sockjs.server.transport.XhrPollingTransportHandler; import org.springframework.sockjs.server.transport.XhrStreamingTransportHandler; @@ -65,9 +62,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi private TaskSchedulerHolder sessionTimeoutSchedulerHolder; - private final Map sessions = new ConcurrentHashMap(); - - private final Map sockJsHandlers = new HashMap(); + private final Map sessions = new ConcurrentHashMap(); public DefaultSockJsService() { @@ -93,22 +88,6 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi } } - public void registerSockJsHandlers(Collection sockJsHandlers) { - for (SockJsHandler sockJsHandler : sockJsHandlers) { - if (!this.sockJsHandlers.containsKey(sockJsHandler)) { - this.sockJsHandlers.put(sockJsHandler, adaptSockJsHandler(sockJsHandler)); - } - } - configureTransportHandlers(); - } - - /** - * Adapt the {@link SockJsHandler} to the {@link WebSocketHandler} contract for - * raw WebSocket communication on SockJS path "/websocket". - */ - protected WebSocketSockJsHandlerAdapter adaptSockJsHandler(SockJsHandler sockJsHandler) { - return new WebSocketSockJsHandlerAdapter(this, sockJsHandler); - } @Override public void afterPropertiesSet() throws Exception { @@ -135,7 +114,11 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi } } - configureTransportHandlers(); + for (TransportHandler h : this.transportHandlers.values()) { + if (h instanceof ConfigurableTransportHandler) { + ((ConfigurableTransportHandler) h).setSockJsConfiguration(this); + } + } this.sessionTimeoutSchedulerHolder.initialize(); @@ -146,7 +129,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi if (logger.isTraceEnabled() && (count != 0)) { logger.trace("Checking " + count + " session(s) for timeouts [" + getName() + "]"); } - for (SockJsSessionSupport session : sessions.values()) { + for (AbstractSockJsSession session : sessions.values()) { if (session.getTimeSinceLastActive() > getDisconnectDelay()) { if (logger.isTraceEnabled()) { logger.trace("Removing " + session + " for [" + getName() + "]"); @@ -172,32 +155,14 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi this.sessionTimeoutSchedulerHolder.destroy(); } - private void configureTransportHandlers() { - for (TransportHandler h : this.transportHandlers.values()) { - if (h instanceof ConfigurableTransportHandler) { - ((ConfigurableTransportHandler) h).setSockJsConfiguration(this); - if (!this.sockJsHandlers.isEmpty()) { - ((ConfigurableTransportHandler) h).registerSockJsHandlers(this.sockJsHandlers.keySet()); - if (h instanceof HandshakeHandler) { - ((HandshakeHandler) h).registerWebSocketHandlers(this.sockJsHandlers.values()); - } - } - } - } - } - @Override protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsHandler sockJsHandler) throws Exception { + WebSocketHandler webSocketHandler) throws Exception { if (isWebSocketEnabled()) { TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); if (transportHandler != null) { if (transportHandler instanceof HandshakeHandler) { - WebSocketHandler webSocketHandler = this.sockJsHandlers.get(sockJsHandler); - if (webSocketHandler == null) { - webSocketHandler = adaptSockJsHandler(sockJsHandler); - } ((HandshakeHandler) transportHandler).doHandshake(request, response, webSocketHandler); return; } @@ -209,7 +174,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, SockJsHandler sockJsHandler) throws Exception { + String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws Exception { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -236,7 +201,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi return; } - SockJsSessionSupport session = getSockJsSession(sessionId, sockJsHandler, transportHandler); + AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler, transportHandler); if (session != null) { if (transportType.setsNoCacheHeader()) { @@ -255,13 +220,13 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi } } - transportHandler.handleRequest(request, response, sockJsHandler, session); + transportHandler.handleRequest(request, response, webSocketHandler, session); } - public SockJsSessionSupport getSockJsSession(String sessionId, SockJsHandler sockJsHandler, + public AbstractSockJsSession getSockJsSession(String sessionId, WebSocketHandler webSocketHandler, TransportHandler transportHandler) { - SockJsSessionSupport session = this.sessions.get(sessionId); + AbstractSockJsSession session = this.sessions.get(sessionId); if (session != null) { return session; } @@ -275,7 +240,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi return session; } logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = (SockJsSessionSupport) sessionFactory.createSession(sessionId, sockJsHandler); + session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, webSocketHandler); this.sessions.put(sessionId, session); return session; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index 22d058e0eb..b5e2b49caf 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -17,7 +17,6 @@ package org.springframework.sockjs.server.support; import java.io.IOException; -import java.util.Collections; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -30,13 +29,13 @@ import org.springframework.http.server.AsyncServletServerHttpRequest; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.server.SockJsService; import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; import org.springframework.web.util.UrlPathHelper; import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.WebSocketHandler; /** @@ -50,7 +49,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory private final SockJsService sockJsService; - private final HandlerProvider handlerProvider; + private final HandlerProvider handlerProvider; private final UrlPathHelper urlPathHelper = new UrlPathHelper(); @@ -62,16 +61,15 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory * that begins with the specified prefix will be handled by this service. In a * Servlet container this is the path within the current servlet mapping. */ - public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, SockJsHandler sockJsHandler) { + public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler webSocketHandler) { Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(sockJsHandler, "sockJsHandler is required"); + Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.prefix = prefix; this.sockJsService = sockJsService; - this.sockJsService.registerSockJsHandlers(Collections.singleton(sockJsHandler)); - this.handlerProvider = new HandlerProvider(sockJsHandler); + this.handlerProvider = new HandlerProvider(webSocketHandler); } /** @@ -82,15 +80,15 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory * Servlet container this is the path within the current servlet mapping. */ public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, - Class sockJsHandlerClass) { + Class webSocketHandlerClass) { Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(sockJsHandlerClass, "sockJsHandlerClass is required"); + Assert.notNull(webSocketHandlerClass, "webSocketHandlerClass is required"); this.prefix = prefix; this.sockJsService = sockJsService; - this.handlerProvider = new HandlerProvider(sockJsHandlerClass); + this.handlerProvider = new HandlerProvider(webSocketHandlerClass); } public String getPrefix() { @@ -121,8 +119,8 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - SockJsHandler sockJsHandler = this.handlerProvider.getHandler(); - this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, sockJsHandler); + WebSocketHandler webSocketHandler = this.handlerProvider.getHandler(); + this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, webSocketHandler); } catch (Exception ex) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 300adbd981..8b7e3604a9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -25,9 +25,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.SockJsSessionSupport; +import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.TransportHandler; +import org.springframework.websocket.WebSocketHandler; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -53,7 +53,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception { if (session == null) { response.setStatusCode(HttpStatus.NOT_FOUND); @@ -64,7 +64,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport } protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - SockJsSessionSupport session) throws Exception { + AbstractSockJsSession session) throws Exception { String[] messages = null; try { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 726fa76c87..978663598c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -16,20 +16,19 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; -import java.util.Collection; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.AbstractSockJsSession; import org.springframework.sockjs.SockJsSessionFactory; -import org.springframework.sockjs.SockJsSessionSupport; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.websocket.WebSocketHandler; /** * TODO @@ -38,7 +37,7 @@ import org.springframework.sockjs.server.SockJsFrame.FrameFormat; * @since 4.0 */ public abstract class AbstractHttpSendingTransportHandler - implements ConfigurableTransportHandler, SockJsSessionFactory { + implements ConfigurableTransportHandler, SockJsSessionFactory { protected final Log logger = LogFactory.getLog(this.getClass()); @@ -50,18 +49,13 @@ public abstract class AbstractHttpSendingTransportHandler this.sockJsConfig = sockJsConfig; } - @Override - public void registerSockJsHandlers(Collection sockJsHandlers) { - // ignore - } - public SockJsConfiguration getSockJsConfig() { return this.sockJsConfig; } @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception { // Set content type before writing response.getHeaders().setContentType(getContentType()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 0c9d624f56..e87006958b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -22,7 +22,6 @@ 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.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -30,6 +29,7 @@ import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportHandler; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.WebSocketHandler; /** * An abstract base class for use with HTTP-based transports. @@ -48,8 +48,10 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock private ServerHttpResponse response; - public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - super(sessionId, sockJsConfig, sockJsHandler); + public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, + WebSocketHandler webSocketHandler) { + + super(sessionId, sockJsConfig, webSocketHandler); } public void setFrameFormat(FrameFormat frameFormat) { @@ -57,7 +59,7 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock } public synchronized void setCurrentRequest(ServerHttpRequest request, ServerHttpResponse response, - FrameFormat frameFormat) throws IOException { + FrameFormat frameFormat) throws Exception { if (isClosed()) { logger.debug("connection already closed"); @@ -90,14 +92,14 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock return this.response; } - protected final synchronized void sendMessageInternal(String message) throws IOException { + protected final synchronized void sendMessageInternal(String message) throws Exception { // assert close() was not called // threads: TH-Session-Endpoint or any other thread this.messageCache.add(message); tryFlushCache(); } - private void tryFlushCache() throws IOException { + private void tryFlushCache() throws Exception { if (isActive() && !getMessageCache().isEmpty()) { logger.trace("Flushing messages"); flushCache(); @@ -107,7 +109,7 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock /** * Only called if the connection is currently active */ - protected abstract void flushCache() throws IOException; + protected abstract void flushCache() throws Exception; @Override protected void disconnect(CloseStatus status) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java index f8dc637c16..0adfd867b1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -19,8 +19,8 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.SockJsHandler; import org.springframework.util.Assert; +import org.springframework.websocket.WebSocketHandler; /** @@ -33,9 +33,9 @@ public abstract class AbstractStreamingTransportHandler extends AbstractHttpSend @Override - public StreamingServerSockJsSession createSession(String sessionId, SockJsHandler sockJsHandler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), sockJsHandler); + return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index 23c9f07efb..fabd525393 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -21,13 +21,13 @@ 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.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; +import org.springframework.websocket.WebSocketHandler; /** @@ -50,9 +50,9 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingServerSockJsSession createSession(String sessionId, SockJsHandler sockJsHandler) { + public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new PollingServerSockJsSession(sessionId, getSockJsConfig(), sockJsHandler); + return new PollingServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index 95e90dccc0..8d39e2d125 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -21,7 +21,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.SockJsSessionSupport; +import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.TransportType; public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler { @@ -34,7 +34,7 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler @Override public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - SockJsSessionSupport sockJsSession) throws Exception { + AbstractSockJsSession sockJsSession) throws Exception { if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { if (request.getQueryParams().getFirst("d") == null) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index baac23d587..c9814f09f2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -15,21 +15,21 @@ */ package org.springframework.sockjs.server.transport; -import java.io.IOException; - -import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.websocket.WebSocketHandler; public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { - public PollingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - super(sessionId, sockJsConfig, sockJsHandler); + public PollingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, + WebSocketHandler webSocketHandler) { + + super(sessionId, sockJsConfig, webSocketHandler); } @Override - protected void flushCache() throws IOException { + protected void flushCache() throws Exception { cancelHeartbeat(); String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]); getMessageCache().clear(); @@ -37,7 +37,7 @@ public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession } @Override - protected void writeFrame(SockJsFrame frame) throws IOException { + protected void writeFrame(SockJsFrame frame) throws Exception { super.writeFrame(frame); resetRequest(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index e616455a2c..4e4dc0987e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -22,14 +22,15 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.sockjs.SockJsHandler; -import org.springframework.sockjs.SockJsSessionSupport; +import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.TextMessageHandler; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -43,55 +44,52 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsWebSocketHandler implements WebSocketHandler { +public class SockJsWebSocketHandler implements TextMessageHandler { private static final Log logger = LogFactory.getLog(SockJsWebSocketHandler.class); private final SockJsConfiguration sockJsConfig; - private final SockJsHandler sockJsHandler; + private final WebSocketHandler webSocketHandler; - private final Map sessions = - new ConcurrentHashMap(); + private final Map sessions = + new ConcurrentHashMap(); // TODO: JSON library used must be configurable private final ObjectMapper objectMapper = new ObjectMapper(); - public SockJsWebSocketHandler(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { + public SockJsWebSocketHandler(SockJsConfiguration sockJsConfig, WebSocketHandler webSocketHandler) { Assert.notNull(sockJsConfig, "sockJsConfig is required"); - Assert.notNull(sockJsHandler, "sockJsHandler is required"); + Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.sockJsConfig = sockJsConfig; - this.sockJsHandler = sockJsHandler; + this.webSocketHandler = webSocketHandler; } protected SockJsConfiguration getSockJsConfig() { return this.sockJsConfig; } - protected SockJsHandler getSockJsHandler() { - return this.sockJsHandler; - } - - protected SockJsSessionSupport getSockJsSession(WebSocketSession wsSession) { + protected AbstractSockJsSession getSockJsSession(WebSocketSession wsSession) { return this.sessions.get(wsSession); } @Override public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { - SockJsSessionSupport session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); + AbstractSockJsSession session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); this.sessions.put(wsSession, session); } @Override - public void handleTextMessage(String message, WebSocketSession wsSession) throws Exception { - if (StringUtils.isEmpty(message)) { + public void handleTextMessage(TextMessage message, WebSocketSession wsSession) throws Exception { + String payload = message.getPayload(); + if (StringUtils.isEmpty(payload)) { logger.trace("Ignoring empty message"); return; } try { - String[] messages = this.objectMapper.readValue(message, String[].class); - SockJsSessionSupport session = getSockJsSession(wsSession); + String[] messages = this.objectMapper.readValue(payload, String[].class); + AbstractSockJsSession session = getSockJsSession(wsSession); session.delegateMessages(messages); } catch (IOException e) { @@ -100,21 +98,15 @@ public class SockJsWebSocketHandler implements WebSocketHandler { } } - @Override - public void handleBinaryMessage(byte[] message, WebSocketSession session) throws Exception { - logger.warn("Unexpected binary message for " + session); - session.close(CloseStatus.NOT_ACCEPTABLE); - } - @Override public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) throws Exception { - SockJsSessionSupport session = this.sessions.remove(wsSession); + AbstractSockJsSession session = this.sessions.remove(wsSession); session.delegateConnectionClosed(status); } @Override public void handleError(Throwable exception, WebSocketSession webSocketSession) { - SockJsSessionSupport session = getSockJsSession(webSocketSession); + AbstractSockJsSession session = getSockJsSession(webSocketSession); session.delegateError(exception); } @@ -135,9 +127,10 @@ public class SockJsWebSocketHandler implements WebSocketHandler { public WebSocketServerSockJsSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) throws Exception { - super(getSockJsSessionId(wsSession), sockJsConfig, getSockJsHandler()); + super(getSockJsSessionId(wsSession), sockJsConfig, SockJsWebSocketHandler.this.webSocketHandler); this.wsSession = wsSession; - this.wsSession.sendTextMessage(SockJsFrame.openFrame().getContent()); + TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); + this.wsSession.sendMessage(message); scheduleHeartbeat(); delegateConnectionEstablished(); } @@ -148,22 +141,23 @@ public class SockJsWebSocketHandler implements WebSocketHandler { } @Override - public void sendMessageInternal(String message) throws IOException { + public void sendMessageInternal(String message) throws Exception { cancelHeartbeat(); writeFrame(SockJsFrame.messageFrame(message)); scheduleHeartbeat(); } @Override - protected void writeFrameInternal(SockJsFrame frame) throws IOException { + protected void writeFrameInternal(SockJsFrame frame) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Write " + frame); } - this.wsSession.sendTextMessage(frame.getContent()); + TextMessage message = new TextMessage(frame.getContent()); + this.wsSession.sendMessage(message); } @Override - protected void disconnect(CloseStatus status) throws IOException { + protected void disconnect(CloseStatus status) throws Exception { this.wsSession.close(status); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index e0293a7372..77cacca780 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -18,9 +18,9 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.SockJsHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.websocket.WebSocketHandler; public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSession { @@ -28,11 +28,13 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio private int byteCount; - public StreamingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - super(sessionId, sockJsConfig, sockJsHandler); + public StreamingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, + WebSocketHandler webSocketHandler) { + + super(sessionId, sockJsConfig, webSocketHandler); } - protected void flushCache() throws IOException { + protected void flushCache() throws Exception { cancelHeartbeat(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java deleted file mode 100644 index 12a5bcd282..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketSockJsHandlerAdapter.java +++ /dev/null @@ -1,132 +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 java.io.IOException; -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.SockJsHandler; -import org.springframework.sockjs.SockJsSessionSupport; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; - - -/** - * A plain {@link WebSocketHandler} to {@link SockJsHandler} adapter that merely delegates - * without any additional SockJS message framing. Used for raw WebSocket communication at - * SockJS path "/websocket". - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class WebSocketSockJsHandlerAdapter implements WebSocketHandler { - - private static final Log logger = LogFactory.getLog(WebSocketSockJsHandlerAdapter.class); - - private final SockJsConfiguration sockJsConfig; - - private final SockJsHandler sockJsHandler; - - private final Map sessions = - new ConcurrentHashMap(); - - - public WebSocketSockJsHandlerAdapter(SockJsConfiguration sockJsConfig, SockJsHandler sockJsHandler) { - Assert.notNull(sockJsConfig, "sockJsConfig is required"); - Assert.notNull(sockJsHandler, "sockJsHandler is required"); - this.sockJsConfig = sockJsConfig; - this.sockJsHandler = sockJsHandler; - } - - protected SockJsConfiguration getSockJsConfig() { - return this.sockJsConfig; - } - - protected SockJsHandler getSockJsHandler() { - return this.sockJsHandler; - } - - protected SockJsSessionSupport getSockJsSession(WebSocketSession wsSession) { - return this.sessions.get(wsSession); - } - - @Override - public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { - SockJsSessionSupport session = new SockJsWebSocketSessionAdapter(wsSession); - this.sessions.put(wsSession, session); - } - - @Override - public void handleTextMessage(String message, WebSocketSession wsSession) throws Exception { - SockJsSessionSupport session = getSockJsSession(wsSession); - session.delegateMessages(new String[] { message }); - } - - @Override - public void handleBinaryMessage(byte[] message, WebSocketSession session) throws Exception { - logger.warn("Unexpected binary message for " + session); - session.close(CloseStatus.NOT_ACCEPTABLE); - } - - @Override - public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) throws Exception { - SockJsSessionSupport session = this.sessions.remove(wsSession); - session.delegateConnectionClosed(status); - } - - @Override - public void handleError(Throwable exception, WebSocketSession wsSession) { - logger.error("Error for " + wsSession); - SockJsSessionSupport session = getSockJsSession(wsSession); - session.delegateError(exception); - } - - - private class SockJsWebSocketSessionAdapter extends SockJsSessionSupport { - - private final WebSocketSession wsSession; - - - public SockJsWebSocketSessionAdapter(WebSocketSession wsSession) throws Exception { - super(wsSession.getId(), getSockJsHandler()); - this.wsSession = wsSession; - delegateConnectionEstablished(); - } - - @Override - public boolean isActive() { - return this.wsSession.isOpen(); - } - - @Override - public void sendMessage(String message) throws IOException { - this.wsSession.sendTextMessage(message); - } - - @Override - public void closeInternal(CloseStatus status) throws IOException { - this.wsSession.close(status); - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 32702d00d9..836e7331f5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -16,17 +16,9 @@ package org.springframework.sockjs.server.transport; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - 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.AbstractSockJsSession; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.TransportHandler; @@ -50,10 +42,6 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, private SockJsConfiguration sockJsConfig; - private final Map sockJsHandlerCache = new HashMap(); - - private final Collection rawWebSocketHandlerCache = new ArrayList(); - public WebSocketTransportHandler(HandshakeHandler handshakeHandler) { Assert.notNull(handshakeHandler, "handshakeHandler is required"); @@ -71,50 +59,22 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, } @Override - public void registerSockJsHandlers(Collection sockJsHandlers) { - this.sockJsHandlerCache.clear(); - for (SockJsHandler sockJsHandler : sockJsHandlers) { - this.sockJsHandlerCache.put(sockJsHandler, adaptSockJsHandler(sockJsHandler)); - } - this.handshakeHandler.registerWebSocketHandlers(getAllWebSocketHandlers()); + public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception { + + this.handshakeHandler.doHandshake(request, response, adaptSockJsHandler(webSocketHandler)); } /** * Adapt the {@link SockJsHandler} to the {@link WebSocketHandler} contract for * exchanging SockJS message over WebSocket. */ - protected WebSocketHandler adaptSockJsHandler(SockJsHandler sockJsHandler) { - return new SockJsWebSocketHandler(this.sockJsConfig, sockJsHandler); - } - - private Collection getAllWebSocketHandlers() { - Set handlers = new HashSet(); - handlers.addAll(this.sockJsHandlerCache.values()); - handlers.addAll(this.rawWebSocketHandlerCache); - return handlers; - } - - @Override - public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - SockJsHandler sockJsHandler, SockJsSessionSupport session) throws Exception { - - WebSocketHandler webSocketHandler = this.sockJsHandlerCache.get(sockJsHandler); - if (webSocketHandler == null) { - webSocketHandler = adaptSockJsHandler(sockJsHandler); - } - - this.handshakeHandler.doHandshake(request, response, webSocketHandler); + protected WebSocketHandler adaptSockJsHandler(WebSocketHandler handler) { + return new SockJsWebSocketHandler(this.sockJsConfig, handler); } // HandshakeHandler methods - @Override - public void registerWebSocketHandlers(Collection webSocketHandlers) { - this.rawWebSocketHandlerCache.clear(); - this.rawWebSocketHandlerCache.addAll(webSocketHandlers); - this.handshakeHandler.registerWebSocketHandlers(getAllWebSocketHandlers()); - } - @Override public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) throws Exception { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index e424bac91a..57b5fd4e94 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -19,11 +19,11 @@ 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.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; +import org.springframework.websocket.WebSocketHandler; /** @@ -50,9 +50,9 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingServerSockJsSession createSession(String sessionId, SockJsHandler sockJsHandler) { + public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new PollingServerSockJsSession(sessionId, getSockJsConfig(), sockJsHandler); + return new PollingServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java new file mode 100644 index 0000000000..5f136bc31e --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java @@ -0,0 +1,85 @@ +/* + * 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.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; + + +/** + * Represents a binary WebSocket message. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public final class BinaryMessage extends WebSocketMessage { + + private final byte[] bytes; + + private final boolean last; + + + public BinaryMessage(ByteBuffer payload) { + this(payload, true); + } + + public BinaryMessage(ByteBuffer payload, boolean isLast) { + super(payload); + this.bytes = null; + this.last = isLast; + } + + public BinaryMessage(byte[] payload) { + this(payload, true); + } + + public BinaryMessage(byte[] payload, boolean isLast) { + super((payload != null) ? ByteBuffer.wrap(payload) : null); + this.bytes = payload; + this.last = isLast; + } + + public boolean isLast() { + return this.last; + } + + public byte[] getByteArray() { + if (this.bytes != null) { + return this.bytes; + } + else if (getPayload() != null){ + byte[] result = new byte[getPayload().remaining()]; + getPayload().get(result); + return result; + } + else { + return null; + } + } + + public InputStream getInputStream() { + byte[] array = getByteArray(); + return (array != null) ? new ByteArrayInputStream(array) : null; + } + + @Override + public String toString() { + int size = (getPayload() != null) ? getPayload().remaining() : 0; + return "WebSocket binary message size=" + size; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java similarity index 52% rename from spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java index 3523f6c0dc..50940e34da 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java @@ -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,33 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.springframework.websocket; -package org.springframework.sockjs; - -import org.springframework.websocket.CloseStatus; /** + * A handler for WebSocket binary messages. * * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsHandlerAdapter implements SockJsHandler { +public interface BinaryMessageHandler extends WebSocketHandler { - @Override - public void afterConnectionEstablished(SockJsSession session) throws Exception { - } - @Override - public void handleMessage(String message, SockJsSession session) throws Exception { - } - - @Override - public void handleError(Throwable exception, SockJsSession session) { - } - - @Override - public void afterConnectionClosed(CloseStatus status, SockJsSession session) { - } + /** + * Handle an incoming binary message. + */ + void handleBinaryMessage(BinaryMessage message, WebSocketSession session) + throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java new file mode 100644 index 0000000000..7752baf6cc --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java @@ -0,0 +1,28 @@ +/* + * 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; + + +/** + * A "marker" interface for {@link BinaryMessageHandler} types that wish to handle partial + * messages. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface PartialMessageHandler { + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java b/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java similarity index 63% rename from spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java rename to spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java index 762cc2fe8b..b254ae7f80 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandshakeRequest.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java @@ -15,34 +15,25 @@ */ package org.springframework.websocket; -import java.net.URI; - -import org.springframework.http.HttpHeaders; +import java.io.Reader; +import java.io.StringReader; /** + * Represents a text WebSocket message. * * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketHandshakeRequest { - - private final URI uri; - - private final HttpHeaders headers; +public final class TextMessage extends WebSocketMessage { - public WebSocketHandshakeRequest(HttpHeaders headers, URI uri) { - this.headers = HttpHeaders.readOnlyHttpHeaders(headers); - this.uri = uri; + public TextMessage(String payload) { + super(payload); } - public URI getUri() { - return this.uri; - } - - public HttpHeaders getHeaders() { - return this.headers; + public Reader getReader() { + return new StringReader(getPayload()); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java new file mode 100644 index 0000000000..ea073eaf3d --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java @@ -0,0 +1,40 @@ +/* + * 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 org.springframework.websocket.WebSocketHandlerAdapter.TextAndBinaryMessageHandlerAdapter; +import org.springframework.websocket.WebSocketHandlerAdapter.TextMessageHandlerAdapter; + + +/** + * A handler for WebSocket text messages. + * + * @author Rossen Stoyanchev + * @since 4.0 + * + * @see TextMessageHandlerAdapter + * @see TextAndBinaryMessageHandlerAdapter + */ +public interface TextMessageHandler extends WebSocketHandler { + + + /** + * Handle an incoming text message. + */ + void handleTextMessage(TextMessage message, WebSocketSession session) + throws Exception; + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 783e9b66d1..b420e83940 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -19,7 +19,7 @@ package org.springframework.websocket; /** - * A handler for WebSocket messages. + * A handler for WebSocket sessions. * * @author Rossen Stoyanchev * @since 4.0 @@ -32,23 +32,13 @@ public interface WebSocketHandler { void afterConnectionEstablished(WebSocketSession session) throws Exception; /** - * Handle an incoming text message. + * A WebSocket connection has been closed. */ - void handleTextMessage(String message, WebSocketSession session) throws Exception; - - /** - * Handle an incoming binary message. - */ - void handleBinaryMessage(byte[] bytes, WebSocketSession session) throws Exception; + void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session) throws Exception; /** * TODO */ void handleError(Throwable exception, WebSocketSession session); - /** - * A WebSocket connection has been closed. - */ - void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session) throws Exception; - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index af03f63b1c..4914f3767a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -18,6 +18,7 @@ package org.springframework.websocket; /** + * A {@link WebSocketHandler} with empty methods. * * @author Rossen Stoyanchev * @since 4.0 @@ -29,19 +30,24 @@ public class WebSocketHandlerAdapter implements WebSocketHandler { } @Override - public void handleTextMessage(String message, WebSocketSession session) throws Exception { - } - - @Override - public void handleBinaryMessage(byte[] message, WebSocketSession session) throws Exception { + public void afterConnectionClosed(CloseStatus status, WebSocketSession session) throws Exception { } @Override public void handleError(Throwable exception, WebSocketSession session) { } - @Override - public void afterConnectionClosed(CloseStatus status, WebSocketSession session) throws Exception { + + public static abstract class TextMessageHandlerAdapter + extends WebSocketHandlerAdapter implements TextMessageHandler { + } + + public static abstract class BinaryMessageHandlerAdapter + extends WebSocketHandlerAdapter implements BinaryMessageHandler { + } + + public static abstract class TextAndBinaryMessageHandlerAdapter extends WebSocketHandlerAdapter + implements TextMessageHandler, BinaryMessageHandler { } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java new file mode 100644 index 0000000000..19b79eda74 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java @@ -0,0 +1,61 @@ +/* + * 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 org.springframework.util.ObjectUtils; + + +/** + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class WebSocketMessage { + + private final T payload; + + + WebSocketMessage(T payload) { + this.payload = payload; + } + + public T getPayload() { + return this.payload; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [payload=" + this.payload + "]"; + } + + @Override + public int hashCode() { + return WebSocketMessage.class.hashCode() * 13 + ObjectUtils.nullSafeHashCode(this.payload); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof WebSocketMessage)) { + return false; + } + WebSocketMessage otherMessage = (WebSocketMessage) other; + return ObjectUtils.nullSafeEquals(this.payload, otherMessage.payload); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index d1d3c27a35..2c9d4355f5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -16,9 +16,7 @@ package org.springframework.websocket; -import java.io.IOException; import java.net.URI; -import java.nio.ByteBuffer; @@ -51,14 +49,10 @@ public interface WebSocketSession { URI getURI(); /** - * Send a text message. + * Send a WebSocket message either {@link TextMessage} or + * {@link BinaryMessage}. */ - void sendTextMessage(String message) throws IOException; - - /** - * Send a binary message. - */ - void sendBinaryMessage(ByteBuffer message) throws IOException; + void sendMessage(WebSocketMessage message) throws Exception; /** * Close the WebSocket connection with status 1000, i.e. equivalent to: @@ -66,11 +60,11 @@ public interface WebSocketSession { * session.close(CloseStatus.NORMAL); * */ - void close() throws IOException; + void close() throws Exception; /** * Close the WebSocket connection with the given close status. */ - void close(CloseStatus status) throws IOException; + void close(CloseStatus status) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index 64f4d4d7fd..0da50203ee 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -18,15 +18,18 @@ package org.springframework.websocket.endpoint; import java.io.IOException; import java.net.URI; -import java.nio.ByteBuffer; import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.RemoteEndpoint.Basic; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; +import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; @@ -70,21 +73,21 @@ public class StandardWebSocketSession implements WebSocketSession { } @Override - public void sendTextMessage(String text) throws IOException { + public void sendMessage(WebSocketMessage message) throws IOException { if (logger.isTraceEnabled()) { - logger.trace("Sending text message: " + text + ", " + this); + logger.trace("Sending: " + message + ", " + this); } Assert.isTrue(isOpen(), "Cannot send message after connection closed."); - this.session.getBasicRemote().sendText(text); - } - - @Override - public void sendBinaryMessage(ByteBuffer message) throws IOException { - if (logger.isTraceEnabled()) { - logger.trace("Sending binary message, " + this); + Basic remote = this.session.getBasicRemote(); + if (message instanceof TextMessage) { + remote.sendText(((TextMessage) message).getPayload()); + } + else if (message instanceof BinaryMessage) { + remote.sendBinary(((BinaryMessage) message).getPayload()); + } + else { + throw new IllegalStateException("Unexpected WebSocketMessage type: " + message); } - Assert.isTrue(isOpen(), "Cannot send message after connection closed."); - this.session.getBasicRemote().sendBinary(message); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 7593adbfd0..82d75c098d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -28,8 +28,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.BinaryMessageHandler; +import org.springframework.websocket.PartialMessageHandler; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.TextMessageHandler; /** @@ -47,19 +52,50 @@ public class WebSocketHandlerEndpoint extends Endpoint { private final Map sessions = new ConcurrentHashMap(); - public WebSocketHandlerEndpoint(WebSocketHandler webSocketHandler) { - this.webSocketHandler = webSocketHandler; + public WebSocketHandlerEndpoint(WebSocketHandler handler) { + Assert.notNull(handler, "webSocketHandler is required"); + this.webSocketHandler = handler; } @Override - public void onOpen(javax.websocket.Session session, EndpointConfig config) { + public void onOpen(final javax.websocket.Session session, EndpointConfig config) { if (logger.isDebugEnabled()) { logger.debug("Client connected, WebSocket session id=" + session.getId() + ", uri=" + session.getRequestURI()); } try { WebSocketSession webSocketSession = new StandardWebSocketSession(session); this.sessions.put(session.getId(), webSocketSession); - session.addMessageHandler(new StandardMessageHandler(session)); + + if (this.webSocketHandler instanceof TextMessageHandler) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + handleTextMessage(session, message); + } + }); + } + else if (this.webSocketHandler instanceof BinaryMessageHandler) { + if (this.webSocketHandler instanceof PartialMessageHandler) { + session.addMessageHandler(new MessageHandler.Partial() { + @Override + public void onMessage(byte[] messagePart, boolean isLast) { + handleBinaryMessage(session, messagePart, isLast); + } + }); + } + else { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(byte[] message) { + handleBinaryMessage(session, message, true); + } + }); + } + } + else { + logger.warn("WebSocketHandler handles neither text nor binary messages: " + this.webSocketHandler); + } + this.webSocketHandler.afterConnectionEstablished(webSocketSession); } catch (Throwable ex) { @@ -68,6 +104,38 @@ public class WebSocketHandlerEndpoint extends Endpoint { } } + private void handleTextMessage(javax.websocket.Session session, String message) { + if (logger.isTraceEnabled()) { + logger.trace("Received message for WebSocket session id=" + session.getId() + ": " + message); + } + WebSocketSession wsSession = getWebSocketSession(session); + Assert.notNull(wsSession, "WebSocketSession not found"); + try { + TextMessage textMessage = new TextMessage(message); + ((TextMessageHandler) webSocketHandler).handleTextMessage(textMessage, wsSession); + } + catch (Throwable ex) { + // TODO + logger.error("Error while processing message", ex); + } + } + + private void handleBinaryMessage(javax.websocket.Session session, byte[] message, boolean isLast) { + if (logger.isTraceEnabled()) { + logger.trace("Received binary data for WebSocket session id=" + session.getId()); + } + WebSocketSession wsSession = getWebSocketSession(session); + Assert.notNull(wsSession, "WebSocketSession not found"); + try { + BinaryMessage binaryMessage = new BinaryMessage(message, isLast); + ((BinaryMessageHandler) webSocketHandler).handleBinaryMessage(binaryMessage, wsSession); + } + catch (Throwable ex) { + // TODO + logger.error("Error while processing message", ex); + } + } + @Override public void onClose(javax.websocket.Session session, CloseReason reason) { if (logger.isDebugEnabled()) { @@ -111,31 +179,4 @@ public class WebSocketHandlerEndpoint extends Endpoint { return this.sessions.get(session.getId()); } - - private class StandardMessageHandler implements MessageHandler.Whole { - - private final javax.websocket.Session session; - - public StandardMessageHandler(javax.websocket.Session session) { - this.session = session; - } - - @Override - public void onMessage(String message) { - if (logger.isTraceEnabled()) { - logger.trace("Received message for WebSocket session id=" + this.session.getId() + ": " + message); - } - WebSocketSession wsSession = getWebSocketSession(this.session); - Assert.notNull(wsSession, "WebSocketSession not found"); - try { - WebSocketHandlerEndpoint.this.webSocketHandler.handleTextMessage(message, wsSession); - } - catch (Throwable ex) { - // TODO - logger.error("Error while processing message", ex); - } - } - - } - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index cf4e4046f6..c7a3fdcb87 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -21,7 +21,6 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -86,11 +85,6 @@ public class DefaultHandshakeHandler implements HandshakeHandler { return this.supportedProtocols.toArray(new String[this.supportedProtocols.size()]); } - @Override - public void registerWebSocketHandlers(Collection handlers) { - this.requestUpgradeStrategy.registerWebSocketHandlers(handlers); - } - @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) throws Exception { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 8222e7258e..2af525c606 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -16,8 +16,6 @@ package org.springframework.websocket.server; -import java.util.Collection; - import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.WebSocketHandler; @@ -31,14 +29,6 @@ import org.springframework.websocket.WebSocketHandler; */ public interface HandshakeHandler { - /** - * Pre-register {@link WebSocketHandler} instances so they can be adapted to the - * underlying runtime and hence re-used at runtime when - * {@link #doHandshake(ServerHttpRequest, ServerHttpResponse, WebSocketHandler) doHandshake} - * is called. - */ - void registerWebSocketHandlers(Collection webSocketHandlers); - /** * * @param request the HTTP request diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index d0cf29fa17..81c238c423 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -16,8 +16,6 @@ package org.springframework.websocket.server; -import java.util.Collection; - import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.WebSocketHandler; @@ -37,14 +35,6 @@ public interface RequestUpgradeStrategy { */ String[] getSupportedVersions(); - /** - * Pre-register {@link WebSocketHandler} instances so they can be adapted to the - * underlying runtime and hence re-used at runtime when - * {@link #upgrade(ServerHttpRequest, ServerHttpResponse, String, WebSocketHandler) - * upgrade} is called. - */ - void registerWebSocketHandlers(Collection webSocketHandlers); - /** * Perform runtime specific steps to complete the upgrade. * Invoked only if the handshake is successful. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index 29b2372f91..b16f3d746d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -16,10 +16,6 @@ package org.springframework.websocket.server.support; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - import javax.websocket.Endpoint; import org.apache.commons.logging.Log; @@ -42,38 +38,18 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS protected final Log logger = LogFactory.getLog(getClass()); - private final Map webSocketHandlers = new HashMap(); - @Override - public void registerWebSocketHandlers(Collection webSocketHandlers) { - for (WebSocketHandler webSocketHandler : webSocketHandlers) { - if (!this.webSocketHandlers.containsKey(webSocketHandler)) { - this.webSocketHandlers.put(webSocketHandler, adaptWebSocketHandler(webSocketHandler)); - } - } + public void upgrade(ServerHttpRequest request, ServerHttpResponse response, + String protocol, WebSocketHandler webSocketHandler) throws Exception { + + upgradeInternal(request, response, protocol, adaptWebSocketHandler(webSocketHandler)); } protected Endpoint adaptWebSocketHandler(WebSocketHandler handler) { return new WebSocketHandlerEndpoint(handler); } - @Override - public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, WebSocketHandler webSocketHandler) throws Exception { - - Endpoint endpoint = this.webSocketHandlers.get(webSocketHandler); - if (endpoint == null) { - endpoint = adaptWebSocketHandler(webSocketHandler); - } - - if (logger.isTraceEnabled()) { - logger.trace("Upgrading with " + endpoint); - } - - upgradeInternal(request, response, protocol, endpoint); - } - protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, Endpoint endpoint) throws Exception; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 6e641eaba0..9418afc689 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -17,7 +17,6 @@ package org.springframework.websocket.server.support; import java.io.IOException; -import java.util.Collections; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -35,8 +34,8 @@ import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.server.HandshakeHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; +import org.springframework.websocket.server.HandshakeHandler; /** @@ -56,7 +55,6 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFact Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.handlerProvider = new HandlerProvider(webSocketHandler); this.handshakeHandler = new DefaultHandshakeHandler(); - this.handshakeHandler.registerWebSocketHandlers(Collections.singleton(webSocketHandler)); } public WebSocketHttpRequestHandler( Class webSocketHandlerClass) { @@ -67,9 +65,6 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFact public void setHandshakeHandler(HandshakeHandler handshakeHandler) { Assert.notNull(handshakeHandler, "handshakeHandler is required"); this.handshakeHandler = handshakeHandler; - if (this.handlerProvider.isSingleton()) { - this.handshakeHandler.registerWebSocketHandlers(Collections.singleton(this.handlerProvider.getHandler())); - } } @Override From 84089bf3968443e4f6bb9239dace0692734d6578 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 23 Apr 2013 11:48:10 -0400 Subject: [PATCH 27/51] Add HandlerProvider interface HandlerProvider is now an interface that can be used to plug in WebSocket handlers with per-connection scope semantics. There are two implementations, of the interface, one simple and a second that creates handler instances through AutowireCapableBeanFactory. HandlerProvider also provides a destroy method that is used to apply a destroy callback whenever a client connection closes. --- .../sockjs/AbstractSockJsSession.java | 21 ++++- .../sockjs/SockJsSessionFactory.java | 3 +- .../server/AbstractServerSockJsSession.java | 5 +- .../sockjs/server/AbstractSockJsService.java | 11 ++- .../sockjs/server/SockJsService.java | 3 +- .../sockjs/server/TransportHandler.java | 3 +- .../server/support/DefaultSockJsService.java | 18 ++-- .../support/SockJsHttpRequestHandler.java | 26 ++--- ...AbstractHttpReceivingTransportHandler.java | 3 +- .../AbstractHttpSendingTransportHandler.java | 3 +- .../AbstractHttpServerSockJsSession.java | 5 +- .../AbstractStreamingTransportHandler.java | 5 +- .../JsonpPollingTransportHandler.java | 5 +- .../transport/PollingServerSockJsSession.java | 5 +- .../transport/SockJsWebSocketHandler.java | 40 +++----- .../StreamingServerSockJsSession.java | 5 +- .../transport/WebSocketTransportHandler.java | 19 ++-- .../transport/XhrPollingTransportHandler.java | 5 +- .../websocket/HandlerProvider.java | 86 ++++------------- .../websocket/client/WebSocketClient.java | 10 +- .../client/WebSocketConnectionManager.java | 16 +++- .../AnnotatedEndpointConnectionManager.java | 23 +++-- .../endpoint/EndpointConnectionManager.java | 14 ++- .../endpoint/StandardWebSocketClient.java | 15 ++- .../endpoint/WebSocketHandlerEndpoint.java | 69 +++++--------- .../server/DefaultHandshakeHandler.java | 7 +- .../websocket/server/HandshakeHandler.java | 15 +-- .../server/RequestUpgradeStrategy.java | 5 +- .../server/endpoint/EndpointRegistration.java | 21 +++-- .../AbstractEndpointContainerFactoryBean.java | 88 ----------------- .../AbstractEndpointUpgradeStrategy.java | 7 +- .../support/WebSocketHttpRequestHandler.java | 22 ++--- .../support/BeanCreatingHandlerProvider.java | 94 +++++++++++++++++++ .../support/SimpleHandlerProvider.java | 61 ++++++++++++ 34 files changed, 371 insertions(+), 367 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 1f3e82541e..29791860a8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.TextMessageHandler; import org.springframework.websocket.WebSocketHandler; @@ -40,6 +41,8 @@ public abstract class AbstractSockJsSession implements WebSocketSession { private final String sessionId; + private final HandlerProvider handlerProvider; + private final TextMessageHandler handler; private State state = State.NEW; @@ -52,14 +55,17 @@ public abstract class AbstractSockJsSession implements WebSocketSession { /** * * @param sessionId - * @param handler the recipient of SockJS messages + * @param handlerProvider the recipient of SockJS messages */ - public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) { + public AbstractSockJsSession(String sessionId, HandlerProvider handlerProvider) { Assert.notNull(sessionId, "sessionId is required"); - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - Assert.isInstanceOf(TextMessageHandler.class, webSocketHandler, "Expected a TextMessageHandler"); + Assert.notNull(handlerProvider, "handlerProvider is required"); this.sessionId = sessionId; + + WebSocketHandler webSocketHandler = handlerProvider.getHandler(); + Assert.isInstanceOf(TextMessageHandler.class, webSocketHandler, "Expected a TextMessageHandler"); this.handler = (TextMessageHandler) webSocketHandler; + this.handlerProvider = handlerProvider; } public String getId() { @@ -180,7 +186,12 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - this.handler.afterConnectionClosed(status, this); + try { + this.handler.afterConnectionClosed(status, this); + } + finally { + this.handlerProvider.destroy(this.handler); + } } } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index c87ff5a7b9..4f6051c1f8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -16,6 +16,7 @@ package org.springframework.sockjs; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -28,6 +29,6 @@ import org.springframework.websocket.WebSocketSession; */ public interface SockJsSessionFactory{ - S createSession(String sessionId, WebSocketHandler webSocketHandler); + S createSession(String sessionId, HandlerProvider handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 51004660a6..0c179b9dd0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -25,6 +25,7 @@ import java.util.concurrent.ScheduledFuture; import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketMessage; @@ -45,9 +46,9 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, - WebSocketHandler webSocketHandler) { + HandlerProvider handler) { - super(sessionId, webSocketHandler); + super(sessionId, handler); this.sockJsConfig = config; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 24c3dacb06..0319ff41bc 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -39,6 +39,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.DigestUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -218,7 +219,7 @@ public abstract class AbstractSockJsService * @throws Exception */ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, WebSocketHandler webSocketHandler) throws Exception { + String sockJsPath, HandlerProvider handler) throws Exception { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -244,7 +245,7 @@ public abstract class AbstractSockJsService return; } else if (sockJsPath.equals("/websocket")) { - handleRawWebSocketRequest(request, response, webSocketHandler); + handleRawWebSocketRequest(request, response, handler); return; } @@ -264,7 +265,7 @@ public abstract class AbstractSockJsService return; } - handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), webSocketHandler); + handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), handler); } finally { response.flush(); @@ -272,10 +273,10 @@ public abstract class AbstractSockJsService } protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws Exception; + HandlerProvider handler) throws Exception; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws Exception; + String sessionId, TransportType transportType, HandlerProvider handler) throws Exception; protected boolean validateRequest(String serverId, String sessionId, String transport) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 604ddcbca1..eeef0913d1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -18,6 +18,7 @@ package org.springframework.sockjs.server; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -30,6 +31,6 @@ public interface SockJsService { void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, - WebSocketHandler webSocketHandler) throws Exception; + HandlerProvider handler) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 8c662127d6..1d0eedb766 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -18,6 +18,7 @@ package org.springframework.sockjs.server; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -31,6 +32,6 @@ public interface TransportHandler { TransportType getTransportType(); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception; + HandlerProvider handler, AbstractSockJsSession session) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index aa35115fe6..f54b540eb1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.beans.factory.InitializingBean; import org.springframework.http.Cookie; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -43,6 +42,7 @@ 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.Assert; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; @@ -54,7 +54,7 @@ import org.springframework.websocket.server.HandshakeHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public class DefaultSockJsService extends AbstractSockJsService implements InitializingBean { +public class DefaultSockJsService extends AbstractSockJsService { private final Map transportHandlers = new HashMap(); @@ -157,13 +157,13 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi @Override protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws Exception { + HandlerProvider handler) throws Exception { if (isWebSocketEnabled()) { TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); if (transportHandler != null) { if (transportHandler instanceof HandshakeHandler) { - ((HandshakeHandler) transportHandler).doHandshake(request, response, webSocketHandler); + ((HandshakeHandler) transportHandler).doHandshake(request, response, handler); return; } } @@ -174,7 +174,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws Exception { + String sessionId, TransportType transportType, HandlerProvider handler) throws Exception { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -201,7 +201,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi return; } - AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler, transportHandler); + AbstractSockJsSession session = getSockJsSession(sessionId, handler, transportHandler); if (session != null) { if (transportType.setsNoCacheHeader()) { @@ -220,10 +220,10 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi } } - transportHandler.handleRequest(request, response, webSocketHandler, session); + transportHandler.handleRequest(request, response, handler, session); } - public AbstractSockJsSession getSockJsSession(String sessionId, WebSocketHandler webSocketHandler, + public AbstractSockJsSession getSockJsSession(String sessionId, HandlerProvider handler, TransportHandler transportHandler) { AbstractSockJsSession session = this.sessions.get(sessionId); @@ -240,7 +240,7 @@ public class DefaultSockJsService extends AbstractSockJsService implements Initi return session; } logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, webSocketHandler); + session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, handler); this.sessions.put(sessionId, session); return session; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index b5e2b49caf..4b16cacb0f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -22,9 +22,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.http.server.AsyncServletServerHttpRequest; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -36,6 +33,7 @@ import org.springframework.web.util.NestedServletException; import org.springframework.web.util.UrlPathHelper; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -43,7 +41,7 @@ import org.springframework.websocket.WebSocketHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactoryAware { +public class SockJsHttpRequestHandler implements HttpRequestHandler { private final String prefix; @@ -61,15 +59,15 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory * that begins with the specified prefix will be handled by this service. In a * Servlet container this is the path within the current servlet mapping. */ - public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler webSocketHandler) { + public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler handler) { Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(webSocketHandler, "webSocketHandler is required"); + Assert.notNull(handler, "webSocketHandler is required"); this.prefix = prefix; this.sockJsService = sockJsService; - this.handlerProvider = new HandlerProvider(webSocketHandler); + this.handlerProvider = new SimpleHandlerProvider(handler); } /** @@ -80,15 +78,15 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory * Servlet container this is the path within the current servlet mapping. */ public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, - Class webSocketHandlerClass) { + HandlerProvider handlerProvider) { Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(webSocketHandlerClass, "webSocketHandlerClass is required"); + Assert.notNull(handlerProvider, "handlerProvider is required"); this.prefix = prefix; this.sockJsService = sockJsService; - this.handlerProvider = new HandlerProvider(webSocketHandlerClass); + this.handlerProvider = handlerProvider; } public String getPrefix() { @@ -99,11 +97,6 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory return this.prefix + "/**"; } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.handlerProvider.setBeanFactory(beanFactory); - } - @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -119,8 +112,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler, BeanFactory ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - WebSocketHandler webSocketHandler = this.handlerProvider.getHandler(); - this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, webSocketHandler); + this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, this.handlerProvider); } catch (Exception ex) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 8b7e3604a9..d3928730a4 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -27,6 +27,7 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.TransportHandler; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import com.fasterxml.jackson.databind.JsonMappingException; @@ -53,7 +54,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception { + HandlerProvider webSocketHandler, AbstractSockJsSession session) throws Exception { if (session == null) { response.setStatusCode(HttpStatus.NOT_FOUND); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 978663598c..e2218715b0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -28,6 +28,7 @@ import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -55,7 +56,7 @@ public abstract class AbstractHttpSendingTransportHandler @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception { + HandlerProvider webSocketHandler, AbstractSockJsSession session) throws Exception { // Set content type before writing response.getHeaders().setContentType(getContentType()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index e87006958b..72aa97e7c1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -29,6 +29,7 @@ import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportHandler; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -49,9 +50,9 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - WebSocketHandler webSocketHandler) { + HandlerProvider handler) { - super(sessionId, sockJsConfig, webSocketHandler); + super(sessionId, sockJsConfig, handler); } public void setFrameFormat(FrameFormat frameFormat) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java index 0adfd867b1..ed626a2961 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java @@ -20,6 +20,7 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.Assert; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -33,9 +34,9 @@ public abstract class AbstractStreamingTransportHandler extends AbstractHttpSend @Override - public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler); + return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index fabd525393..3f5854d30b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -27,6 +27,7 @@ import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -50,9 +51,9 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { + public PollingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new PollingServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler); + return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index c9814f09f2..86851ab9d5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -17,15 +17,16 @@ package org.springframework.sockjs.server.transport; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { public PollingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - WebSocketHandler webSocketHandler) { + HandlerProvider handler) { - super(sessionId, sockJsConfig, webSocketHandler); + super(sessionId, sockJsConfig, handler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 4e4dc0987e..0e50456957 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -17,8 +17,6 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,6 +27,7 @@ import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.TextMessageHandler; import org.springframework.websocket.WebSocketHandler; @@ -38,8 +37,8 @@ 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. + * A wrapper around a {@link WebSocketHandler} instance that parses and adds SockJS + * messages frames as well as sends SockJS heartbeat messages. * * @author Rossen Stoyanchev * @since 4.0 @@ -50,34 +49,28 @@ public class SockJsWebSocketHandler implements TextMessageHandler { private final SockJsConfiguration sockJsConfig; - private final WebSocketHandler webSocketHandler; + private final HandlerProvider handlerProvider; - private final Map sessions = - new ConcurrentHashMap(); + private AbstractSockJsSession session; // TODO: JSON library used must be configurable private final ObjectMapper objectMapper = new ObjectMapper(); - public SockJsWebSocketHandler(SockJsConfiguration sockJsConfig, WebSocketHandler webSocketHandler) { - Assert.notNull(sockJsConfig, "sockJsConfig is required"); - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.sockJsConfig = sockJsConfig; - this.webSocketHandler = webSocketHandler; + public SockJsWebSocketHandler(SockJsConfiguration config, HandlerProvider handlerProvider) { + Assert.notNull(config, "sockJsConfig is required"); + Assert.notNull(handlerProvider, "handlerProvider is required"); + this.sockJsConfig = config; + this.handlerProvider = handlerProvider; } protected SockJsConfiguration getSockJsConfig() { return this.sockJsConfig; } - protected AbstractSockJsSession getSockJsSession(WebSocketSession wsSession) { - return this.sessions.get(wsSession); - } - @Override public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { - AbstractSockJsSession session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); - this.sessions.put(wsSession, session); + this.session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); } @Override @@ -89,8 +82,7 @@ public class SockJsWebSocketHandler implements TextMessageHandler { } try { String[] messages = this.objectMapper.readValue(payload, String[].class); - AbstractSockJsSession session = getSockJsSession(wsSession); - session.delegateMessages(messages); + this.session.delegateMessages(messages); } catch (IOException e) { logger.error("Broken data received. Terminating WebSocket connection abruptly", e); @@ -100,14 +92,12 @@ public class SockJsWebSocketHandler implements TextMessageHandler { @Override public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) throws Exception { - AbstractSockJsSession session = this.sessions.remove(wsSession); - session.delegateConnectionClosed(status); + this.session.delegateConnectionClosed(status); } @Override public void handleError(Throwable exception, WebSocketSession webSocketSession) { - AbstractSockJsSession session = getSockJsSession(webSocketSession); - session.delegateError(exception); + this.session.delegateError(exception); } private static String getSockJsSessionId(WebSocketSession wsSession) { @@ -127,7 +117,7 @@ public class SockJsWebSocketHandler implements TextMessageHandler { public WebSocketServerSockJsSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) throws Exception { - super(getSockJsSessionId(wsSession), sockJsConfig, SockJsWebSocketHandler.this.webSocketHandler); + super(getSockJsSessionId(wsSession), sockJsConfig, SockJsWebSocketHandler.this.handlerProvider); this.wsSession = wsSession; TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); this.wsSession.sendMessage(message); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index 77cacca780..094fd115e9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -20,6 +20,7 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -29,9 +30,9 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio public StreamingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - WebSocketHandler webSocketHandler) { + HandlerProvider handler) { - super(sessionId, sockJsConfig, webSocketHandler); + super(sessionId, sockJsConfig, handler); } protected void flushCache() throws Exception { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 836e7331f5..361c8b3c14 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -24,8 +24,10 @@ import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.TransportHandler; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.HandshakeHandler; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -60,26 +62,19 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws Exception { + HandlerProvider handler, AbstractSockJsSession session) throws Exception { - this.handshakeHandler.doHandshake(request, response, adaptSockJsHandler(webSocketHandler)); - } - - /** - * Adapt the {@link SockJsHandler} to the {@link WebSocketHandler} contract for - * exchanging SockJS message over WebSocket. - */ - protected WebSocketHandler adaptSockJsHandler(WebSocketHandler handler) { - return new SockJsWebSocketHandler(this.sockJsConfig, handler); + WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, handler); + this.handshakeHandler.doHandshake(request, response, new SimpleHandlerProvider(sockJsWrapper)); } // HandshakeHandler methods @Override public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws Exception { + HandlerProvider handler) throws Exception { - return this.handshakeHandler.doHandshake(request, response, webSocketHandler); + return this.handshakeHandler.doHandshake(request, response, handler); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index 57b5fd4e94..06a6aaf37d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -23,6 +23,7 @@ import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -50,9 +51,9 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { + public PollingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new PollingServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler); + return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java index 135c674d8b..3c8dc2c455 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java @@ -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,82 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - 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; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - /** + * A strategy for obtaining a handler instance that is scoped to external lifecycle events + * such as the opening and closing of a WebSocket connection. * * @author Rossen Stoyanchev * @since 4.0 */ -public class HandlerProvider implements BeanFactoryAware { +public interface HandlerProvider { - private Log logger = LogFactory.getLog(this.getClass()); + /** + * Whether the provided handler is a shared instance or not. + */ + boolean isSingleton(); - private final T handlerBean; + /** + * The type of handler provided. + */ + Class getHandlerType(); - private final Class handlerClass; + /** + * Obtain the handler instance, either shared or created every time. + */ + T getHandler(); - private AutowireCapableBeanFactory beanFactory; - - - public HandlerProvider(T handlerBean) { - Assert.notNull(handlerBean, "handlerBean is required"); - this.handlerBean = handlerBean; - this.handlerClass = null; - } - - public HandlerProvider(Class handlerClass) { - Assert.notNull(handlerClass, "handlerClass is required"); - this.handlerBean = null; - this.handlerClass = handlerClass; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (beanFactory instanceof AutowireCapableBeanFactory) { - this.beanFactory = (AutowireCapableBeanFactory) beanFactory; - } - } - - public void setLogger(Log logger) { - this.logger = logger; - } - - public boolean isSingleton() { - return (this.handlerBean != null); - } - - @SuppressWarnings("unchecked") - public Class getHandlerType() { - if (this.handlerClass != null) { - return this.handlerClass; - } - return (Class) ClassUtils.getUserClass(this.handlerBean.getClass()); - } - - public T getHandler() { - if (this.handlerBean != null) { - if (logger != null && logger.isTraceEnabled()) { - logger.trace("Returning handler singleton " + this.handlerBean); - } - return this.handlerBean; - } - Assert.isTrue(this.beanFactory != null, "BeanFactory is required to initialize handler instances."); - if (logger != null && logger.isTraceEnabled()) { - logger.trace("Creating handler of type " + this.handlerClass); - } - return this.beanFactory.createBean(this.handlerClass); - } + /** + * Callback to destroy a previously created handler instance if it is not shared. + */ + void destroy(T handler); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java index ae82de0dc0..579fcb1d8a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -18,6 +18,7 @@ package org.springframework.websocket.client; import java.net.URI; import org.springframework.http.HttpHeaders; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -36,13 +37,10 @@ import org.springframework.websocket.WebSocketSession; public interface WebSocketClient { - WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables) - throws WebSocketConnectFailureException; + WebSocketSession doHandshake(HandlerProvider handler, + String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; - WebSocketSession doHandshake(WebSocketHandler handler, URI uri) - throws WebSocketConnectFailureException; - - WebSocketSession doHandshake(WebSocketHandler handler, HttpHeaders headers, URI uri) + WebSocketSession doHandshake(HandlerProvider handler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index cb16e5be44..29b465e477 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -21,6 +21,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -32,7 +33,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag private final WebSocketClient client; - private final HandlerProvider webSocketHandlerProvider; + private final HandlerProvider handlerProvider; private WebSocketSession webSocketSession; @@ -43,8 +44,16 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - this.webSocketHandlerProvider = new HandlerProvider(webSocketHandler); this.client = webSocketClient; + this.handlerProvider = new SimpleHandlerProvider(webSocketHandler); + } + + public WebSocketConnectionManager(WebSocketClient webSocketClient, + HandlerProvider handlerProvider, String uriTemplate, Object... uriVariables) { + + super(uriTemplate, uriVariables); + this.client = webSocketClient; + this.handlerProvider = handlerProvider; } public void setSubProtocols(List subProtocols) { @@ -57,10 +66,9 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag @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()); + this.webSocketSession = this.client.doHandshake(this.handlerProvider, headers, getUri()); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java index 75433591ca..9bb7fe1d2a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java @@ -22,6 +22,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.support.BeanCreatingHandlerProvider; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -32,28 +34,29 @@ import org.springframework.websocket.HandlerProvider; public class AnnotatedEndpointConnectionManager extends EndpointConnectionManagerSupport implements BeanFactoryAware { - private final HandlerProvider endpointProvider; + private final HandlerProvider handlerProvider; - public AnnotatedEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { - super(uriTemplate, uriVariables); - this.endpointProvider = new HandlerProvider(endpointClass); - } - public AnnotatedEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - this.endpointProvider = new HandlerProvider(endpointBean); + this.handlerProvider = new SimpleHandlerProvider(endpointBean); + } + + public AnnotatedEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { + super(uriTemplate, uriVariables); + this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.endpointProvider.setBeanFactory(beanFactory); + if (this.handlerProvider instanceof BeanFactoryAware) { + ((BeanFactoryAware) this.handlerProvider).setBeanFactory(beanFactory); + } } - @Override protected void openConnection() throws Exception { - Object endpoint = this.endpointProvider.getHandler(); + Object endpoint = this.handlerProvider.getHandler(); Session session = getWebSocketContainer().connectToServer(endpoint, getUri()); updateSession(session); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java index b7006ab2fc..1307f55d9e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java @@ -32,6 +32,8 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.support.BeanCreatingHandlerProvider; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -43,19 +45,19 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); - private final HandlerProvider endpointProvider; + private final HandlerProvider handlerProvider; public EndpointConnectionManager(Endpoint endpointBean, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); Assert.notNull(endpointBean, "endpointBean is required"); - this.endpointProvider = new HandlerProvider(endpointBean); + this.handlerProvider = new SimpleHandlerProvider(endpointBean); } public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVars) { super(uriTemplate, uriVars); Assert.notNull(endpointClass, "endpointClass is required"); - this.endpointProvider = new HandlerProvider(endpointClass); + this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); } public void setSubProtocols(String... subprotocols) { @@ -80,12 +82,14 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.endpointProvider.setBeanFactory(beanFactory); + if (this.handlerProvider instanceof BeanFactoryAware) { + ((BeanFactoryAware) this.handlerProvider).setBeanFactory(beanFactory); + } } @Override protected void openConnection() throws Exception { - Endpoint endpoint = this.endpointProvider.getHandler(); + Endpoint endpoint = this.handlerProvider.getHandler(); ClientEndpointConfig endpointConfig = this.configBuilder.build(); Session session = getWebSocketContainer().connectToServer(endpoint, endpointConfig, getUri()); updateSession(session); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index 522bfcdf1e..1a15d4bd95 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -31,6 +31,7 @@ import javax.websocket.WebSocketContainer; import org.springframework.http.HttpHeaders; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.client.WebSocketClient; @@ -40,6 +41,7 @@ import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; /** + * A standard Java {@link WebSocketClient}. * * @author Rossen Stoyanchev * @since 4.0 @@ -57,21 +59,16 @@ public class StandardWebSocketClient implements WebSocketClient { this.webSocketContainer = container; } - public WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, - Object... uriVariables) throws WebSocketConnectFailureException { + public WebSocketSession doHandshake(HandlerProvider 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 { + public WebSocketSession doHandshake(HandlerProvider handler, + final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { Endpoint endpoint = new WebSocketHandlerEndpoint(handler); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 82d75c098d..bb1048c791 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -16,9 +16,6 @@ package org.springframework.websocket.endpoint; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; @@ -27,14 +24,15 @@ import javax.websocket.MessageHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.BinaryMessageHandler; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.PartialMessageHandler; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.TextMessage; import org.springframework.websocket.TextMessageHandler; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; /** @@ -47,14 +45,17 @@ public class WebSocketHandlerEndpoint extends Endpoint { private static Log logger = LogFactory.getLog(WebSocketHandlerEndpoint.class); - private final WebSocketHandler webSocketHandler; + private final HandlerProvider handlerProvider; - private final Map sessions = new ConcurrentHashMap(); + private final WebSocketHandler handler; + + private WebSocketSession webSocketSession; - public WebSocketHandlerEndpoint(WebSocketHandler handler) { - Assert.notNull(handler, "webSocketHandler is required"); - this.webSocketHandler = handler; + public WebSocketHandlerEndpoint(HandlerProvider handlerProvider) { + Assert.notNull(handlerProvider, "handlerProvider is required"); + this.handlerProvider = handlerProvider; + this.handler = handlerProvider.getHandler(); } @Override @@ -63,10 +64,9 @@ public class WebSocketHandlerEndpoint extends Endpoint { logger.debug("Client connected, WebSocket session id=" + session.getId() + ", uri=" + session.getRequestURI()); } try { - WebSocketSession webSocketSession = new StandardWebSocketSession(session); - this.sessions.put(session.getId(), webSocketSession); + this.webSocketSession = new StandardWebSocketSession(session); - if (this.webSocketHandler instanceof TextMessageHandler) { + if (this.handler instanceof TextMessageHandler) { session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { @@ -74,8 +74,8 @@ public class WebSocketHandlerEndpoint extends Endpoint { } }); } - else if (this.webSocketHandler instanceof BinaryMessageHandler) { - if (this.webSocketHandler instanceof PartialMessageHandler) { + else if (this.handler instanceof BinaryMessageHandler) { + if (this.handler instanceof PartialMessageHandler) { session.addMessageHandler(new MessageHandler.Partial() { @Override public void onMessage(byte[] messagePart, boolean isLast) { @@ -93,10 +93,10 @@ public class WebSocketHandlerEndpoint extends Endpoint { } } else { - logger.warn("WebSocketHandler handles neither text nor binary messages: " + this.webSocketHandler); + logger.warn("WebSocketHandler handles neither text nor binary messages: " + this.handler); } - this.webSocketHandler.afterConnectionEstablished(webSocketSession); + this.handler.afterConnectionEstablished(this.webSocketSession); } catch (Throwable ex) { // TODO @@ -108,11 +108,9 @@ public class WebSocketHandlerEndpoint extends Endpoint { if (logger.isTraceEnabled()) { logger.trace("Received message for WebSocket session id=" + session.getId() + ": " + message); } - WebSocketSession wsSession = getWebSocketSession(session); - Assert.notNull(wsSession, "WebSocketSession not found"); try { TextMessage textMessage = new TextMessage(message); - ((TextMessageHandler) webSocketHandler).handleTextMessage(textMessage, wsSession); + ((TextMessageHandler) handler).handleTextMessage(textMessage, this.webSocketSession); } catch (Throwable ex) { // TODO @@ -124,11 +122,9 @@ public class WebSocketHandlerEndpoint extends Endpoint { if (logger.isTraceEnabled()) { logger.trace("Received binary data for WebSocket session id=" + session.getId()); } - WebSocketSession wsSession = getWebSocketSession(session); - Assert.notNull(wsSession, "WebSocketSession not found"); try { BinaryMessage binaryMessage = new BinaryMessage(message, isLast); - ((BinaryMessageHandler) webSocketHandler).handleBinaryMessage(binaryMessage, wsSession); + ((BinaryMessageHandler) handler).handleBinaryMessage(binaryMessage, this.webSocketSession); } catch (Throwable ex) { // TODO @@ -142,32 +138,23 @@ public class WebSocketHandlerEndpoint extends Endpoint { logger.debug("Client disconnected, WebSocket session id=" + session.getId() + ", " + reason); } try { - WebSocketSession wsSession = this.sessions.remove(session.getId()); - if (wsSession != null) { - CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); - this.webSocketHandler.afterConnectionClosed(closeStatus, wsSession); - } - else { - Assert.notNull(wsSession, "No WebSocket session"); - } + CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); + this.handler.afterConnectionClosed(closeStatus, this.webSocketSession); } catch (Throwable ex) { // TODO logger.error("Error while processing session closing", ex); } + finally { + this.handlerProvider.destroy(this.handler); + } } @Override public void onError(javax.websocket.Session session, Throwable exception) { logger.error("Error for WebSocket session id=" + session.getId(), exception); try { - WebSocketSession wsSession = getWebSocketSession(session); - if (wsSession != null) { - this.webSocketHandler.handleError(exception, wsSession); - } - else { - logger.warn("WebSocketSession not found. Perhaps onError was called after onClose?"); - } + this.handler.handleError(exception, this.webSocketSession); } catch (Throwable ex) { // TODO @@ -175,8 +162,4 @@ public class WebSocketHandlerEndpoint extends Endpoint { } } - private WebSocketSession getWebSocketSession(javax.websocket.Session session) { - return this.sessions.get(session.getId()); - } - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index c7a3fdcb87..3ed1ccb441 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -35,6 +35,7 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -87,7 +88,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws Exception { + HandlerProvider handler) throws Exception { logger.debug("Starting handshake for " + request.getURI()); @@ -135,10 +136,10 @@ public class DefaultHandshakeHandler implements HandshakeHandler { response.flush(); if (logger.isTraceEnabled()) { - logger.trace("Upgrading with " + webSocketHandler); + logger.trace("Upgrading with " + handler); } - this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, webSocketHandler); + this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, handler); return true; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 2af525c606..61f0e42b33 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -18,6 +18,7 @@ package org.springframework.websocket.server; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -29,16 +30,8 @@ import org.springframework.websocket.WebSocketHandler; */ public interface HandshakeHandler { - /** - * - * @param request the HTTP request - * @param response the HTTP response - * @param webSocketMessageHandler the handler to process WebSocket messages with - * @return a boolean indicating whether the handshake negotiation was successful - * - * @throws Exception - */ - boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) - throws Exception; + + boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, + HandlerProvider handler) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index 81c238c423..6d2827baaa 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -18,6 +18,7 @@ package org.springframework.websocket.server; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -39,9 +40,9 @@ public interface RequestUpgradeStrategy { * Perform runtime specific steps to complete the upgrade. * Invoked only if the handshake is successful. * - * @param webSocketHandler the handler for WebSocket messages + * @param handler the handler for WebSocket messages */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - WebSocketHandler webSocketHandler) throws Exception; + HandlerProvider handler) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index d9e36a61fb..c6aadd3028 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -37,6 +37,8 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; +import org.springframework.websocket.support.BeanCreatingHandlerProvider; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -54,11 +56,9 @@ import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; */ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAware { - private static Log logger = LogFactory.getLog(EndpointRegistration.class); - private final String path; - private final HandlerProvider endpointProvider; + private final HandlerProvider handlerProvider; private List> encoders = new ArrayList>(); @@ -84,16 +84,14 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw Assert.hasText(path, "path must not be empty"); Assert.notNull(endpointClass, "endpointClass is required"); this.path = path; - this.endpointProvider = new HandlerProvider(endpointClass); - this.endpointProvider.setLogger(logger); + this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); } public EndpointRegistration(String path, Endpoint endpointBean) { Assert.hasText(path, "path must not be empty"); Assert.notNull(endpointBean, "endpointBean is required"); this.path = path; - this.endpointProvider = new HandlerProvider(endpointBean); - this.endpointProvider.setLogger(logger); + this.handlerProvider = new SimpleHandlerProvider(endpointBean); } @Override @@ -102,12 +100,13 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw } @Override + @SuppressWarnings("unchecked") public Class getEndpointClass() { - return this.endpointProvider.getHandlerType(); + return (Class) this.handlerProvider.getHandlerType(); } public Endpoint getEndpoint() { - return this.endpointProvider.getHandler(); + return this.handlerProvider.getHandler(); } public void setSubprotocols(List subprotocols) { @@ -193,7 +192,9 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.endpointProvider.setBeanFactory(beanFactory); + if (this.handlerProvider instanceof BeanFactoryAware) { + ((BeanFactoryAware) this.handlerProvider).setBeanFactory(beanFactory); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java deleted file mode 100644 index ee6211341b..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointContainerFactoryBean.java +++ /dev/null @@ -1,88 +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.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, 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; - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index b16f3d746d..f7884838d3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -22,6 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.server.RequestUpgradeStrategy; @@ -41,12 +42,12 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, WebSocketHandler webSocketHandler) throws Exception { + String protocol, HandlerProvider handler) throws Exception { - upgradeInternal(request, response, protocol, adaptWebSocketHandler(webSocketHandler)); + upgradeInternal(request, response, protocol, adaptWebSocketHandler(handler)); } - protected Endpoint adaptWebSocketHandler(WebSocketHandler handler) { + protected Endpoint adaptWebSocketHandler(HandlerProvider handler) { return new WebSocketHandlerEndpoint(handler); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 9418afc689..096ee1ca0b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -22,9 +22,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; @@ -36,6 +33,7 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -44,7 +42,7 @@ import org.springframework.websocket.server.HandshakeHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFactoryAware { +public class WebSocketHttpRequestHandler implements HttpRequestHandler { private HandshakeHandler handshakeHandler; @@ -53,13 +51,13 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFact public WebSocketHttpRequestHandler(WebSocketHandler webSocketHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.handlerProvider = new HandlerProvider(webSocketHandler); + this.handlerProvider = new SimpleHandlerProvider(webSocketHandler); this.handshakeHandler = new DefaultHandshakeHandler(); } - public WebSocketHttpRequestHandler( Class webSocketHandlerClass) { - Assert.notNull(webSocketHandlerClass, "webSocketHandlerClass is required"); - this.handlerProvider = new HandlerProvider(webSocketHandlerClass); + public WebSocketHttpRequestHandler( HandlerProvider handlerProvider) { + Assert.notNull(handlerProvider, "handlerProvider is required"); + this.handlerProvider = handlerProvider; } public void setHandshakeHandler(HandshakeHandler handshakeHandler) { @@ -67,11 +65,6 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFact this.handshakeHandler = handshakeHandler; } - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.handlerProvider.setBeanFactory(beanFactory); - } - @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -80,8 +73,7 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler, BeanFact ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - WebSocketHandler webSocketHandler = this.handlerProvider.getHandler(); - this.handshakeHandler.doHandshake(httpRequest, httpResponse, webSocketHandler); + this.handshakeHandler.doHandshake(httpRequest, httpResponse, this.handlerProvider); } catch (Exception e) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java new file mode 100644 index 0000000000..1c6567dc9a --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java @@ -0,0 +1,94 @@ +/* + * 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanUtils; +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.util.Assert; +import org.springframework.websocket.HandlerProvider; + + +/** + * A {@link HandlerProvider} that uses {@link AutowireCapableBeanFactory#createBean(Class) + * creating a fresh instance every time #getHandler() is called. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class BeanCreatingHandlerProvider implements HandlerProvider, BeanFactoryAware { + + private static final Log logger = LogFactory.getLog(BeanCreatingHandlerProvider.class); + + private final Class handlerClass; + + private AutowireCapableBeanFactory beanFactory; + + + public BeanCreatingHandlerProvider(Class handlerClass) { + Assert.notNull(handlerClass, "handlerClass is required"); + this.handlerClass = handlerClass; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof AutowireCapableBeanFactory) { + this.beanFactory = (AutowireCapableBeanFactory) beanFactory; + } + } + + public boolean isSingleton() { + return false; + } + + public Class getHandlerType() { + return this.handlerClass; + } + + public T getHandler() { + if (logger.isTraceEnabled()) { + logger.trace("Creating instance for handler type " + this.handlerClass); + } + if (this.beanFactory == null) { + logger.warn("No BeanFactory available, attempting to use default constructor"); + return BeanUtils.instantiate(this.handlerClass); + } + else { + return this.beanFactory.createBean(this.handlerClass); + } + } + + @Override + public void destroy(T handler) { + if (this.beanFactory != null) { + if (logger.isTraceEnabled()) { + logger.trace("Destroying handler instance " + handler); + } + this.beanFactory.destroyBean(handler); + } + } + + @Override + public String toString() { + return "BeanCreatingHandlerProvider [handlerClass=" + handlerClass + "]"; + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java new file mode 100644 index 0000000000..c880a64802 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java @@ -0,0 +1,61 @@ +/* + * 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.support; + +import org.springframework.util.ClassUtils; +import org.springframework.websocket.HandlerProvider; + + +/** + * A {@link HandlerProvider} that returns a singleton instance. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class SimpleHandlerProvider implements HandlerProvider { + + private final T handler; + + + public SimpleHandlerProvider(T handler) { + this.handler = handler; + } + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public Class getHandlerType() { + return ClassUtils.getUserClass(this.handler); + } + + @Override + public T getHandler() { + return this.handler; + } + + @Override + public void destroy(T handler) { + } + + @Override + public String toString() { + return "SimpleHandlerProvider [handler=" + handler + "]"; + } + +} From 3cd4909ba344407bdabe021b815ae852fa3017a6 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 23 Apr 2013 11:56:57 -0400 Subject: [PATCH 28/51] Minor fix --- .../websocket/endpoint/WebSocketHandlerEndpoint.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index bb1048c791..be728fdcc1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -47,7 +47,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { private final HandlerProvider handlerProvider; - private final WebSocketHandler handler; + private WebSocketHandler handler; private WebSocketSession webSocketSession; @@ -55,7 +55,6 @@ public class WebSocketHandlerEndpoint extends Endpoint { public WebSocketHandlerEndpoint(HandlerProvider handlerProvider) { Assert.notNull(handlerProvider, "handlerProvider is required"); this.handlerProvider = handlerProvider; - this.handler = handlerProvider.getHandler(); } @Override @@ -64,6 +63,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { logger.debug("Client connected, WebSocket session id=" + session.getId() + ", uri=" + session.getRequestURI()); } try { + this.handler = handlerProvider.getHandler(); this.webSocketSession = new StandardWebSocketSession(session); if (this.handler instanceof TextMessageHandler) { From a14161f0ca43996e10fa3915d959e2c4cc61bab7 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 23 Apr 2013 12:00:06 -0700 Subject: [PATCH 29/51] JettyRequestUpgradeStrategy --- build.gradle | 5 + .../server/DefaultHandshakeHandler.java | 5 + .../support/JettyRequestUpgradeStrategy.java | 305 ++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java diff --git a/build.gradle b/build.gradle index 57b4a48509..72eebf8059 100644 --- a/build.gradle +++ b/build.gradle @@ -528,6 +528,11 @@ project("spring-websocket") { optional("org.glassfish.tyrus:tyrus-websocket-core:1.0-SNAPSHOT") optional("org.glassfish.tyrus:tyrus-container-servlet:1.0-SNAPSHOT") + optional("org.eclipse.jetty:jetty-webapp:9.0.1.v20130408") { + exclude group: "org.eclipse.jetty.orbit", module: "javax.servlet" + } + optional("org.eclipse.jetty.websocket:websocket-server:9.0.1.v20130408") + optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") // required for SockJS support currently } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 3ed1ccb441..6ae721819c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -214,6 +214,8 @@ public class DefaultHandshakeHandler implements HandshakeHandler { private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", DefaultHandshakeHandler.class.getClassLoader()); + private static final boolean jettyWebSocketPresent = ClassUtils.isPresent( + "org.eclipse.jetty.websocket.server.UpgradeContext", DefaultHandshakeHandler.class.getClassLoader()); private RequestUpgradeStrategy create() { String className; @@ -223,6 +225,9 @@ public class DefaultHandshakeHandler implements HandshakeHandler { else if (glassfishWebSocketPresent) { className = "org.springframework.websocket.server.support.GlassfishRequestUpgradeStrategy"; } + else if (jettyWebSocketPresent) { + className = "org.springframework.websocket.server.support.JettyRequestUpgradeStrategy"; + } else { throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName()); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java new file mode 100644 index 0000000000..a00ee912ef --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -0,0 +1,305 @@ +/* + * 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 java.io.IOException; +import java.net.URI; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.UpgradeRequest; +import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.eclipse.jetty.websocket.server.HandshakeRFC6455; +import org.eclipse.jetty.websocket.server.ServletWebSocketRequest; +import org.eclipse.jetty.websocket.server.WebSocketServerFactory; +import org.eclipse.jetty.websocket.servlet.WebSocketCreator; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.BinaryMessageHandler; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.TextMessageHandler; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.server.RequestUpgradeStrategy; + +/** + * {@link RequestUpgradeStrategy} for use with Jetty. Based on Jetty's internal + * {@code org.eclipse.jetty.websocket.server.WebSocketHandler} class. + * + * @author Phillip Webb + */ +public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { + + private static Log logger = LogFactory.getLog(JettyRequestUpgradeStrategy.class); + + // FIXME jetty has options, timeouts etc. Do we need a common abstraction + + // FIXME need a way for someone to plug their own RequestUpgradeStrategy or override + // Jetty settings + + // FIXME when to call factory.cleanup(); + + private static final String HANDLER_PROVIDER = JettyRequestUpgradeStrategy.class.getName() + + ".HANDLER_PROVIDER"; + + private WebSocketServerFactory factory; + + + public JettyRequestUpgradeStrategy() { + this.factory = new WebSocketServerFactory(); + this.factory.setCreator(new WebSocketCreator() { + @Override + @SuppressWarnings("unchecked") + public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) { + Assert.isInstanceOf(ServletWebSocketRequest.class, req); + ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) req; + HandlerProvider handlerProvider = (HandlerProvider) servletRequest.getServletAttributes().get( + HANDLER_PROVIDER); + return new WebSocketHandlerAdapter(handlerProvider); + } + }); + try { + this.factory.init(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public String[] getSupportedVersions() { + return new String[] { String.valueOf(HandshakeRFC6455.VERSION) }; + } + + @Override + public void upgrade(ServerHttpRequest request, ServerHttpResponse response, + String selectedProtocol, HandlerProvider handlerProvider) + throws Exception { + Assert.isInstanceOf(ServletServerHttpRequest.class, request); + Assert.isInstanceOf(ServletServerHttpResponse.class, response); + upgrade(((ServletServerHttpRequest) request).getServletRequest(), + ((ServletServerHttpResponse) response).getServletResponse(), + selectedProtocol, handlerProvider); + } + + private void upgrade(HttpServletRequest request, HttpServletResponse response, + String selectedProtocol, final HandlerProvider handlerProvider) + throws Exception { + request.setAttribute(HANDLER_PROVIDER, handlerProvider); + Assert.state(factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); + Assert.state(factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); + } + + + /** + * Adapts Spring's {@link WebSocketHandler} to Jetty's {@link WebSocketListener}. + */ + private static class WebSocketHandlerAdapter implements WebSocketListener { + + private final HandlerProvider provider; + + private WebSocketHandler handler; + + private WebSocketSession session; + + + public WebSocketHandlerAdapter(HandlerProvider provider) { + Assert.notNull(provider, "Provider must not be null"); + Assert.isAssignable(WebSocketHandler.class, provider.getHandlerType()); + this.provider = provider; + } + + + @Override + public void onWebSocketConnect(Session session) { + Assert.state(this.session == null, "WebSocket already open"); + try { + this.session = new WebSocketSessionAdapter(session); + if (logger.isDebugEnabled()) { + logger.debug("Client connected, WebSocket session id=" + + this.session.getId() + ", uri=" + this.session.getURI()); + } + this.handler = this.provider.getHandler(); + this.handler.afterConnectionEstablished(this.session); + } + catch (Exception ex) { + try { + // FIXME revisit after error handling + onWebSocketError(ex); + } + finally { + this.session = null; + this.handler = null; + } + } + } + + @Override + public void onWebSocketClose(int statusCode, String reason) { + Assert.state(this.session != null, "WebSocket not open"); + try { + CloseStatus closeStatus = new CloseStatus(statusCode, reason); + if (logger.isDebugEnabled()) { + logger.debug("Client disconnected, WebSocket session id=" + + this.session.getId() + ", " + closeStatus); + } + this.handler.afterConnectionClosed(closeStatus, this.session); + } + catch (Exception ex) { + onWebSocketError(ex); + } + finally { + try { + if (this.handler != null) { + this.provider.destroy(this.handler); + } + } + finally { + this.session = null; + this.handler = null; + } + } + } + + @Override + public void onWebSocketText(String payload) { + try { + TextMessage message = new TextMessage(payload); + if (logger.isTraceEnabled()) { + logger.trace("Received message for WebSocket session id=" + + this.session.getId() + ": " + message); + } + if (this.handler instanceof TextMessageHandler) { + ((TextMessageHandler) this.handler).handleTextMessage(message, this.session); + } + } + catch(Exception ex) { + ex.printStackTrace(); //FIXME + } + } + + @Override + public void onWebSocketBinary(byte[] payload, int offset, int len) { + try { + BinaryMessage message = new BinaryMessage(payload, offset, len); + if (logger.isTraceEnabled()) { + logger.trace("Received binary data for WebSocket session id=" + + this.session.getId() + ": " + message); + } + if (this.handler instanceof BinaryMessageHandler) { + ((BinaryMessageHandler) this.handler).handleBinaryMessage(message, + this.session); + } + } + catch(Exception ex) { + ex.printStackTrace(); //FIXME + } + } + + @Override + public void onWebSocketError(Throwable cause) { + try { + this.handler.handleError(cause, this.session); + } + catch (Throwable ex) { + // FIXME exceptions + logger.error("Error for WebSocket session id=" + this.session.getId(), + cause); + } + } + } + + + /** + * Adapts Jetty's {@link Session} to Spring's {@link WebSocketSession}. + */ + private static class WebSocketSessionAdapter implements WebSocketSession { + + private Session session; + + + public WebSocketSessionAdapter(Session session) { + this.session = session; + } + + + @Override + public String getId() { + return ObjectUtils.getIdentityHexString(this.session); + } + + @Override + public boolean isOpen() { + return this.session.isOpen(); + } + + @Override + public boolean isSecure() { + return this.session.isSecure(); + } + + @Override + public URI getURI() { + return this.session.getUpgradeRequest().getRequestURI(); + } + + @Override + public void sendMessage(WebSocketMessage message) throws Exception { + if (message instanceof BinaryMessage) { + sendMessage((BinaryMessage) message); + } + else if (message instanceof TextMessage) { + sendMessage((TextMessage) message); + } + else { + throw new IllegalArgumentException("Unsupported message type"); + } + } + + private void sendMessage(BinaryMessage message) throws Exception { + this.session.getRemote().sendBytes(message.getPayload()); + } + + private void sendMessage(TextMessage message) throws Exception { + this.session.getRemote().sendString(message.getPayload()); + } + + @Override + public void close() throws IOException { + this.session.close(); + } + + @Override + public void close(CloseStatus status) throws IOException { + this.session.close(status.getCode(), status.getReason()); + } + } + +} From ff2e9aa5bc4b182d74e1e3e654f24e253ad4768b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 23 Apr 2013 12:08:55 -0700 Subject: [PATCH 30/51] Minor tweaks to WebSocketMessage - payload is now required - byte data is obtained and stored only once (allowing multiple calls) - minor formatting polish --- .../websocket/BinaryMessage.java | 38 ++++++++++++------- .../websocket/TextMessage.java | 2 - .../websocket/WebSocketMessage.java | 2 + 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java index 5f136bc31e..dbb12bcee2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java @@ -19,6 +19,8 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; +import org.springframework.util.Assert; + /** * Represents a binary WebSocket message. @@ -28,7 +30,7 @@ import java.nio.ByteBuffer; */ public final class BinaryMessage extends WebSocketMessage { - private final byte[] bytes; + private byte[] bytes; private final boolean last; @@ -48,8 +50,19 @@ public final class BinaryMessage extends WebSocketMessage { } public BinaryMessage(byte[] payload, boolean isLast) { - super((payload != null) ? ByteBuffer.wrap(payload) : null); - this.bytes = payload; + this(payload, 0, (payload == null ? 0 : payload.length), isLast); + } + + public BinaryMessage(byte[] payload, int offset, int len) { + this(payload, offset, len, true); + } + + public BinaryMessage(byte[] payload, int offset, int len, boolean isLast) { + super(payload != null ? ByteBuffer.wrap(payload, offset, len) : null); + if(payload != null && offset == 0 && len == payload.length) { + // FIXME better if a message always needs a payload? + this.bytes = payload; + } this.last = isLast; } @@ -58,17 +71,16 @@ public final class BinaryMessage extends WebSocketMessage { } public byte[] getByteArray() { - if (this.bytes != null) { - return this.bytes; - } - else if (getPayload() != null){ - byte[] result = new byte[getPayload().remaining()]; - getPayload().get(result); - return result; - } - else { - return null; + if(this.bytes == null && getPayload() != null) { + this.bytes = getRemainingBytes(getPayload()); } + return this.bytes; + } + + private byte[] getRemainingBytes(ByteBuffer payload) { + byte[] result = new byte[getPayload().remaining()]; + getPayload().get(result); + return result; } public InputStream getInputStream() { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java index b254ae7f80..61f3916547 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java @@ -18,7 +18,6 @@ package org.springframework.websocket; import java.io.Reader; import java.io.StringReader; - /** * Represents a text WebSocket message. * @@ -27,7 +26,6 @@ import java.io.StringReader; */ public final class TextMessage extends WebSocketMessage { - public TextMessage(String payload) { super(payload); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java index 19b79eda74..6ed55410ad 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java @@ -15,6 +15,7 @@ */ package org.springframework.websocket; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -29,6 +30,7 @@ public abstract class WebSocketMessage { WebSocketMessage(T payload) { + Assert.notNull(payload, "Payload must not be null"); this.payload = payload; } From db4de526d2e9f231910d8946269d2e399863d26f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 23 Apr 2013 12:10:19 -0700 Subject: [PATCH 31/51] Minor polish and FIXMEs --- .../sockjs/server/transport/SockJsWebSocketHandler.java | 2 +- .../websocket/server/RequestUpgradeStrategy.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 0e50456957..776df0e30b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -104,7 +104,7 @@ public class SockJsWebSocketHandler implements TextMessageHandler { Assert.notNull(wsSession, "wsSession is required"); String path = wsSession.getURI().getPath(); String[] segments = StringUtils.tokenizeToStringArray(path, "/"); - Assert.isTrue(segments.length > 3, "SockJS request should have at least 3 patgh segments: " + path); + Assert.isTrue(segments.length > 3, "SockJS request should have at least 3 path segments: " + path); return segments[segments.length-2]; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index 6d2827baaa..fadf15c99e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -43,6 +43,7 @@ public interface RequestUpgradeStrategy { * @param handler the handler for WebSocket messages */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - HandlerProvider handler) throws Exception; + HandlerProvider handlerProvider) throws Exception; + // FIXME how to indicate failure to upgrade? } From c28ce0e2bd96fa93c20282403400e39e160601c7 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 23 Apr 2013 17:44:53 -0400 Subject: [PATCH 32/51] Ensure WebSocketHandlerEndpoint can connect only once WebSocketHandlerEndpoint and SockJsWebSocketHandler are stateful wrappers that are not intended to be used with one client connection. --- .../sockjs/AbstractSockJsSession.java | 13 +-- .../transport/SockJsWebSocketHandler.java | 10 ++- .../endpoint/WebSocketHandlerEndpoint.java | 79 ++++++++++--------- 3 files changed, 59 insertions(+), 43 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 29791860a8..d8565455c1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -43,7 +43,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { private final HandlerProvider handlerProvider; - private final TextMessageHandler handler; + private TextMessageHandler handler; private State state = State.NEW; @@ -61,10 +61,6 @@ public abstract class AbstractSockJsSession implements WebSocketSession { Assert.notNull(sessionId, "sessionId is required"); Assert.notNull(handlerProvider, "handlerProvider is required"); this.sessionId = sessionId; - - WebSocketHandler webSocketHandler = handlerProvider.getHandler(); - Assert.isInstanceOf(TextMessageHandler.class, webSocketHandler, "Expected a TextMessageHandler"); - this.handler = (TextMessageHandler) webSocketHandler; this.handlerProvider = handlerProvider; } @@ -127,9 +123,16 @@ public abstract class AbstractSockJsSession implements WebSocketSession { public void delegateConnectionEstablished() throws Exception { this.state = State.OPEN; + initHandler(); this.handler.afterConnectionEstablished(this); } + private void initHandler() { + WebSocketHandler webSocketHandler = handlerProvider.getHandler(); + Assert.isInstanceOf(TextMessageHandler.class, webSocketHandler, "Expected a TextMessageHandler"); + this.handler = (TextMessageHandler) webSocketHandler; + } + public void delegateMessages(String[] messages) throws Exception { for (String message : messages) { this.handler.handleTextMessage(new TextMessage(message), this); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 776df0e30b..eef056aab1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -17,6 +17,7 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,6 +54,8 @@ public class SockJsWebSocketHandler implements TextMessageHandler { private AbstractSockJsSession session; + private final AtomicInteger sessionCount = new AtomicInteger(0); + // TODO: JSON library used must be configurable private final ObjectMapper objectMapper = new ObjectMapper(); @@ -70,6 +73,7 @@ public class SockJsWebSocketHandler implements TextMessageHandler { @Override public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { + Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); this.session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); } @@ -80,14 +84,16 @@ public class SockJsWebSocketHandler implements TextMessageHandler { logger.trace("Ignoring empty message"); return; } + String[] messages; try { - String[] messages = this.objectMapper.readValue(payload, String[].class); - this.session.delegateMessages(messages); + messages = this.objectMapper.readValue(payload, String[].class); } catch (IOException e) { logger.error("Broken data received. Terminating WebSocket connection abruptly", e); wsSession.close(); + return; } + this.session.delegateMessages(messages); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index be728fdcc1..7f7bd50a45 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -16,6 +16,8 @@ package org.springframework.websocket.endpoint; +import java.util.concurrent.atomic.AtomicInteger; + import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; @@ -51,6 +53,8 @@ public class WebSocketHandlerEndpoint extends Endpoint { private WebSocketSession webSocketSession; + private final AtomicInteger sessionCount = new AtomicInteger(0); + public WebSocketHandlerEndpoint(HandlerProvider handlerProvider) { Assert.notNull(handlerProvider, "handlerProvider is required"); @@ -59,48 +63,54 @@ public class WebSocketHandlerEndpoint extends Endpoint { @Override public void onOpen(final javax.websocket.Session session, EndpointConfig config) { - if (logger.isDebugEnabled()) { - logger.debug("Client connected, WebSocket session id=" + session.getId() + ", uri=" + session.getRequestURI()); - } - try { - this.handler = handlerProvider.getHandler(); - this.webSocketSession = new StandardWebSocketSession(session); - if (this.handler instanceof TextMessageHandler) { - session.addMessageHandler(new MessageHandler.Whole() { + Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); + + if (logger.isDebugEnabled()) { + logger.debug("Client connected, javax.websocket.Session id=" + + session.getId() + ", uri=" + session.getRequestURI()); + } + + this.webSocketSession = new StandardWebSocketSession(session); + this.handler = handlerProvider.getHandler(); + + if (this.handler instanceof TextMessageHandler) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + handleTextMessage(session, message); + } + }); + } + else if (this.handler instanceof BinaryMessageHandler) { + if (this.handler instanceof PartialMessageHandler) { + session.addMessageHandler(new MessageHandler.Partial() { @Override - public void onMessage(String message) { - handleTextMessage(session, message); + public void onMessage(byte[] messagePart, boolean isLast) { + handleBinaryMessage(session, messagePart, isLast); } }); } - else if (this.handler instanceof BinaryMessageHandler) { - if (this.handler instanceof PartialMessageHandler) { - session.addMessageHandler(new MessageHandler.Partial() { - @Override - public void onMessage(byte[] messagePart, boolean isLast) { - handleBinaryMessage(session, messagePart, isLast); - } - }); - } - else { - session.addMessageHandler(new MessageHandler.Whole() { - @Override - public void onMessage(byte[] message) { - handleBinaryMessage(session, message, true); - } - }); - } - } else { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(byte[] message) { + handleBinaryMessage(session, message, true); + } + }); + } + } + else { + if (logger.isWarnEnabled()) { logger.warn("WebSocketHandler handles neither text nor binary messages: " + this.handler); } + } + try { this.handler.afterConnectionEstablished(this.webSocketSession); } catch (Throwable ex) { - // TODO - logger.error("Error while processing new session", ex); + this.handler.handleError(ex, this.webSocketSession); } } @@ -113,8 +123,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { ((TextMessageHandler) handler).handleTextMessage(textMessage, this.webSocketSession); } catch (Throwable ex) { - // TODO - logger.error("Error while processing message", ex); + this.handler.handleError(ex, this.webSocketSession); } } @@ -127,8 +136,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { ((BinaryMessageHandler) handler).handleBinaryMessage(binaryMessage, this.webSocketSession); } catch (Throwable ex) { - // TODO - logger.error("Error while processing message", ex); + this.handler.handleError(ex, this.webSocketSession); } } @@ -142,7 +150,6 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.handler.afterConnectionClosed(closeStatus, this.webSocketSession); } catch (Throwable ex) { - // TODO logger.error("Error while processing session closing", ex); } finally { @@ -157,7 +164,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.handler.handleError(exception, this.webSocketSession); } catch (Throwable ex) { - // TODO + // TODO: close the session? logger.error("Failed to handle error", ex); } } From 36148b7cb1066b77b7849f9852d36d0fcf0f5db0 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 23 Apr 2013 21:35:24 -0400 Subject: [PATCH 33/51] Switch DefaultSockJsService to constructor DI DefaultSockJsService now relies on constructors and requires a TaskScheduler at a minimum. It no longer needs lifecycle methods. --- .../server/AbstractServerSockJsSession.java | 4 +- .../sockjs/server/AbstractSockJsService.java | 74 +------- .../sockjs/server/SockJsConfiguration.java | 6 +- .../server/support/DefaultSockJsService.java | 161 +++++++++--------- .../client/WebSocketConnectionManager.java | 9 +- .../support/DefaultSockJsServiceTests.java | 55 ++++++ 6 files changed, 150 insertions(+), 159 deletions(-) create mode 100644 spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 0c179b9dd0..a39e5d6789 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -129,13 +129,13 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession } protected void scheduleHeartbeat() { - Assert.notNull(getSockJsConfig().getHeartbeatScheduler(), "heartbeatScheduler not configured"); + Assert.notNull(getSockJsConfig().getTaskScheduler(), "heartbeatScheduler not configured"); cancelHeartbeat(); if (!isActive()) { return; } Date time = new Date(System.currentTimeMillis() + getSockJsConfig().getHeartbeatTime()); - this.heartbeatTask = getSockJsConfig().getHeartbeatScheduler().schedule(new Runnable() { + this.heartbeatTask = getSockJsConfig().getTaskScheduler().schedule(new Runnable() { public void run() { try { sendHeartbeat(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 0319ff41bc..e44c44cec0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -25,15 +25,12 @@ import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpMethod; 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.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.DigestUtils; @@ -49,8 +46,7 @@ import org.springframework.websocket.WebSocketHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractSockJsService - implements SockJsService, SockJsConfiguration, InitializingBean, DisposableBean { +public abstract class AbstractSockJsService implements SockJsService, SockJsConfiguration { protected final Log logger = LogFactory.getLog(getClass()); @@ -71,17 +67,13 @@ public abstract class AbstractSockJsService private boolean webSocketsEnabled = true; - private final TaskSchedulerHolder heartbeatSchedulerHolder; + private final TaskScheduler taskScheduler; - public AbstractSockJsService() { - this.heartbeatSchedulerHolder = new TaskSchedulerHolder("SockJs-heartbeat-"); - } - - public AbstractSockJsService(TaskScheduler heartbeatScheduler) { - Assert.notNull(heartbeatScheduler, "heartbeatScheduler is required"); - this.heartbeatSchedulerHolder = new TaskSchedulerHolder(heartbeatScheduler); + public AbstractSockJsService(TaskScheduler scheduler) { + Assert.notNull(scheduler, "scheduler is required"); + this.taskScheduler = scheduler; } /** @@ -159,8 +151,8 @@ public abstract class AbstractSockJsService return this.heartbeatTime; } - public TaskScheduler getHeartbeatScheduler() { - return this.heartbeatSchedulerHolder.getScheduler(); + public TaskScheduler getTaskScheduler() { + return this.taskScheduler; } /** @@ -199,16 +191,6 @@ public abstract class AbstractSockJsService return this.webSocketsEnabled; } - @Override - public void afterPropertiesSet() throws Exception { - this.heartbeatSchedulerHolder.initialize(); - } - - @Override - public void destroy() throws Exception { - this.heartbeatSchedulerHolder.destroy(); - } - /** * TODO * @@ -423,46 +405,4 @@ public abstract class AbstractSockJsService } }; - - /** - * Holds an externally provided or an internally managed TaskScheduler. Provides - * initialize and destroy methods have no effect if the scheduler is externally - * managed. - */ - protected static class TaskSchedulerHolder { - - private final TaskScheduler taskScheduler; - - private final boolean isDefaultTaskScheduler; - - public TaskSchedulerHolder(TaskScheduler taskScheduler) { - Assert.notNull(taskScheduler, "taskScheduler is required"); - this.taskScheduler = taskScheduler; - this.isDefaultTaskScheduler = false; - } - - public TaskSchedulerHolder(String threadNamePrefix) { - ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - scheduler.setThreadNamePrefix(threadNamePrefix); - this.taskScheduler = scheduler; - this.isDefaultTaskScheduler = true; - } - - public TaskScheduler getScheduler() { - return this.taskScheduler; - } - - public void initialize() { - if (this.isDefaultTaskScheduler) { - ((ThreadPoolTaskScheduler) this.taskScheduler).afterPropertiesSet(); - } - } - - public void destroy() { - if (this.isDefaultTaskScheduler) { - ((ThreadPoolTaskScheduler) this.taskScheduler).shutdown(); - } - } - } - } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java index 4bf1db79e8..1fa79db883 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java @@ -48,10 +48,8 @@ public interface SockJsConfiguration { public long getHeartbeatTime(); /** - * A scheduler instance to use for scheduling heartbeat frames. - *

- * By default a {@link ThreadPoolTaskScheduler} with default settings is used. + * A scheduler instance to use for scheduling heart-beat messages. */ - public TaskScheduler getHeartbeatScheduler(); + public TaskScheduler getTaskScheduler(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index f54b540eb1..5a4baadf3f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -16,10 +16,15 @@ package org.springframework.sockjs.server.support; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; import org.springframework.http.Cookie; import org.springframework.http.HttpMethod; @@ -41,7 +46,7 @@ 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.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; @@ -58,101 +63,58 @@ public class DefaultSockJsService extends AbstractSockJsService { private final Map transportHandlers = new HashMap(); - private final Map transportHandlerOverrides = new HashMap(); - - private TaskSchedulerHolder sessionTimeoutSchedulerHolder; - private final Map sessions = new ConcurrentHashMap(); + private ScheduledFuture sessionCleanupTask; - public DefaultSockJsService() { - this.sessionTimeoutSchedulerHolder = new TaskSchedulerHolder("SockJs-sessionTimeout-"); + + public DefaultSockJsService(TaskScheduler taskScheduler) { + this(taskScheduler, null); } - public DefaultSockJsService(TaskScheduler heartbeatScheduler, TaskScheduler sessionTimeoutScheduler) { - Assert.notNull(sessionTimeoutScheduler, "sessionTimeoutScheduler is required"); - this.sessionTimeoutSchedulerHolder = new TaskSchedulerHolder(sessionTimeoutScheduler); + public DefaultSockJsService(TaskScheduler taskScheduler, Set transportHandlers, + TransportHandler... transportHandlerOverrides) { + + super(taskScheduler); + + transportHandlers = CollectionUtils.isEmpty(transportHandlers) ? getDefaultTransportHandlers() : transportHandlers; + addTransportHandlers(transportHandlers); + addTransportHandlers(Arrays.asList(transportHandlerOverrides)); } - public void setTransportHandlers(TransportHandler... handlers) { - this.transportHandlers.clear(); + protected Set getDefaultTransportHandlers() { + Set result = new HashSet(); + result.add(new XhrPollingTransportHandler()); + result.add(new XhrTransportHandler()); + result.add(new JsonpPollingTransportHandler()); + result.add(new JsonpTransportHandler()); + result.add(new XhrStreamingTransportHandler()); + result.add(new EventSourceTransportHandler()); + result.add(new HtmlFileTransportHandler()); + if (isWebSocketEnabled()) { + try { + result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler())); + } + catch (Exception ex) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to add default WebSocketTransportHandler: " + ex.getMessage()); + } + } + } + return result; + } + + protected void addTransportHandlers(Collection handlers) { for (TransportHandler handler : handlers) { + if (handler instanceof ConfigurableTransportHandler) { + ((ConfigurableTransportHandler) handler).setSockJsConfiguration(this); + } this.transportHandlers.put(handler.getTransportType(), handler); } } - public void setTransportHandlerOverrides(TransportHandler... handlers) { - this.transportHandlerOverrides.clear(); - for (TransportHandler handler : handlers) { - this.transportHandlerOverrides.put(handler.getTransportType(), handler); - } - } - - - @Override - public void afterPropertiesSet() throws Exception { - - super.afterPropertiesSet(); - - if (this.transportHandlers.isEmpty()) { - if (isWebSocketEnabled() && (this.transportHandlerOverrides.get(TransportType.WEBSOCKET) == null)) { - this.transportHandlers.put(TransportType.WEBSOCKET, - new WebSocketTransportHandler(new DefaultHandshakeHandler())); - } - this.transportHandlers.put(TransportType.XHR, new XhrPollingTransportHandler()); - this.transportHandlers.put(TransportType.XHR_SEND, new XhrTransportHandler()); - this.transportHandlers.put(TransportType.JSONP, new JsonpPollingTransportHandler()); - this.transportHandlers.put(TransportType.JSONP_SEND, new JsonpTransportHandler()); - this.transportHandlers.put(TransportType.XHR_STREAMING, new XhrStreamingTransportHandler()); - this.transportHandlers.put(TransportType.EVENT_SOURCE, new EventSourceTransportHandler()); - this.transportHandlers.put(TransportType.HTML_FILE, new HtmlFileTransportHandler()); - } - - if (!this.transportHandlerOverrides.isEmpty()) { - for (TransportHandler transportHandler : this.transportHandlerOverrides.values()) { - this.transportHandlers.put(transportHandler.getTransportType(), transportHandler); - } - } - - for (TransportHandler h : this.transportHandlers.values()) { - if (h instanceof ConfigurableTransportHandler) { - ((ConfigurableTransportHandler) h).setSockJsConfiguration(this); - } - } - - this.sessionTimeoutSchedulerHolder.initialize(); - - this.sessionTimeoutSchedulerHolder.getScheduler().scheduleAtFixedRate(new Runnable() { - public void run() { - try { - int count = sessions.size(); - if (logger.isTraceEnabled() && (count != 0)) { - logger.trace("Checking " + count + " session(s) for timeouts [" + getName() + "]"); - } - for (AbstractSockJsSession session : sessions.values()) { - if (session.getTimeSinceLastActive() > getDisconnectDelay()) { - if (logger.isTraceEnabled()) { - logger.trace("Removing " + session + " for [" + getName() + "]"); - } - session.close(); - sessions.remove(session.getId()); - } - } - if (logger.isTraceEnabled() && (count != 0)) { - logger.trace(sessions.size() + " remaining session(s) [" + getName() + "]"); - } - } - catch (Throwable t) { - logger.error("Failed to complete session timeout checks for [" + getName() + "]", t); - } - } - }, getDisconnectDelay()); - } - - @Override - public void destroy() throws Exception { - super.destroy(); - this.sessionTimeoutSchedulerHolder.destroy(); + public Map getTransportHandlers() { + return Collections.unmodifiableMap(this.transportHandlers); } @Override @@ -239,6 +201,9 @@ public class DefaultSockJsService extends AbstractSockJsService { if (session != null) { return session; } + if (this.sessionCleanupTask == null) { + scheduleSessionTask(); + } logger.debug("Creating new session with session id \"" + sessionId + "\""); session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, handler); this.sessions.put(sessionId, session); @@ -249,4 +214,32 @@ public class DefaultSockJsService extends AbstractSockJsService { return null; } + private void scheduleSessionTask() { + this.sessionCleanupTask = getTaskScheduler().scheduleAtFixedRate(new Runnable() { + public void run() { + try { + int count = sessions.size(); + if (logger.isTraceEnabled() && (count != 0)) { + logger.trace("Checking " + count + " session(s) for timeouts [" + getName() + "]"); + } + for (AbstractSockJsSession session : sessions.values()) { + if (session.getTimeSinceLastActive() > getDisconnectDelay()) { + if (logger.isTraceEnabled()) { + logger.trace("Removing " + session + " for [" + getName() + "]"); + } + session.close(); + sessions.remove(session.getId()); + } + } + if (logger.isTraceEnabled() && (count != 0)) { + logger.trace(sessions.size() + " remaining session(s) [" + getName() + "]"); + } + } + catch (Throwable t) { + logger.error("Failed to complete session timeout checks for [" + getName() + "]", t); + } + } + }, getDisconnectDelay()); + } + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index 29b465e477..05eddadd77 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -15,9 +15,11 @@ */ package org.springframework.websocket.client; +import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpHeaders; +import org.springframework.util.CollectionUtils; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -37,7 +39,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag private WebSocketSession webSocketSession; - private List subProtocols; + private final List subProtocols = new ArrayList(); public WebSocketConnectionManager(WebSocketClient webSocketClient, @@ -57,7 +59,10 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag } public void setSubProtocols(List subProtocols) { - this.subProtocols = subProtocols; + this.subProtocols.clear(); + if (!CollectionUtils.isEmpty(subProtocols)) { + this.subProtocols.addAll(subProtocols); + } } public List getSubProtocols() { diff --git a/spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java new file mode 100644 index 0000000000..77ee2edf15 --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java @@ -0,0 +1,55 @@ +/* + * 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.support; + +import java.util.Map; + +import org.junit.Test; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.sockjs.server.TransportHandler; +import org.springframework.sockjs.server.TransportType; + +import static org.junit.Assert.*; + + +/** + * Test fixture for {@link DefaultSockJsService}. + * + * @author Rossen Stoyanchev + */ +public class DefaultSockJsServiceTests { + + + @Test + public void testDefaultTransportHandlers() { + + DefaultSockJsService sockJsService = new DefaultSockJsService(new ThreadPoolTaskScheduler()); + Map handlers = sockJsService.getTransportHandlers(); + + assertEquals(8, handlers.size()); + assertNotNull(handlers.get(TransportType.WEBSOCKET)); + assertNotNull(handlers.get(TransportType.XHR)); + assertNotNull(handlers.get(TransportType.XHR_SEND)); + assertNotNull(handlers.get(TransportType.XHR_STREAMING)); + assertNotNull(handlers.get(TransportType.JSONP)); + assertNotNull(handlers.get(TransportType.JSONP_SEND)); + assertNotNull(handlers.get(TransportType.HTML_FILE)); + assertNotNull(handlers.get(TransportType.EVENT_SOURCE)); + } + + +} From 34c95034d8437249b573912aaf275c0a734b15a7 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 24 Apr 2013 13:29:08 -0400 Subject: [PATCH 34/51] Fix minor issue and polish --- .../sockjs/AbstractSockJsSession.java | 2 +- .../server/support/DefaultSockJsService.java | 41 ++++++++++++++----- .../transport/SockJsWebSocketHandler.java | 2 +- .../websocket/CloseStatus.java | 12 ++---- .../websocket/WebSocketHandler.java | 2 +- .../websocket/client/WebSocketClient.java | 3 ++ .../endpoint/StandardWebSocketClient.java | 8 ++++ .../endpoint/WebSocketHandlerEndpoint.java | 12 +++--- .../support/JettyRequestUpgradeStrategy.java | 4 +- .../support/WebSocketHttpRequestHandler.java | 17 ++++---- 10 files changed, 66 insertions(+), 37 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index d8565455c1..24c5e84cbb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -139,7 +139,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } } - public void delegateError(Throwable ex) { + public void delegateError(Throwable ex) throws Exception { this.handler.handleError(ex, this); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 5a4baadf3f..3d389353e9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -36,6 +36,7 @@ import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.server.AbstractSockJsService; import org.springframework.sockjs.server.ConfigurableTransportHandler; +import org.springframework.sockjs.server.SockJsService; import org.springframework.sockjs.server.TransportHandler; import org.springframework.sockjs.server.TransportType; import org.springframework.sockjs.server.transport.EventSourceTransportHandler; @@ -54,7 +55,8 @@ import org.springframework.websocket.server.HandshakeHandler; /** - * TODO + * A default implementation of {@link SockJsService} adding support for transport handling + * and session management. * * @author Rossen Stoyanchev * @since 4.0 @@ -68,10 +70,31 @@ public class DefaultSockJsService extends AbstractSockJsService { private ScheduledFuture sessionCleanupTask; + /** + * Create an instance with default {@link TransportHandler transport handler} types. + * + * @param taskScheduler a task scheduler for heart-beat messages and removing + * timed-out sessions; the provided TaskScheduler should be declared as a + * Spring bean to ensure it is initialized at start up and shut down when the + * application stops. + */ public DefaultSockJsService(TaskScheduler taskScheduler) { this(taskScheduler, null); } + /** + * Create an instance by overriding or replacing completely the default + * {@link TransportHandler transport handler} types. + * + * @param taskScheduler a task scheduler for heart-beat messages and removing + * timed-out sessions; the provided TaskScheduler should be declared as a + * Spring bean to ensure it is initialized at start up and shut down when the + * application stops. + * @param transportHandlers the transport handlers to use (replaces the default ones); + * can be {@code null}. + * @param transportHandlerOverrides zero or more overrides to the default transport + * handler types. + */ public DefaultSockJsService(TaskScheduler taskScheduler, Set transportHandlers, TransportHandler... transportHandlerOverrides) { @@ -82,7 +105,7 @@ public class DefaultSockJsService extends AbstractSockJsService { addTransportHandlers(Arrays.asList(transportHandlerOverrides)); } - protected Set getDefaultTransportHandlers() { + protected final Set getDefaultTransportHandlers() { Set result = new HashSet(); result.add(new XhrPollingTransportHandler()); result.add(new XhrTransportHandler()); @@ -91,14 +114,12 @@ public class DefaultSockJsService extends AbstractSockJsService { result.add(new XhrStreamingTransportHandler()); result.add(new EventSourceTransportHandler()); result.add(new HtmlFileTransportHandler()); - if (isWebSocketEnabled()) { - try { - result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler())); - } - catch (Exception ex) { - if (logger.isWarnEnabled()) { - logger.warn("Failed to add default WebSocketTransportHandler: " + ex.getMessage()); - } + try { + result.add(new WebSocketTransportHandler(new DefaultHandshakeHandler())); + } + catch (Exception ex) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to add default WebSocketTransportHandler: " + ex.getMessage()); } } return result; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index eef056aab1..07b6cf3457 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -102,7 +102,7 @@ public class SockJsWebSocketHandler implements TextMessageHandler { } @Override - public void handleError(Throwable exception, WebSocketSession webSocketSession) { + public void handleError(Throwable exception, WebSocketSession webSocketSession) throws Exception { this.session.delegateError(exception); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java index 979ec4090f..b0f751392d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java @@ -113,19 +113,15 @@ public final class CloseStatus { public static final CloseStatus SERVER_ERROR = new CloseStatus(1011); /** - * 1012 indicates that the service is restarted. A client may reconnect, and if it - * choses to do, should reconnect using a randomized delay of 5 - 30s. - *

See - * [hybi] Additional WebSocket Close Error Codes + * "1012 indicates that the service is restarted. A client may reconnect, and if it + * chooses to do, should reconnect using a randomized delay of 5 - 30s." */ public static final CloseStatus SERVICE_RESTARTED = new CloseStatus(1012); /** - * 1013 indicates that the service is experiencing overload. A client should only + * "1013 indicates that the service is experiencing overload. A client should only * connect to a different IP (when there are multiple for the target) or reconnect to - * the same IP upon user action. - *

See - * [hybi] Additional WebSocket Close Error Codes + * the same IP upon user action." */ public static final CloseStatus SERVICE_OVERLOAD = new CloseStatus(1013); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index b420e83940..a4972eeb46 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -39,6 +39,6 @@ public interface WebSocketHandler { /** * TODO */ - void handleError(Throwable exception, WebSocketSession session); + void handleError(Throwable exception, WebSocketSession session) throws Exception; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java index 579fcb1d8a..169170da8d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -37,6 +37,9 @@ import org.springframework.websocket.WebSocketSession; public interface WebSocketClient { + WebSocketSession doHandshake(WebSocketHandler handler, + String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; + WebSocketSession doHandshake(HandlerProvider handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index 1a15d4bd95..97655d29e9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -38,6 +38,7 @@ import org.springframework.websocket.client.WebSocketClient; import org.springframework.websocket.client.WebSocketConnectFailureException; import org.springframework.websocket.endpoint.StandardWebSocketSession; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; +import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -59,6 +60,13 @@ public class StandardWebSocketClient implements WebSocketClient { this.webSocketContainer = container; } + @Override + public WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables) + throws WebSocketConnectFailureException { + + return doHandshake(new SimpleHandlerProvider(handler), uriTemplate, uriVariables); + } + public WebSocketSession doHandshake(HandlerProvider handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 7f7bd50a45..1b15116486 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -67,7 +67,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); if (logger.isDebugEnabled()) { - logger.debug("Client connected, javax.websocket.Session id=" + logger.debug("Connection established, javax.websocket.Session id=" + session.getId() + ", uri=" + session.getRequestURI()); } @@ -110,7 +110,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.handler.afterConnectionEstablished(this.webSocketSession); } catch (Throwable ex) { - this.handler.handleError(ex, this.webSocketSession); + onError(session, ex); } } @@ -123,7 +123,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { ((TextMessageHandler) handler).handleTextMessage(textMessage, this.webSocketSession); } catch (Throwable ex) { - this.handler.handleError(ex, this.webSocketSession); + onError(session, ex); } } @@ -136,21 +136,21 @@ public class WebSocketHandlerEndpoint extends Endpoint { ((BinaryMessageHandler) handler).handleBinaryMessage(binaryMessage, this.webSocketSession); } catch (Throwable ex) { - this.handler.handleError(ex, this.webSocketSession); + onError(session, ex); } } @Override public void onClose(javax.websocket.Session session, CloseReason reason) { if (logger.isDebugEnabled()) { - logger.debug("Client disconnected, WebSocket session id=" + session.getId() + ", " + reason); + logger.debug("Connection closed, WebSocket session id=" + session.getId() + ", " + reason); } try { CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); this.handler.afterConnectionClosed(closeStatus, this.webSocketSession); } catch (Throwable ex) { - logger.error("Error while processing session closing", ex); + onError(session, ex); } finally { this.handlerProvider.destroy(this.handler); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index a00ee912ef..6be0568cb8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -143,7 +143,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { try { this.session = new WebSocketSessionAdapter(session); if (logger.isDebugEnabled()) { - logger.debug("Client connected, WebSocket session id=" + logger.debug("Connection established, WebSocket session id=" + this.session.getId() + ", uri=" + this.session.getURI()); } this.handler = this.provider.getHandler(); @@ -167,7 +167,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { try { CloseStatus closeStatus = new CloseStatus(statusCode, reason); if (logger.isDebugEnabled()) { - logger.debug("Client disconnected, WebSocket session id=" + logger.debug("Connection closed, WebSocket session id=" + this.session.getId() + ", " + closeStatus); } this.handler.afterConnectionClosed(closeStatus, this.session); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 096ee1ca0b..343b065f55 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -44,25 +44,26 @@ import org.springframework.websocket.support.SimpleHandlerProvider; */ public class WebSocketHttpRequestHandler implements HttpRequestHandler { - private HandshakeHandler handshakeHandler; + private final HandshakeHandler handshakeHandler; private final HandlerProvider handlerProvider; public WebSocketHttpRequestHandler(WebSocketHandler webSocketHandler) { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.handlerProvider = new SimpleHandlerProvider(webSocketHandler); - this.handshakeHandler = new DefaultHandshakeHandler(); + this(new SimpleHandlerProvider(webSocketHandler)); } public WebSocketHttpRequestHandler( HandlerProvider handlerProvider) { - Assert.notNull(handlerProvider, "handlerProvider is required"); - this.handlerProvider = handlerProvider; + this(handlerProvider, new DefaultHandshakeHandler()); } - public void setHandshakeHandler(HandshakeHandler handshakeHandler) { + public WebSocketHttpRequestHandler( HandlerProvider handlerProvider, + HandshakeHandler handshakeHandler) { + + Assert.notNull(handlerProvider, "handlerProvider is required"); Assert.notNull(handshakeHandler, "handshakeHandler is required"); - this.handshakeHandler = handshakeHandler; + this.handlerProvider = handlerProvider; + this.handshakeHandler = new DefaultHandshakeHandler(); } @Override From 8200601acebfac7e768942acf9f2f6688c19fc38 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 25 Apr 2013 18:23:16 -0400 Subject: [PATCH 35/51] Tighten up exception handling strategy WebSocketHandler implementations: - methods must deal with exceptions locally - uncaught runtime exceptions are handled by ending the session - transport errors (websocket engine) are passed into handleError WebSocketSession methods may raise IOException SockJS implementation of WebSocketHandler: - delegate SockJS transport errors into handleError - stop runtime exceptions from user WebSocketHandler and end session SockJsServce and TransportHandlers: - raise IOException or TransportErrorException HandshakeHandler: - raise IOException --- .../sockjs/AbstractSockJsSession.java | 84 +++++++++++++--- .../server/AbstractServerSockJsSession.java | 16 ++-- .../sockjs/server/AbstractSockJsService.java | 14 +-- .../sockjs/server/SockJsService.java | 4 +- .../server/TransportErrorException.java | 55 +++++++++++ .../sockjs/server/TransportHandler.java | 2 +- .../server/support/DefaultSockJsService.java | 7 +- ...AbstractHttpReceivingTransportHandler.java | 28 ++++-- .../AbstractHttpSendingTransportHandler.java | 34 +++---- .../AbstractHttpServerSockJsSession.java | 95 ++++++++++++------- .../AbstractStreamingTransportHandler.java | 62 ------------ .../EventSourceTransportHandler.java | 20 ++-- .../transport/HtmlFileTransportHandler.java | 49 ++++++---- .../JsonpPollingTransportHandler.java | 19 ++-- .../transport/JsonpTransportHandler.java | 13 ++- .../transport/PollingServerSockJsSession.java | 6 +- .../transport/SockJsWebSocketHandler.java | 83 ++++++++-------- .../StreamingServerSockJsSession.java | 22 ++++- .../transport/WebSocketTransportHandler.java | 16 +++- .../XhrStreamingTransportHandler.java | 26 +++-- .../websocket/BinaryMessageHandler.java | 3 +- .../websocket/TextMessageHandler.java | 3 +- .../websocket/WebSocketHandler.java | 6 +- .../websocket/WebSocketHandlerAdapter.java | 6 +- .../websocket/WebSocketSession.java | 8 +- .../endpoint/WebSocketHandlerEndpoint.java | 40 ++++++-- .../server/DefaultHandshakeHandler.java | 15 ++- .../websocket/server/HandshakeHandler.java | 4 +- .../server/RequestUpgradeStrategy.java | 4 +- .../AbstractEndpointUpgradeStrategy.java | 6 +- .../GlassfishRequestUpgradeStrategy.java | 31 ++++-- .../support/JettyRequestUpgradeStrategy.java | 91 ++++++++++-------- 32 files changed, 556 insertions(+), 316 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 24c5e84cbb..ee8157e5b6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -16,6 +16,7 @@ package org.springframework.sockjs; +import java.io.IOException; import java.net.URI; import org.apache.commons.logging.Log; @@ -121,10 +122,15 @@ public abstract class AbstractSockJsSession implements WebSocketSession { this.timeLastActive = System.currentTimeMillis(); } - public void delegateConnectionEstablished() throws Exception { + public void delegateConnectionEstablished() { this.state = State.OPEN; initHandler(); - this.handler.afterConnectionEstablished(this); + try { + this.handler.afterConnectionEstablished(this); + } + catch (Throwable ex) { + tryCloseWithError(ex, null); + } } private void initHandler() { @@ -133,14 +139,61 @@ public abstract class AbstractSockJsSession implements WebSocketSession { this.handler = (TextMessageHandler) webSocketHandler; } - public void delegateMessages(String[] messages) throws Exception { - for (String message : messages) { - this.handler.handleTextMessage(new TextMessage(message), this); + /** + * Close due to unhandled runtime error from WebSocketHandler. + * @param closeStatus TODO + */ + private void tryCloseWithError(Throwable ex, CloseStatus closeStatus) { + logger.error("Unhandled error for " + this, ex); + try { + closeStatus = (closeStatus != null) ? closeStatus : CloseStatus.SERVER_ERROR; + close(closeStatus); + } + catch (Throwable t) { + destroyHandler(); } } - public void delegateError(Throwable ex) throws Exception { - this.handler.handleError(ex, this); + private void destroyHandler() { + try { + if (this.handler != null) { + this.handlerProvider.destroy(this.handler); + } + } + catch (Throwable t) { + logger.warn("Error while destroying handler", t); + } + finally { + this.handler = null; + } + } + + /** + * Close due to error arising from SockJS transport handling. + */ + protected void tryCloseWithSockJsTransportError(Throwable ex, CloseStatus closeStatus) { + delegateError(ex); + tryCloseWithError(ex, closeStatus); + } + + public void delegateMessages(String[] messages) { + try { + for (String message : messages) { + this.handler.handleTextMessage(new TextMessage(message), this); + } + } + catch (Throwable ex) { + tryCloseWithError(ex, null); + } + } + + public void delegateError(Throwable ex) { + try { + this.handler.handleTransportError(ex, this); + } + catch (Throwable t) { + tryCloseWithError(t, null); + } } /** @@ -149,7 +202,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * {@link TextMessageHandler}. This is in contrast to {@link #close()} that pro-actively * closes the connection. */ - public final void delegateConnectionClosed(CloseStatus status) throws Exception { + public final void delegateConnectionClosed(CloseStatus status) { if (!isClosed()) { if (logger.isDebugEnabled()) { logger.debug(this + " was closed, " + status); @@ -159,7 +212,12 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - this.handler.afterConnectionClosed(status, this); + try { + this.handler.afterConnectionClosed(status, this); + } + finally { + destroyHandler(); + } } } } @@ -171,7 +229,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * {@inheritDoc} *

Performs cleanup and notifies the {@link SockJsHandler}. */ - public final void close() throws Exception { + public final void close() throws IOException { close(CloseStatus.NORMAL); } @@ -179,7 +237,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * {@inheritDoc} *

Performs cleanup and notifies the {@link SockJsHandler}. */ - public final void close(CloseStatus status) throws Exception { + public final void close(CloseStatus status) throws IOException { if (!isClosed()) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this + ", " + status); @@ -193,13 +251,13 @@ public abstract class AbstractSockJsSession implements WebSocketSession { this.handler.afterConnectionClosed(status, this); } finally { - this.handlerProvider.destroy(this.handler); + destroyHandler(); } } } } - protected abstract void closeInternal(CloseStatus status) throws Exception; + protected abstract void closeInternal(CloseStatus status) throws IOException; @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index a39e5d6789..13ff66c27a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -56,13 +56,13 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession return this.sockJsConfig; } - public final synchronized void sendMessage(WebSocketMessage message) throws Exception { - Assert.isTrue(!isClosed(), "Cannot send a message, session has been closed"); + public final synchronized void sendMessage(WebSocketMessage message) throws IOException { + Assert.isTrue(!isClosed(), "Cannot send a message when session is closed"); Assert.isInstanceOf(TextMessage.class, message, "Expected text message: " + message); sendMessageInternal(((TextMessage) message).getPayload()); } - protected abstract void sendMessageInternal(String message) throws Exception; + protected abstract void sendMessageInternal(String message) throws IOException; @Override @@ -72,7 +72,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession } @Override - public final synchronized void closeInternal(CloseStatus status) throws Exception { + public final synchronized void closeInternal(CloseStatus status) throws IOException { if (isActive()) { // TODO: deliver messages "in flight" before sending close frame try { @@ -89,13 +89,13 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession } // TODO: close status/reason - protected abstract void disconnect(CloseStatus status) throws Exception; + protected abstract void disconnect(CloseStatus status) throws IOException; /** * For internal use within a TransportHandler and the (TransportHandler-specific) * session sub-class. */ - protected void writeFrame(SockJsFrame frame) throws Exception { + protected void writeFrame(SockJsFrame frame) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Preparing to write " + frame); } @@ -115,7 +115,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession catch (Throwable ex) { logger.warn("Terminating connection due to failure to send message: " + ex.getMessage()); close(); - throw new NestedSockJsRuntimeException("Failed to write frame " + frame, ex); + throw new NestedSockJsRuntimeException("Failed to write " + frame, ex); } } @@ -140,7 +140,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession try { sendHeartbeat(); } - catch (Exception e) { + catch (Throwable t) { // ignore } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index e44c44cec0..755c8ee4b6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -201,7 +201,8 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * @throws Exception */ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, HandlerProvider handler) throws Exception { + String sockJsPath, HandlerProvider handler) + throws IOException, TransportErrorException { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -255,10 +256,11 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf } protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws Exception; + HandlerProvider handler) throws IOException; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, HandlerProvider handler) throws Exception; + String sessionId, TransportType transportType, HandlerProvider handler) + throws IOException, TransportErrorException; protected boolean validateRequest(String serverId, String sessionId, String transport) { @@ -321,7 +323,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf private interface SockJsRequestHandler { - void handle(ServerHttpRequest request, ServerHttpResponse response) throws Exception; + void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException; } private static final Random random = new Random(); @@ -331,7 +333,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf private static final String INFO_CONTENT = "{\"entropy\":%s,\"origins\":[\"*:*\"],\"cookie_needed\":%s,\"websocket\":%s}"; - public void handle(ServerHttpRequest request, ServerHttpResponse response) throws Exception { + public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { if (HttpMethod.GET.equals(request.getMethod())) { @@ -376,7 +378,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf "\n" + ""; - public void handle(ServerHttpRequest request, ServerHttpResponse response) throws Exception { + public void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException { if (!HttpMethod.GET.equals(request.getMethod())) { sendMethodNotAllowed(response, Arrays.asList(HttpMethod.GET)); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index eeef0913d1..8df23d2134 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -16,6 +16,8 @@ package org.springframework.sockjs.server; +import java.io.IOException; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; @@ -31,6 +33,6 @@ public interface SockJsService { void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, - HandlerProvider handler) throws Exception; + HandlerProvider handler) throws IOException, TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java new file mode 100644 index 0000000000..90eb5e8170 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java @@ -0,0 +1,55 @@ +/* + * 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.springframework.core.NestedRuntimeException; +import org.springframework.websocket.WebSocketHandler; + + +/** + * Raised when a TransportHandler fails during request processing. + * + *

If the underlying exception occurs while sending messages to the client, + * the session will have been closed and the {@link WebSocketHandler} notified. + * + *

If the underlying exception occurs while processing an incoming HTTP request + * including posted messages, the session will remain open. Only the incoming + * request is rejected. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +@SuppressWarnings("serial") +public class TransportErrorException extends NestedRuntimeException { + + private final String sockJsSessionId; + + public TransportErrorException(String msg, Throwable cause, String sockJsSessionId) { + super(msg, cause); + this.sockJsSessionId = sockJsSessionId; + } + + public String getSockJsSessionId() { + return sockJsSessionId; + } + + @Override + public String getMessage() { + return "Transport error for SockJS session id=" + this.sockJsSessionId + ", " + super.getMessage(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 1d0eedb766..6364a1c83f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -32,6 +32,6 @@ public interface TransportHandler { TransportType getTransportType(); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler, AbstractSockJsSession session) throws Exception; + HandlerProvider handler, AbstractSockJsSession session) throws TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 3d389353e9..925ba0305a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -15,6 +15,7 @@ */ package org.springframework.sockjs.server.support; +import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -37,6 +38,7 @@ import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.server.AbstractSockJsService; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsService; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportHandler; import org.springframework.sockjs.server.TransportType; import org.springframework.sockjs.server.transport.EventSourceTransportHandler; @@ -140,7 +142,7 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws Exception { + HandlerProvider handler) throws IOException { if (isWebSocketEnabled()) { TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); @@ -157,7 +159,8 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, HandlerProvider handler) throws Exception { + String sessionId, TransportType transportType, HandlerProvider handler) + throws IOException, TransportErrorException { TransportHandler transportHandler = this.transportHandlers.get(transportType); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index d3928730a4..99a59a723f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -26,6 +26,7 @@ import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportHandler; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -54,7 +55,8 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider webSocketHandler, AbstractSockJsSession session) throws Exception { + HandlerProvider webSocketHandler, AbstractSockJsSession session) + throws TransportErrorException { if (session == null) { response.setStatusCode(HttpStatus.NOT_FOUND); @@ -65,20 +67,22 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport } protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractSockJsSession session) throws Exception { + AbstractSockJsSession session) throws TransportErrorException { String[] messages = null; try { messages = readMessages(request); } catch (JsonMappingException ex) { - response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - response.getBody().write("Payload expected.".getBytes("UTF-8")); + sendInternalServerError(response, "Payload expected.", session.getId()); return; } catch (IOException ex) { - response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - response.getBody().write("Broken JSON encoding.".getBytes("UTF-8")); + sendInternalServerError(response, "Broken JSON encoding.", session.getId()); + return; + } + catch (Throwable t) { + sendInternalServerError(response, "Failed to process messages", session.getId()); return; } @@ -92,6 +96,18 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); } + protected void sendInternalServerError(ServerHttpResponse response, String error, + String sessionId) throws TransportErrorException { + + try { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write(error.getBytes("UTF-8")); + } + catch (Throwable t) { + throw new TransportErrorException("Failed to send error message to client", t, sessionId); + } + } + protected abstract String[] readMessages(ServerHttpRequest request) throws IOException; protected abstract HttpStatus getResponseStatus(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index e2218715b0..e2aa777fb5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -27,6 +27,7 @@ import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -56,7 +57,8 @@ public abstract class AbstractHttpSendingTransportHandler @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider webSocketHandler, AbstractSockJsSession session) throws Exception { + HandlerProvider webSocketHandler, AbstractSockJsSession session) + throws TransportErrorException { // Set content type before writing response.getHeaders().setContentType(getContentType()); @@ -66,30 +68,28 @@ public abstract class AbstractHttpSendingTransportHandler } protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSockJsSession httpServerSession) throws Exception, IOException { + AbstractHttpServerSockJsSession httpServerSession) throws TransportErrorException { if (httpServerSession.isNew()) { - handleNewSession(request, response, httpServerSession); + logger.debug("Opening " + getTransportType() + " connection"); + httpServerSession.setInitialRequest(request, response, getFrameFormat(request)); } - else if (httpServerSession.isActive()) { - logger.debug("another " + getTransportType() + " connection still open: " + httpServerSession); - httpServerSession.writeFrame(response, SockJsFrame.closeFrameAnotherConnectionOpen()); + else if (!httpServerSession.isActive()) { + logger.debug("starting " + getTransportType() + " async request"); + httpServerSession.setLongPollingRequest(request, response, getFrameFormat(request)); } else { - logger.debug("starting " + getTransportType() + " async request"); - httpServerSession.setCurrentRequest(request, response, getFrameFormat(request)); + try { + logger.debug("another " + getTransportType() + " connection still open: " + httpServerSession); + SockJsFrame closeFrame = SockJsFrame.closeFrameAnotherConnectionOpen(); + response.getBody().write(getFrameFormat(request).format(closeFrame).getContentBytes()); + } + catch (IOException e) { + throw new TransportErrorException("Failed to send SockJS close frame", e, httpServerSession.getId()); + } } } - protected void handleNewSession(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSockJsSession session) throws Exception { - - logger.debug("Opening " + getTransportType() + " connection"); - session.setFrameFormat(getFrameFormat(request)); - session.writeFrame(response, SockJsFrame.openFrame()); - session.delegateConnectionEstablished(); - } - protected abstract MediaType getContentType(); protected abstract FrameFormat getFrameFormat(ServerHttpRequest request); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 72aa97e7c1..7e6742a1e1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -25,8 +25,8 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportHandler; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; @@ -55,32 +55,64 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock super(sessionId, sockJsConfig, handler); } - public void setFrameFormat(FrameFormat frameFormat) { - this.frameFormat = frameFormat; + public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response, + FrameFormat frameFormat) throws TransportErrorException { + + try { + udpateRequest(request, response, frameFormat); + writePrelude(); + writeFrame(SockJsFrame.openFrame()); + } + catch (Throwable t) { + tryCloseWithSockJsTransportError(t, null); + throw new TransportErrorException("Failed open SockJS session", t, getId()); + } + delegateConnectionEstablished(); } - public synchronized void setCurrentRequest(ServerHttpRequest request, ServerHttpResponse response, - FrameFormat frameFormat) throws Exception { + protected void writePrelude() throws IOException { + } - if (isClosed()) { - logger.debug("connection already closed"); - writeFrame(response, SockJsFrame.closeFrameGoAway()); - return; + public synchronized void setLongPollingRequest(ServerHttpRequest request, ServerHttpResponse response, + FrameFormat frameFormat) throws TransportErrorException { + + try { + udpateRequest(request, response, frameFormat); + + if (isClosed()) { + logger.debug("connection already closed"); + try { + writeFrame(SockJsFrame.closeFrameGoAway()); + } + catch (IOException ex) { + throw new TransportErrorException("Failed to send SockJS close frame", ex, getId()); + } + return; + } + + this.asyncRequest.setTimeout(-1); + this.asyncRequest.startAsync(); + + scheduleHeartbeat(); + tryFlushCache(); } + catch (Throwable t) { + tryCloseWithSockJsTransportError(t, null); + throw new TransportErrorException("Failed to start long running request and flush messages", t, getId()); + } + } + private void udpateRequest(ServerHttpRequest request, ServerHttpResponse response, FrameFormat frameFormat) { + Assert.notNull(request, "expected request"); + Assert.notNull(response, "expected response"); + Assert.notNull(frameFormat, "expected frameFormat"); Assert.isInstanceOf(AsyncServerHttpRequest.class, request, "Expected AsyncServerHttpRequest"); - this.asyncRequest = (AsyncServerHttpRequest) request; - this.asyncRequest.setTimeout(-1); - this.asyncRequest.startAsync(); - this.response = response; this.frameFormat = frameFormat; - - scheduleHeartbeat(); - tryFlushCache(); } + public synchronized boolean isActive() { return ((this.asyncRequest != null) && (!this.asyncRequest.isAsyncCompleted())); } @@ -89,18 +121,20 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock return this.messageCache; } + protected ServerHttpRequest getRequest() { + return this.asyncRequest; + } + protected ServerHttpResponse getResponse() { return this.response; } - protected final synchronized void sendMessageInternal(String message) throws Exception { - // assert close() was not called - // threads: TH-Session-Endpoint or any other thread + protected final synchronized void sendMessageInternal(String message) throws IOException { this.messageCache.add(message); tryFlushCache(); } - private void tryFlushCache() throws Exception { + private void tryFlushCache() throws IOException { if (isActive() && !getMessageCache().isEmpty()) { logger.trace("Flushing messages"); flushCache(); @@ -110,7 +144,7 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock /** * Only called if the connection is currently active */ - protected abstract void flushCache() throws Exception; + protected abstract void flushCache() throws IOException; @Override protected void disconnect(CloseStatus status) { @@ -133,21 +167,12 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock protected synchronized void writeFrameInternal(SockJsFrame frame) throws IOException { if (isActive()) { - writeFrame(this.response, frame); + frame = this.frameFormat.format(frame); + if (logger.isTraceEnabled()) { + logger.trace("Writing " + frame); + } + this.response.getBody().write(frame.getContentBytes()); } } - /** - * This method may be called by a {@link TransportHandler} to write a frame - * even when the connection is not active, as long as a valid OutputStream - * is provided. - */ - public void writeFrame(ServerHttpResponse response, SockJsFrame frame) throws IOException { - frame = this.frameFormat.format(frame); - if (logger.isTraceEnabled()) { - logger.trace("Writing " + frame); - } - response.getBody().write(frame.getContentBytes()); - } - } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java deleted file mode 100644 index ed626a2961..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractStreamingTransportHandler.java +++ /dev/null @@ -1,62 +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 java.io.IOException; - -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; -import org.springframework.websocket.WebSocketHandler; - - -/** - * TODO - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public abstract class AbstractStreamingTransportHandler extends AbstractHttpSendingTransportHandler { - - - @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { - Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler); - } - - @Override - public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSockJsSession session) throws Exception { - - writePrelude(request, response); - super.handleRequestInternal(request, response, session); - } - - protected abstract void writePrelude(ServerHttpRequest request, ServerHttpResponse response) - throws IOException; - - @Override - protected void handleNewSession(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSockJsSession session) throws IOException, Exception { - - super.handleNewSession(request, response, session); - - session.setCurrentRequest(request, response, getFrameFormat(request)); - } - -} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index ae8efbc667..700fd34706 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -20,10 +20,12 @@ 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.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; +import org.springframework.util.Assert; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.WebSocketHandler; /** @@ -32,7 +34,7 @@ import org.springframework.sockjs.server.TransportType; * @author Rossen Stoyanchev * @since 4.0 */ -public class EventSourceTransportHandler extends AbstractStreamingTransportHandler { +public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler { @Override @@ -46,10 +48,16 @@ public class EventSourceTransportHandler extends AbstractStreamingTransportHandl } @Override - protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { - response.getBody().write('\r'); - response.getBody().write('\n'); - response.flush(); + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { + @Override + protected void writePrelude() throws IOException { + getResponse().getBody().write('\r'); + getResponse().getBody().write('\n'); + getResponse().flush(); + } + }; } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index eb752cd302..ccd029d0c1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -24,9 +24,13 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportType; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.WebSocketHandler; /** @@ -35,7 +39,7 @@ import org.springframework.web.util.JavaScriptUtils; * @author Rossen Stoyanchev * @since 4.0 */ -public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler { +public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandler { private static final String PARTIAL_HTML_CONTENT; @@ -77,27 +81,40 @@ public class HtmlFileTransportHandler extends AbstractStreamingTransportHandler } @Override - public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSockJsSession session) throws Exception { + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); - String callback = request.getQueryParams().getFirst("c"); - if (! StringUtils.hasText(callback)) { - response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); - return; - } - super.handleRequestInternal(request, response, session); + return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { + + @Override + protected void writePrelude() throws IOException { + // we already validated the parameter.. + String callback = getRequest().getQueryParams().getFirst("c"); + + String html = String.format(PARTIAL_HTML_CONTENT, callback); + getResponse().getBody().write(html.getBytes("UTF-8")); + getResponse().flush(); + } + }; } @Override - protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { + public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, + AbstractHttpServerSockJsSession session) throws TransportErrorException { - // we already validated the parameter.. - String callback = request.getQueryParams().getFirst("c"); + try { + String callback = request.getQueryParams().getFirst("c"); + if (! StringUtils.hasText(callback)) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); + return; + } + } + catch (Throwable t) { + throw new TransportErrorException("Failed to send error to client", t, session.getId()); + } - String html = String.format(PARTIAL_HTML_CONTENT, callback); - response.getBody().write(html.getBytes("UTF-8")); - response.flush(); + super.handleRequestInternal(request, response, session); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index 3f5854d30b..24c754e89d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -23,6 +23,7 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -58,14 +59,20 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa @Override public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractHttpServerSockJsSession session) throws Exception { + AbstractHttpServerSockJsSession session) throws TransportErrorException { - String callback = request.getQueryParams().getFirst("c"); - if (! StringUtils.hasText(callback)) { - response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); - return; + try { + String callback = request.getQueryParams().getFirst("c"); + if (! StringUtils.hasText(callback)) { + response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + response.getBody().write("\"callback\" parameter required".getBytes("UTF-8")); + return; + } } + catch (Throwable t) { + throw new TransportErrorException("Failed to send error to client", t, session.getId()); + } + super.handleRequestInternal(request, response, session); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index 8d39e2d125..5cbd6288e2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -22,6 +22,7 @@ import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportType; public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler { @@ -34,19 +35,23 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler @Override public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, - AbstractSockJsSession sockJsSession) throws Exception { + AbstractSockJsSession sockJsSession) throws TransportErrorException { if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { if (request.getQueryParams().getFirst("d") == null) { - response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - response.getBody().write("Payload expected.".getBytes("UTF-8")); + sendInternalServerError(response, "Payload expected.", sockJsSession.getId()); return; } } super.handleRequestInternal(request, response, sockJsSession); - response.getBody().write("ok".getBytes("UTF-8")); + try { + response.getBody().write("ok".getBytes("UTF-8")); + } + catch (Throwable t) { + throw new TransportErrorException("Failed to write response body", t, sockJsSession.getId()); + } } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index 86851ab9d5..743c369487 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -15,6 +15,8 @@ */ package org.springframework.sockjs.server.transport; +import java.io.IOException; + import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.websocket.HandlerProvider; @@ -30,7 +32,7 @@ public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession } @Override - protected void flushCache() throws Exception { + protected void flushCache() throws IOException { cancelHeartbeat(); String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]); getMessageCache().clear(); @@ -38,7 +40,7 @@ public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession } @Override - protected void writeFrame(SockJsFrame frame) throws Exception { + protected void writeFrame(SockJsFrame frame) throws IOException { super.writeFrame(frame); resetRequest(); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 07b6cf3457..691571a72a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -19,9 +19,6 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; @@ -38,7 +35,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * A wrapper around a {@link WebSocketHandler} instance that parses and adds SockJS + * A wrapper around a {@link WebSocketHandler} instance that parses as well as adds SockJS * messages frames as well as sends SockJS heartbeat messages. * * @author Rossen Stoyanchev @@ -46,13 +43,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; */ public class SockJsWebSocketHandler implements TextMessageHandler { - private static final Log logger = LogFactory.getLog(SockJsWebSocketHandler.class); - private final SockJsConfiguration sockJsConfig; private final HandlerProvider handlerProvider; - private AbstractSockJsSession session; + private WebSocketServerSockJsSession sockJsSession; private final AtomicInteger sessionCount = new AtomicInteger(0); @@ -72,38 +67,25 @@ public class SockJsWebSocketHandler implements TextMessageHandler { } @Override - public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { + public void afterConnectionEstablished(WebSocketSession wsSession) { Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); - this.session = new WebSocketServerSockJsSession(wsSession, getSockJsConfig()); + this.sockJsSession = new WebSocketServerSockJsSession(getSockJsSessionId(wsSession), getSockJsConfig()); + this.sockJsSession.initWebSocketSession(wsSession); } @Override - public void handleTextMessage(TextMessage message, WebSocketSession wsSession) throws Exception { - String payload = message.getPayload(); - if (StringUtils.isEmpty(payload)) { - logger.trace("Ignoring empty message"); - return; - } - String[] messages; - try { - messages = this.objectMapper.readValue(payload, String[].class); - } - catch (IOException e) { - logger.error("Broken data received. Terminating WebSocket connection abruptly", e); - wsSession.close(); - return; - } - this.session.delegateMessages(messages); + public void handleTextMessage(TextMessage message, WebSocketSession wsSession) { + this.sockJsSession.handleMessage(message, wsSession); } @Override - public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) throws Exception { - this.session.delegateConnectionClosed(status); + public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) { + this.sockJsSession.delegateConnectionClosed(status); } @Override - public void handleError(Throwable exception, WebSocketSession webSocketSession) throws Exception { - this.session.delegateError(exception); + public void handleTransportError(Throwable exception, WebSocketSession webSocketSession) { + this.sockJsSession.delegateError(exception); } private static String getSockJsSessionId(WebSocketSession wsSession) { @@ -117,16 +99,23 @@ public class SockJsWebSocketHandler implements TextMessageHandler { private class WebSocketServerSockJsSession extends AbstractServerSockJsSession { - private final WebSocketSession wsSession; + private WebSocketSession wsSession; - public WebSocketServerSockJsSession(WebSocketSession wsSession, SockJsConfiguration sockJsConfig) - throws Exception { + public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config) { + super(sessionId, config, SockJsWebSocketHandler.this.handlerProvider); + } - super(getSockJsSessionId(wsSession), sockJsConfig, SockJsWebSocketHandler.this.handlerProvider); + public void initWebSocketSession(WebSocketSession wsSession) { this.wsSession = wsSession; - TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); - this.wsSession.sendMessage(message); + try { + TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); + this.wsSession.sendMessage(message); + } + catch (IOException ex) { + tryCloseWithSockJsTransportError(ex, null); + return; + } scheduleHeartbeat(); delegateConnectionEstablished(); } @@ -136,15 +125,33 @@ public class SockJsWebSocketHandler implements TextMessageHandler { return this.wsSession.isOpen(); } + public void handleMessage(TextMessage message, WebSocketSession wsSession) { + String payload = message.getPayload(); + if (StringUtils.isEmpty(payload)) { + logger.trace("Ignoring empty message"); + return; + } + String[] messages; + try { + messages = objectMapper.readValue(payload, String[].class); + } + catch (IOException ex) { + logger.error("Broken data received. Terminating WebSocket connection abruptly", ex); + tryCloseWithSockJsTransportError(ex, CloseStatus.BAD_DATA); + return; + } + delegateMessages(messages); + } + @Override - public void sendMessageInternal(String message) throws Exception { + public void sendMessageInternal(String message) throws IOException { cancelHeartbeat(); writeFrame(SockJsFrame.messageFrame(message)); scheduleHeartbeat(); } @Override - protected void writeFrameInternal(SockJsFrame frame) throws Exception { + protected void writeFrameInternal(SockJsFrame frame) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Write " + frame); } @@ -153,7 +160,7 @@ public class SockJsWebSocketHandler implements TextMessageHandler { } @Override - protected void disconnect(CloseStatus status) throws Exception { + protected void disconnect(CloseStatus status) throws IOException { this.wsSession.close(status); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index 094fd115e9..7e4a3c83fb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -17,9 +17,12 @@ package org.springframework.sockjs.server.transport; import java.io.IOException; +import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -35,7 +38,15 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio super(sessionId, sockJsConfig, handler); } - protected void flushCache() throws Exception { + @Override + public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response, + FrameFormat frameFormat) throws TransportErrorException { + + super.setInitialRequest(request, response, frameFormat); + super.setLongPollingRequest(request, response, frameFormat); + } + + protected void flushCache() throws IOException { cancelHeartbeat(); @@ -68,9 +79,12 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio } @Override - public void writeFrame(ServerHttpResponse response, SockJsFrame frame) throws IOException { - super.writeFrame(response, frame); - response.flush(); + protected synchronized void writeFrameInternal(SockJsFrame frame) throws IOException { + if (isActive()) { + super.writeFrameInternal(frame); + getResponse().flush(); + } } + } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 361c8b3c14..8f615c913d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -16,11 +16,14 @@ package org.springframework.sockjs.server.transport; +import java.io.IOException; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportHandler; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; @@ -62,17 +65,22 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler, AbstractSockJsSession session) throws Exception { + HandlerProvider handler, AbstractSockJsSession session) throws TransportErrorException { - WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, handler); - this.handshakeHandler.doHandshake(request, response, new SimpleHandlerProvider(sockJsWrapper)); + try { + WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, handler); + this.handshakeHandler.doHandshake(request, response, new SimpleHandlerProvider(sockJsWrapper)); + } + catch (Throwable t) { + throw new TransportErrorException("Failed to start handshake request", t, session.getId()); + } } // HandshakeHandler methods @Override public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws Exception { + HandlerProvider handler) throws IOException { return this.handshakeHandler.doHandshake(request, response, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index cf40744284..9e6c7457a4 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -20,10 +20,12 @@ 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.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; +import org.springframework.util.Assert; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.WebSocketHandler; /** @@ -32,7 +34,7 @@ import org.springframework.sockjs.server.TransportType; * @author Rossen Stoyanchev * @since 4.0 */ -public class XhrStreamingTransportHandler extends AbstractStreamingTransportHandler { +public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHandler { @Override @@ -46,12 +48,20 @@ public class XhrStreamingTransportHandler extends AbstractStreamingTransportHand } @Override - protected void writePrelude(ServerHttpRequest request, ServerHttpResponse response) throws IOException { - for (int i=0; i < 2048; i++) { - response.getBody().write('h'); - } - response.getBody().write('\n'); - response.flush(); + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); + + return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { + + @Override + protected void writePrelude() throws IOException { + for (int i=0; i < 2048; i++) { + getResponse().getBody().write('h'); + } + getResponse().getBody().write('\n'); + getResponse().flush(); + } + }; } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java index 50940e34da..bac882087f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java @@ -29,7 +29,6 @@ public interface BinaryMessageHandler extends WebSocketHandler { /** * Handle an incoming binary message. */ - void handleBinaryMessage(BinaryMessage message, WebSocketSession session) - throws Exception; + void handleBinaryMessage(BinaryMessage message, WebSocketSession session); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java index ea073eaf3d..fe79622b4b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java @@ -34,7 +34,6 @@ public interface TextMessageHandler extends WebSocketHandler { /** * Handle an incoming text message. */ - void handleTextMessage(TextMessage message, WebSocketSession session) - throws Exception; + void handleTextMessage(TextMessage message, WebSocketSession session); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index a4972eeb46..da4b841d2c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -29,16 +29,16 @@ public interface WebSocketHandler { /** * A new WebSocket connection has been opened and is ready to be used. */ - void afterConnectionEstablished(WebSocketSession session) throws Exception; + void afterConnectionEstablished(WebSocketSession session); /** * A WebSocket connection has been closed. */ - void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session) throws Exception; + void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session); /** * TODO */ - void handleError(Throwable exception, WebSocketSession session) throws Exception; + void handleTransportError(Throwable exception, WebSocketSession session); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index 4914f3767a..4d9e5259d9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -26,15 +26,15 @@ package org.springframework.websocket; public class WebSocketHandlerAdapter implements WebSocketHandler { @Override - public void afterConnectionEstablished(WebSocketSession session) throws Exception { + public void afterConnectionEstablished(WebSocketSession session) { } @Override - public void afterConnectionClosed(CloseStatus status, WebSocketSession session) throws Exception { + public void afterConnectionClosed(CloseStatus status, WebSocketSession session) { } @Override - public void handleError(Throwable exception, WebSocketSession session) { + public void handleTransportError(Throwable exception, WebSocketSession session) { } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index 2c9d4355f5..483c6f72e8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -16,10 +16,10 @@ package org.springframework.websocket; +import java.io.IOException; import java.net.URI; - /** * Allows sending messages over a WebSocket connection as well as closing it. * @@ -52,7 +52,7 @@ public interface WebSocketSession { * Send a WebSocket message either {@link TextMessage} or * {@link BinaryMessage}. */ - void sendMessage(WebSocketMessage message) throws Exception; + void sendMessage(WebSocketMessage message) throws IOException; /** * Close the WebSocket connection with status 1000, i.e. equivalent to: @@ -60,11 +60,11 @@ public interface WebSocketSession { * session.close(CloseStatus.NORMAL); * */ - void close() throws Exception; + void close() throws IOException; /** * Close the WebSocket connection with the given close status. */ - void close(CloseStatus status) throws Exception; + void close(CloseStatus status) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 1b15116486..ce9f0736ba 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -110,7 +110,34 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.handler.afterConnectionEstablished(this.webSocketSession); } catch (Throwable ex) { - onError(session, ex); + tryCloseWithError(ex); + } + } + + private void tryCloseWithError(Throwable ex) { + logger.error("Unhandled error for " + this.webSocketSession, ex); + if (this.webSocketSession.isOpen()) { + try { + this.webSocketSession.close(CloseStatus.SERVER_ERROR); + } + catch (Throwable t) { + destroyHandler(); + } + } + } + + private void destroyHandler() { + try { + if (this.handler != null) { + this.handlerProvider.destroy(this.handler); + } + } + catch (Throwable t) { + logger.warn("Error while destroying handler", t); + } + finally { + this.webSocketSession = null; + this.handler = null; } } @@ -123,7 +150,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { ((TextMessageHandler) handler).handleTextMessage(textMessage, this.webSocketSession); } catch (Throwable ex) { - onError(session, ex); + tryCloseWithError(ex); } } @@ -136,7 +163,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { ((BinaryMessageHandler) handler).handleBinaryMessage(binaryMessage, this.webSocketSession); } catch (Throwable ex) { - onError(session, ex); + tryCloseWithError(ex); } } @@ -150,7 +177,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.handler.afterConnectionClosed(closeStatus, this.webSocketSession); } catch (Throwable ex) { - onError(session, ex); + logger.error("Unhandled error for " + this.webSocketSession, ex); } finally { this.handlerProvider.destroy(this.handler); @@ -161,11 +188,10 @@ public class WebSocketHandlerEndpoint extends Endpoint { public void onError(javax.websocket.Session session, Throwable exception) { logger.error("Error for WebSocket session id=" + session.getId(), exception); try { - this.handler.handleError(exception, this.webSocketSession); + this.handler.handleTransportError(exception, this.webSocketSession); } catch (Throwable ex) { - // TODO: close the session? - logger.error("Failed to handle error", ex); + tryCloseWithError(ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 6ae721819c..79463681ea 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -88,7 +88,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws Exception { + HandlerProvider handler) throws IOException { logger.debug("Starting handshake for " + request.getURI()); @@ -199,10 +199,15 @@ public class DefaultHandshakeHandler implements HandshakeHandler { return null; } - private String getWebSocketKeyHash(String key) throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA1"); - byte[] bytes = digest.digest((key + GUID).getBytes(Charset.forName("ISO-8859-1"))); - return DatatypeConverter.printBase64Binary(bytes); + private String getWebSocketKeyHash(String key) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + byte[] bytes = digest.digest((key + GUID).getBytes(Charset.forName("ISO-8859-1"))); + return DatatypeConverter.printBase64Binary(bytes); + } + catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("Failed to generate value for Sec-WebSocket-Key header", ex); + } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 61f0e42b33..1989f61ad0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -16,6 +16,8 @@ package org.springframework.websocket.server; +import java.io.IOException; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; @@ -32,6 +34,6 @@ public interface HandshakeHandler { boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws Exception; + HandlerProvider handler) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index fadf15c99e..359cf9dc47 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -16,6 +16,8 @@ package org.springframework.websocket.server; +import java.io.IOException; + import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; @@ -43,7 +45,7 @@ public interface RequestUpgradeStrategy { * @param handler the handler for WebSocket messages */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - HandlerProvider handlerProvider) throws Exception; + HandlerProvider handlerProvider) throws IOException; // FIXME how to indicate failure to upgrade? } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index f7884838d3..ef53d8e237 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -16,6 +16,8 @@ package org.springframework.websocket.server.support; +import java.io.IOException; + import javax.websocket.Endpoint; import org.apache.commons.logging.Log; @@ -42,7 +44,7 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, HandlerProvider handler) throws Exception { + String protocol, HandlerProvider handler) throws IOException { upgradeInternal(request, response, protocol, adaptWebSocketHandler(handler)); } @@ -52,6 +54,6 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, Endpoint endpoint) throws Exception; + String selectedProtocol, Endpoint endpoint) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java index e462e65dd7..a5ca56c5b5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java @@ -16,6 +16,7 @@ package org.springframework.websocket.server.support; +import java.io.IOException; import java.lang.reflect.Constructor; import java.net.URI; import java.util.Arrays; @@ -24,6 +25,7 @@ import java.util.Random; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; +import javax.websocket.DeploymentException; import javax.websocket.Endpoint; import org.glassfish.tyrus.core.ComponentProviderService; @@ -67,7 +69,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, Endpoint endpoint) throws Exception { + String selectedProtocol, Endpoint endpoint) throws IOException { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -78,7 +80,13 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra TyrusEndpoint tyrusEndpoint = createTyrusEndpoint(servletRequest, endpoint, selectedProtocol); WebSocketEngine engine = WebSocketEngine.getEngine(); - engine.register(tyrusEndpoint); + + try { + engine.register(tyrusEndpoint); + } + catch (DeploymentException ex) { + throw new IllegalStateException("Failed to deploy endpoint in Glassfish", ex); + } try { if (!performUpgrade(servletRequest, servletResponse, request.getHeaders(), tyrusEndpoint)) { @@ -91,7 +99,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra } private boolean performUpgrade(HttpServletRequest request, HttpServletResponse response, - HttpHeaders headers, TyrusEndpoint tyrusEndpoint) throws Exception { + HttpHeaders headers, TyrusEndpoint tyrusEndpoint) throws IOException { final TyrusHttpUpgradeHandler upgradeHandler = request.upgrade(TyrusHttpUpgradeHandler.class); @@ -128,12 +136,17 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra endpointConfig.getConfigurator())); } - private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) throws Exception { - String name = "org.glassfish.tyrus.servlet.ConnectionImpl"; - Class clazz = ClassUtils.forName(name, GlassfishRequestUpgradeStrategy.class.getClassLoader()); - Constructor constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class); - ReflectionUtils.makeAccessible(constructor); - return (Connection) constructor.newInstance(handler, response); + private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) { + try { + String name = "org.glassfish.tyrus.servlet.ConnectionImpl"; + Class clazz = ClassUtils.forName(name, GlassfishRequestUpgradeStrategy.class.getClassLoader()); + Constructor constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class); + ReflectionUtils.makeAccessible(constructor); + return (Connection) constructor.newInstance(handler, response); + } + catch (Exception ex) { + throw new IllegalStateException("Failed to instantiate Glassfish connection", ex); + } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 6be0568cb8..c313f9cdc0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -18,6 +18,7 @@ package org.springframework.websocket.server.support; import java.io.IOException; import java.net.URI; +import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -101,7 +102,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, HandlerProvider handlerProvider) - throws Exception { + throws IOException { Assert.isInstanceOf(ServletServerHttpRequest.class, request); Assert.isInstanceOf(ServletServerHttpResponse.class, response); upgrade(((ServletServerHttpRequest) request).getServletRequest(), @@ -111,7 +112,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { private void upgrade(HttpServletRequest request, HttpServletResponse response, String selectedProtocol, final HandlerProvider handlerProvider) - throws Exception { + throws IOException { request.setAttribute(HANDLER_PROVIDER, handlerProvider); Assert.state(factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); Assert.state(factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); @@ -129,6 +130,8 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { private WebSocketSession session; + private final AtomicInteger sessionCount = new AtomicInteger(0); + public WebSocketHandlerAdapter(HandlerProvider provider) { Assert.notNull(provider, "Provider must not be null"); @@ -139,31 +142,53 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void onWebSocketConnect(Session session) { - Assert.state(this.session == null, "WebSocket already open"); + + Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); + + this.session = new WebSocketSessionAdapter(session); + if (logger.isDebugEnabled()) { + logger.debug("Connection established, WebSocket session id=" + + this.session.getId() + ", uri=" + this.session.getURI()); + } + this.handler = this.provider.getHandler(); + try { - this.session = new WebSocketSessionAdapter(session); - if (logger.isDebugEnabled()) { - logger.debug("Connection established, WebSocket session id=" - + this.session.getId() + ", uri=" + this.session.getURI()); - } - this.handler = this.provider.getHandler(); this.handler.afterConnectionEstablished(this.session); } - catch (Exception ex) { + catch (Throwable ex) { + tryCloseWithError(ex); + } + } + + private void tryCloseWithError(Throwable ex) { + logger.error("Unhandled error for " + this.session, ex); + if (this.session.isOpen()) { try { - // FIXME revisit after error handling - onWebSocketError(ex); + this.session.close(CloseStatus.SERVER_ERROR); } - finally { - this.session = null; - this.handler = null; + catch (Throwable t) { + destroyHandler(); } } } + private void destroyHandler() { + try { + if (this.handler != null) { + this.provider.destroy(this.handler); + } + } + catch (Throwable t) { + logger.warn("Error while destroying handler", t); + } + finally { + this.session = null; + this.handler = null; + } + } + @Override public void onWebSocketClose(int statusCode, String reason) { - Assert.state(this.session != null, "WebSocket not open"); try { CloseStatus closeStatus = new CloseStatus(statusCode, reason); if (logger.isDebugEnabled()) { @@ -172,19 +197,11 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { } this.handler.afterConnectionClosed(closeStatus, this.session); } - catch (Exception ex) { - onWebSocketError(ex); + catch (Throwable ex) { + logger.error("Unhandled error for " + this.session, ex); } finally { - try { - if (this.handler != null) { - this.provider.destroy(this.handler); - } - } - finally { - this.session = null; - this.handler = null; - } + destroyHandler(); } } @@ -200,8 +217,8 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { ((TextMessageHandler) this.handler).handleTextMessage(message, this.session); } } - catch(Exception ex) { - ex.printStackTrace(); //FIXME + catch(Throwable ex) { + tryCloseWithError(ex); } } @@ -218,20 +235,18 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { this.session); } } - catch(Exception ex) { - ex.printStackTrace(); //FIXME + catch(Throwable ex) { + tryCloseWithError(ex); } } @Override public void onWebSocketError(Throwable cause) { try { - this.handler.handleError(cause, this.session); + this.handler.handleTransportError(cause, this.session); } catch (Throwable ex) { - // FIXME exceptions - logger.error("Error for WebSocket session id=" + this.session.getId(), - cause); + tryCloseWithError(ex); } } } @@ -271,7 +286,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { } @Override - public void sendMessage(WebSocketMessage message) throws Exception { + public void sendMessage(WebSocketMessage message) throws IOException { if (message instanceof BinaryMessage) { sendMessage((BinaryMessage) message); } @@ -283,11 +298,11 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { } } - private void sendMessage(BinaryMessage message) throws Exception { + private void sendMessage(BinaryMessage message) throws IOException { this.session.getRemote().sendBytes(message.getPayload()); } - private void sendMessage(TextMessage message) throws Exception { + private void sendMessage(TextMessage message) throws IOException { this.session.getRemote().sendString(message.getPayload()); } From 9da2c21edbaa31bcbd12447afbf578b621a1de93 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 25 Apr 2013 19:01:49 -0400 Subject: [PATCH 36/51] Consolidate WebSocketHandler interface and sub-interfaces --- .../sockjs/AbstractSockJsSession.java | 11 +---- .../transport/SockJsWebSocketHandler.java | 4 +- ...ava => BinaryWebSocketHandlerAdapter.java} | 29 ++++++------ ....java => TextWebSocketHandlerAdapter.java} | 21 +++++---- .../websocket/WebSocketHandler.java | 10 ++++ .../websocket/WebSocketHandlerAdapter.java | 21 ++++----- .../endpoint/WebSocketHandlerEndpoint.java | 47 +++++++------------ .../support/JettyRequestUpgradeStrategy.java | 11 +---- 8 files changed, 69 insertions(+), 85 deletions(-) rename spring-websocket/src/main/java/org/springframework/websocket/{TextMessageHandler.java => BinaryWebSocketHandlerAdapter.java} (51%) rename spring-websocket/src/main/java/org/springframework/websocket/{BinaryMessageHandler.java => TextWebSocketHandlerAdapter.java} (63%) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index ee8157e5b6..e584333759 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -25,7 +25,6 @@ import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.TextMessageHandler; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -44,7 +43,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { private final HandlerProvider handlerProvider; - private TextMessageHandler handler; + private WebSocketHandler handler; private State state = State.NEW; @@ -124,7 +123,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { public void delegateConnectionEstablished() { this.state = State.OPEN; - initHandler(); + this.handler = handlerProvider.getHandler(); try { this.handler.afterConnectionEstablished(this); } @@ -133,12 +132,6 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } } - private void initHandler() { - WebSocketHandler webSocketHandler = handlerProvider.getHandler(); - Assert.isInstanceOf(TextMessageHandler.class, webSocketHandler, "Expected a TextMessageHandler"); - this.handler = (TextMessageHandler) webSocketHandler; - } - /** * Close due to unhandled runtime error from WebSocketHandler. * @param closeStatus TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 691571a72a..95cb4a0522 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -27,7 +27,7 @@ import org.springframework.util.StringUtils; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.TextMessageHandler; +import org.springframework.websocket.TextWebSocketHandlerAdapter; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -41,7 +41,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Rossen Stoyanchev * @since 4.0 */ -public class SockJsWebSocketHandler implements TextMessageHandler { +public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final SockJsConfiguration sockJsConfig; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java similarity index 51% rename from spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java index fe79622b4b..acfeeab6d2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/TextMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java @@ -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,27 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket; -import org.springframework.websocket.WebSocketHandlerAdapter.TextAndBinaryMessageHandlerAdapter; -import org.springframework.websocket.WebSocketHandlerAdapter.TextMessageHandlerAdapter; +import java.io.IOException; /** - * A handler for WebSocket text messages. * - * @author Rossen Stoyanchev - * @since 4.0 - * - * @see TextMessageHandlerAdapter - * @see TextAndBinaryMessageHandlerAdapter + * @author rossen */ -public interface TextMessageHandler extends WebSocketHandler { +public class BinaryWebSocketHandlerAdapter extends WebSocketHandlerAdapter { - - /** - * Handle an incoming text message. - */ - void handleTextMessage(TextMessage message, WebSocketSession session); + @Override + public void handleTextMessage(TextMessage message, WebSocketSession session) { + try { + session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Text messages not supported")); + } + catch (IOException e) { + // ignore + } + } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java similarity index 63% rename from spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java rename to spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java index bac882087f..69941bfa14 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java @@ -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,22 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket; +import java.io.IOException; /** - * A handler for WebSocket binary messages. * * @author Rossen Stoyanchev * @since 4.0 */ -public interface BinaryMessageHandler extends WebSocketHandler { +public class TextWebSocketHandlerAdapter extends WebSocketHandlerAdapter { - - /** - * Handle an incoming binary message. - */ - void handleBinaryMessage(BinaryMessage message, WebSocketSession session); + @Override + public void handleBinaryMessage(BinaryMessage message, WebSocketSession session) { + try { + session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Binary messages not supported")); + } + catch (IOException e) { + // ignore + } + } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index da4b841d2c..b835c383af 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -36,6 +36,16 @@ public interface WebSocketHandler { */ void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session); + /** + * Handle an incoming text message. + */ + void handleTextMessage(TextMessage message, WebSocketSession session); + + /** + * Handle an incoming binary message. + */ + void handleBinaryMessage(BinaryMessage message, WebSocketSession session); + /** * TODO */ diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index 4d9e5259d9..95cd36af56 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -33,21 +33,16 @@ public class WebSocketHandlerAdapter implements WebSocketHandler { public void afterConnectionClosed(CloseStatus status, WebSocketSession session) { } + @Override + public void handleTextMessage(TextMessage message, WebSocketSession session) { + } + + @Override + public void handleBinaryMessage(BinaryMessage message, WebSocketSession session) { + } + @Override public void handleTransportError(Throwable exception, WebSocketSession session) { } - - public static abstract class TextMessageHandlerAdapter - extends WebSocketHandlerAdapter implements TextMessageHandler { - } - - public static abstract class BinaryMessageHandlerAdapter - extends WebSocketHandlerAdapter implements BinaryMessageHandler { - } - - public static abstract class TextAndBinaryMessageHandlerAdapter extends WebSocketHandlerAdapter - implements TextMessageHandler, BinaryMessageHandler { - } - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index ce9f0736ba..4d02cabd6b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -27,12 +27,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.BinaryMessageHandler; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.PartialMessageHandler; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.TextMessageHandler; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -74,36 +72,27 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.webSocketSession = new StandardWebSocketSession(session); this.handler = handlerProvider.getHandler(); - if (this.handler instanceof TextMessageHandler) { - session.addMessageHandler(new MessageHandler.Whole() { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + handleTextMessage(session, message); + } + }); + if (this.handler instanceof PartialMessageHandler) { + session.addMessageHandler(new MessageHandler.Partial() { @Override - public void onMessage(String message) { - handleTextMessage(session, message); + public void onMessage(byte[] messagePart, boolean isLast) { + handleBinaryMessage(session, messagePart, isLast); } }); } - else if (this.handler instanceof BinaryMessageHandler) { - if (this.handler instanceof PartialMessageHandler) { - session.addMessageHandler(new MessageHandler.Partial() { - @Override - public void onMessage(byte[] messagePart, boolean isLast) { - handleBinaryMessage(session, messagePart, isLast); - } - }); - } - else { - session.addMessageHandler(new MessageHandler.Whole() { - @Override - public void onMessage(byte[] message) { - handleBinaryMessage(session, message, true); - } - }); - } - } else { - if (logger.isWarnEnabled()) { - logger.warn("WebSocketHandler handles neither text nor binary messages: " + this.handler); - } + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(byte[] message) { + handleBinaryMessage(session, message, true); + } + }); } try { @@ -147,7 +136,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { } try { TextMessage textMessage = new TextMessage(message); - ((TextMessageHandler) handler).handleTextMessage(textMessage, this.webSocketSession); + this.handler.handleTextMessage(textMessage, this.webSocketSession); } catch (Throwable ex) { tryCloseWithError(ex); @@ -160,7 +149,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { } try { BinaryMessage binaryMessage = new BinaryMessage(message, isLast); - ((BinaryMessageHandler) handler).handleBinaryMessage(binaryMessage, this.webSocketSession); + this.handler.handleBinaryMessage(binaryMessage, this.webSocketSession); } catch (Throwable ex) { tryCloseWithError(ex); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index c313f9cdc0..22c186637a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -40,11 +40,9 @@ import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.BinaryMessageHandler; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.TextMessageHandler; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; @@ -213,9 +211,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { logger.trace("Received message for WebSocket session id=" + this.session.getId() + ": " + message); } - if (this.handler instanceof TextMessageHandler) { - ((TextMessageHandler) this.handler).handleTextMessage(message, this.session); - } + this.handler.handleTextMessage(message, this.session); } catch(Throwable ex) { tryCloseWithError(ex); @@ -230,10 +226,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { logger.trace("Received binary data for WebSocket session id=" + this.session.getId() + ": " + message); } - if (this.handler instanceof BinaryMessageHandler) { - ((BinaryMessageHandler) this.handler).handleBinaryMessage(message, - this.session); - } + this.handler.handleBinaryMessage(message, this.session); } catch(Throwable ex) { tryCloseWithError(ex); From db2c2480dbcad7e95f5cae60d62ab014c7d96d75 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 24 Apr 2013 22:51:36 -0700 Subject: [PATCH 37/51] Javadocs and general formatting polish --- .../sockjs/AbstractSockJsSession.java | 3 +- .../sockjs/SockJsSessionFactory.java | 8 ++- .../springframework/sockjs/package-info.java | 16 +++++- .../server/AbstractServerSockJsSession.java | 1 - .../sockjs/server/AbstractSockJsService.java | 1 - .../server/ConfigurableTransportHandler.java | 2 - .../server/NestedSockJsRuntimeException.java | 2 - .../sockjs/server/SockJsConfiguration.java | 2 +- .../sockjs/server/SockJsFrame.java | 5 +- .../sockjs/server/SockJsService.java | 3 -- .../sockjs/server/TransportHandler.java | 3 +- .../sockjs/server/TransportType.java | 3 +- .../sockjs/server/package-info.java | 16 +++++- .../server/support/DefaultSockJsService.java | 1 + .../support/SockJsHttpRequestHandler.java | 3 +- .../sockjs/server/support/package-info.java | 15 ++++++ ...AbstractHttpReceivingTransportHandler.java | 2 +- .../AbstractHttpSendingTransportHandler.java | 3 +- .../AbstractHttpServerSockJsSession.java | 1 + .../EventSourceTransportHandler.java | 3 +- .../transport/HtmlFileTransportHandler.java | 2 +- .../JsonpPollingTransportHandler.java | 3 +- .../transport/JsonpTransportHandler.java | 2 +- .../StreamingServerSockJsSession.java | 3 +- .../sockjs/server/transport/package-info.java | 15 ++++++ .../websocket/BinaryMessage.java | 49 +++++++++++++++++-- .../websocket/CloseStatus.java | 42 +++++++++++----- .../websocket/HandlerProvider.java | 2 +- .../websocket/PartialMessageHandler.java | 2 +- .../websocket/TextMessage.java | 14 ++++-- .../websocket/WebSocketHandler.java | 2 - .../websocket/WebSocketHandlerAdapter.java | 1 + .../websocket/WebSocketMessage.java | 15 +++++- .../websocket/WebSocketSession.java | 1 - .../AbstractWebSocketConnectionManager.java | 4 +- .../websocket/client/WebSocketClient.java | 2 - .../WebSocketConnectFailureException.java | 4 +- .../client/WebSocketConnectionManager.java | 4 +- .../AnnotatedEndpointConnectionManager.java | 3 +- .../endpoint/EndpointConnectionManager.java | 3 +- .../EndpointConnectionManagerSupport.java | 3 +- .../endpoint/StandardWebSocketClient.java | 2 +- .../WebSocketContainerFactoryBean.java | 2 +- .../client/endpoint/package-info.java | 16 ++++++ .../websocket/client/package-info.java | 17 ++++++- .../endpoint/StandardWebSocketSession.java | 2 +- .../endpoint/WebSocketHandlerEndpoint.java | 1 + .../websocket/endpoint/package-info.java | 16 +++++- .../websocket/package-info.java | 16 +++++- .../server/DefaultHandshakeHandler.java | 2 +- .../websocket/server/HandshakeHandler.java | 1 - .../server/RequestUpgradeStrategy.java | 1 - .../server/endpoint/EndpointExporter.java | 1 + .../server/endpoint/EndpointRegistration.java | 1 + .../ServletServerContainerFactoryBean.java | 4 +- .../server/endpoint/SpringConfigurator.java | 1 - .../server/endpoint/package-info.java | 15 ++++++ .../websocket/server/package-info.java | 16 +++++- .../AbstractEndpointUpgradeStrategy.java | 1 - .../support/JettyRequestUpgradeStrategy.java | 1 + .../support/TomcatRequestUpgradeStrategy.java | 2 - .../support/WebSocketHttpRequestHandler.java | 2 - .../server/support/package-info.java | 19 ++++++- .../support/BeanCreatingHandlerProvider.java | 2 +- .../support/SimpleHandlerProvider.java | 3 +- 65 files changed, 321 insertions(+), 92 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index e584333759..9f3248b8e0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -37,7 +37,8 @@ import org.springframework.websocket.WebSocketSession; */ public abstract class AbstractSockJsSession implements WebSocketSession { - protected Log logger = LogFactory.getLog(this.getClass()); + protected final Log logger = LogFactory.getLog(getClass()); + private final String sessionId; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index 4f6051c1f8..d9be624791 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -20,15 +20,21 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; - /** * A factory for creating a SockJS session. * + * @param The type of session being created * @author Rossen Stoyanchev * @since 4.0 */ public interface SockJsSessionFactory{ + /** + * Create a new SockJS session. + * @param sessionId the ID of the session + * @param handler the underlying {@link WebSocketHandler} + * @return a new non-null session + */ S createSession(String sessionId, HandlerProvider handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java index 5b2f27cafe..f7f8da613c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java @@ -1,7 +1,21 @@ +/* + * 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. + */ /** * Common abstractions and Spring configuration support for the SockJS protocol. - * */ package org.springframework.sockjs; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 13ff66c27a..7f2e7bbeea 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -30,7 +30,6 @@ import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketMessage; - /** * Provides partial implementations of {@link SockJsSession} methods to send messages, * including heartbeat messages and to manage session state. diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 755c8ee4b6..8e8999a049 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -39,7 +39,6 @@ import org.springframework.util.StringUtils; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * Provides support for SockJS configuration options and serves the static SockJS URLs. * diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java index 799ba50d89..44d55ff0c5 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java @@ -16,9 +16,7 @@ package org.springframework.sockjs.server; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java index 9f89c4a120..63f82da257 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java @@ -18,7 +18,6 @@ package org.springframework.sockjs.server; import org.springframework.core.NestedRuntimeException; - /** * * @author Rossen Stoyanchev @@ -27,7 +26,6 @@ import org.springframework.core.NestedRuntimeException; @SuppressWarnings("serial") public class NestedSockJsRuntimeException extends NestedRuntimeException { - public NestedSockJsRuntimeException(String msg) { super(msg); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java index 1fa79db883..4cec5cff6f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** - * * @author Rossen Stoyanchev * @since 4.0 */ diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java index ded997e945..5552757580 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server; import java.nio.charset.Charset; @@ -21,10 +22,7 @@ import org.springframework.util.Assert; import com.fasterxml.jackson.core.io.JsonStringEncoder; - /** - * - * * @author Rossen Stoyanchev * @since 4.0 */ @@ -46,6 +44,7 @@ public class SockJsFrame { this.content = content; } + public static SockJsFrame openFrame() { return OPEN_FRAME; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 8df23d2134..46d357cc6a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -23,15 +23,12 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ public interface SockJsService { - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, HandlerProvider handler) throws IOException, TransportErrorException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 6364a1c83f..beac1aa1a3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server; import org.springframework.http.server.ServerHttpRequest; @@ -21,9 +22,7 @@ import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java index 04936f7d56..a71775534c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server; import java.util.Arrays; @@ -20,9 +21,7 @@ import java.util.List; import org.springframework.http.HttpMethod; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java index 8748c1b582..167717047a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java @@ -1,7 +1,21 @@ +/* + * 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. + */ /** * Server-side SockJS abstractions and base classes. - * */ package org.springframework.sockjs.server; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 925ba0305a..a0d61f79e2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.support; import java.io.IOException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index 4b16cacb0f..cb583622d2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -35,9 +35,7 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.support.SimpleHandlerProvider; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ @@ -89,6 +87,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { this.handlerProvider = handlerProvider; } + public String getPrefix() { return this.prefix; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java index b2030dead7..1d3b9d2052 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java @@ -1,3 +1,18 @@ +/* + * 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. + */ /** * Server-side SockJS classes including a diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 99a59a723f..0f1fd0d246 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; @@ -34,7 +35,6 @@ import org.springframework.websocket.WebSocketHandler; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; - /** * TODO * diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index e2aa777fb5..ac53c77f58 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; @@ -94,4 +95,4 @@ public abstract class AbstractHttpSendingTransportHandler protected abstract FrameFormat getFrameFormat(ServerHttpRequest request); -} \ No newline at end of file +} diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 7e6742a1e1..0492fee315 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index 700fd34706..8fb5a0313c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; @@ -27,7 +28,6 @@ import org.springframework.util.Assert; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * TODO * @@ -36,7 +36,6 @@ import org.springframework.websocket.WebSocketHandler; */ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.EVENT_SOURCE; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index ccd029d0c1..3075aade2b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; @@ -32,7 +33,6 @@ import org.springframework.web.util.JavaScriptUtils; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * TODO * diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index 24c754e89d..cc03645354 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.nio.charset.Charset; @@ -31,7 +32,6 @@ import org.springframework.web.util.JavaScriptUtils; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * TODO * @@ -40,7 +40,6 @@ import org.springframework.websocket.WebSocketHandler; */ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.JSONP; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java index 5cbd6288e2..b0d74454ef 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; @@ -27,7 +28,6 @@ import org.springframework.sockjs.server.TransportType; public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler { - @Override public TransportType getTransportType() { return TransportType.JSONP_SEND; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index 7e4a3c83fb..506985ba73 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.sockjs.server.transport; import java.io.IOException; @@ -26,7 +27,6 @@ import org.springframework.sockjs.server.TransportErrorException; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSession { private int byteCount; @@ -38,6 +38,7 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio super(sessionId, sockJsConfig, handler); } + @Override public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response, FrameFormat frameFormat) throws TransportErrorException { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java index b31cef1145..c35beb3138 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java @@ -1,3 +1,18 @@ +/* + * 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. + */ /** * Server-side support for SockJS transports including diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java index dbb12bcee2..8bc0e42007 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java @@ -23,10 +23,11 @@ import org.springframework.util.Assert; /** - * Represents a binary WebSocket message. + * A {@link WebSocketMessage} that contains a binary {@link ByteBuffer} payload. * * @author Rossen Stoyanchev * @since 4.0 + * @see WebSocketMessage */ public final class BinaryMessage extends WebSocketMessage { @@ -35,41 +36,80 @@ public final class BinaryMessage extends WebSocketMessage { private final boolean last; + /** + * Create a new {@link BinaryMessage} instance. + * @param payload a non-null payload + */ public BinaryMessage(ByteBuffer payload) { this(payload, true); } + /** + * Create a new {@link BinaryMessage} instance. + * @param payload a non-null payload + * @param isLast if the message is the last of a series of partial messages + */ public BinaryMessage(ByteBuffer payload, boolean isLast) { super(payload); this.bytes = null; this.last = isLast; } + /** + * Create a new {@link BinaryMessage} instance. + * @param payload a non-null payload + */ public BinaryMessage(byte[] payload) { this(payload, true); } + /** + * Create a new {@link BinaryMessage} instance. + * @param payload a non-null payload + * @param isLast if the message is the last of a series of partial messages + */ public BinaryMessage(byte[] payload, boolean isLast) { this(payload, 0, (payload == null ? 0 : payload.length), isLast); } + /** + * Create a new {@link BinaryMessage} instance by wrapping an existing byte array. + * @param payload a non-null payload, NOTE: this value is not copied so care must be + * taken not to modify the array. + * @param isLast if the message is the last of a series of partial messages + */ public BinaryMessage(byte[] payload, int offset, int len) { this(payload, offset, len, true); } + /** + * Create a new {@link BinaryMessage} instance by wrapping an existing byte array. + * @param payload a non-null payload, NOTE: this value is not copied so care must be + * taken not to modify the array. + * @param offset the offet into the array where the payload starts + * @param len the length of the array considered for the payload + * @param isLast if the message is the last of a series of partial messages + */ public BinaryMessage(byte[] payload, int offset, int len, boolean isLast) { super(payload != null ? ByteBuffer.wrap(payload, offset, len) : null); - if(payload != null && offset == 0 && len == payload.length) { - // FIXME better if a message always needs a payload? + if(offset == 0 && len == payload.length) { this.bytes = payload; } this.last = isLast; } + /** + * Returns if this is the last part in a series of partial messages. If this is + * not a partial message this method will return {@code true}. + */ public boolean isLast() { return this.last; } + /** + * Returns access to the message payload as a byte array. NOTE: the returned array + * should be considered read-only and should not be modified. + */ public byte[] getByteArray() { if(this.bytes == null && getPayload() != null) { this.bytes = getRemainingBytes(getPayload()); @@ -83,6 +123,9 @@ public final class BinaryMessage extends WebSocketMessage { return result; } + /** + * Returns access to the message payload as an {@link InputStream}. + */ public InputStream getInputStream() { byte[] array = getByteArray(); return (array != null) ? new ByteArrayInputStream(array) : null; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java index b0f751392d..dafcb988e7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java @@ -15,10 +15,10 @@ */ package org.springframework.websocket; +import org.eclipse.jetty.websocket.api.StatusCode; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; - /** * Represents a WebSocket close status code and reason. Status codes in the 1xxx range are * pre-defined by the protocol. Optionally, a status code may be sent with a reason. @@ -28,11 +28,10 @@ import org.springframework.util.ObjectUtils; * * @author Rossen Stoyanchev * @since 4.0 - * */ public final class CloseStatus { - /** + /** * "1000 indicates a normal closure, meaning that the purpose for which the connection * was established has been fulfilled." */ @@ -44,13 +43,13 @@ public final class CloseStatus { */ public static final CloseStatus GOING_AWAY = new CloseStatus(1001); - /** + /** * "1002 indicates that an endpoint is terminating the connection due to a protocol * error." */ public static final CloseStatus PROTOCOL_ERROR = new CloseStatus(1002); - /** + /** * "1003 indicates that an endpoint is terminating the connection because it has * received a type of data it cannot accept (e.g., an endpoint that understands only * text data MAY send this if it receives a binary message)." @@ -82,7 +81,7 @@ public final class CloseStatus { */ public static final CloseStatus BAD_DATA = new CloseStatus(1007); - /** + /** * "1008 indicates that an endpoint is terminating the connection because it has * received a message that violates its policy. This is a generic status code that can * be returned when there is no other more suitable status code (e.g., 1003 or 1009) @@ -90,13 +89,13 @@ public final class CloseStatus { */ public static final CloseStatus POLICY_VIOLATION = new CloseStatus(1008); - /** + /** * "1009 indicates that an endpoint is terminating the connection because it has * received a message that is too big for it to process." */ public static final CloseStatus TOO_BIG_TO_PROCESS = new CloseStatus(1009); - /** + /** * "1010 indicates that an endpoint (client) is terminating the connection because it * has expected the server to negotiate one or more extension, but the server didn't * return them in the response message of the WebSocket handshake. The list of @@ -106,7 +105,7 @@ public final class CloseStatus { */ public static final CloseStatus REQUIRED_EXTENSION = new CloseStatus(1010); - /** + /** * "1011 indicates that a server is terminating the connection because it encountered * an unexpected condition that prevented it from fulfilling the request." */ @@ -125,7 +124,7 @@ public final class CloseStatus { */ public static final CloseStatus SERVICE_OVERLOAD = new CloseStatus(1013); - /** + /** * "1015 is a reserved value and MUST NOT be set as a status code in a Close control * frame by an endpoint. It is designated for use in applications expecting a status * code to indicate that the connection was closed due to a failure to perform a TLS @@ -134,32 +133,51 @@ public final class CloseStatus { public static final CloseStatus TLS_HANDSHAKE_FAILURE = new CloseStatus(1015); - private final int code; private final String reason; + /** + * Create a new {@link CloseStatus} instance. + * @param code the status code + */ public CloseStatus(int code) { this(code, null); } + /** + * Create a new {@link CloseStatus} instance. + * @param code + * @param reason + */ public CloseStatus(int code, String reason) { Assert.isTrue((code >= 1000 && code < 5000), "Invalid code"); this.code = code; this.reason = reason; } + /** + * Returns the status code. + */ public int getCode() { return this.code; } + /** + * Returns the reason or {@code null}. + */ public String getReason() { return this.reason; } + /** + * Crate a new {@link CloseStatus} from this one with the specified reason. + * @param reason the reason + * @return a new {@link StatusCode} instance + */ public CloseStatus withReason(String reason) { - Assert.hasText(reason, "Expected non-empty reason"); + Assert.hasText(reason, "Reason must not be empty"); return new CloseStatus(this.code, reason); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java index 3c8dc2c455..5158a669a3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.websocket; /** * A strategy for obtaining a handler instance that is scoped to external lifecycle events diff --git a/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java index 7752baf6cc..30524be5fa 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.websocket; /** * A "marker" interface for {@link BinaryMessageHandler} types that wish to handle partial diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java index 61f3916547..df78a6ef63 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java @@ -13,23 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket; import java.io.Reader; import java.io.StringReader; /** - * Represents a text WebSocket message. + * A {@link WebSocketMessage} that contains a textual {@link String} payload. * * @author Rossen Stoyanchev * @since 4.0 */ public final class TextMessage extends WebSocketMessage { - public TextMessage(String payload) { - super(payload); + /** + * Create a new {@link TextMessage} instance. + * @param payload the payload + */ + public TextMessage(CharSequence payload) { + super(payload.toString()); } + /** + * Returns access to the message payload as a {@link Reader}. + */ public Reader getReader() { return new StringReader(getPayload()); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index b835c383af..a16ab765bf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -16,8 +16,6 @@ package org.springframework.websocket; - - /** * A handler for WebSocket sessions. * diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java index 95cd36af56..b42f7000f0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java @@ -22,6 +22,7 @@ package org.springframework.websocket; * * @author Rossen Stoyanchev * @since 4.0 + * @see WebSocketHandler */ public class WebSocketHandlerAdapter implements WebSocketHandler { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java index 6ed55410ad..61d4f11b7c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java @@ -13,27 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; - /** + * A message that can be sent or received over a WebSocket connection. A WebSocket + * message must be either a {@link BinaryMessage} or a {@link TextMessage} depending + * on the payload. No further subclasses are supported. * * @author Rossen Stoyanchev * @since 4.0 + * @see BinaryMessage + * @see TextMessage */ public abstract class WebSocketMessage { private final T payload; + /** + * Create a new {@link WebSocketMessage} instance. + * @param payload a non-null payload + */ WebSocketMessage(T payload) { Assert.notNull(payload, "Payload must not be null"); this.payload = payload; } + + /** + * Returns the message payload. This will never be {@code null}. + */ public T getPayload() { return this.payload; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index 483c6f72e8..e1ef76b61d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -19,7 +19,6 @@ package org.springframework.websocket; import java.io.IOException; import java.net.URI; - /** * Allows sending messages over a WebSocket connection as well as closing it. * diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java index 7839c7c2a3..ab696d9c8f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.client; import java.net.URI; @@ -24,8 +25,8 @@ import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.web.util.UriComponentsBuilder; - /** + * Abstract base class for WebSocketConnection managers. * * @author Rossen Stoyanchev * @since 4.0 @@ -34,6 +35,7 @@ public abstract class AbstractWebSocketConnectionManager implements SmartLifecyc protected final Log logger = LogFactory.getLog(getClass()); + private final URI uri; private boolean autoStartup = false; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java index 169170da8d..18260dfd9b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -22,7 +22,6 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; - /** * Contract for starting a WebSocket handshake request. * @@ -36,7 +35,6 @@ import org.springframework.websocket.WebSocketSession; */ public interface WebSocketClient { - WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java index 553423cb92..a6f904b1ef 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java @@ -13,20 +13,18 @@ * 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); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index 05eddadd77..5b84998caf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.client; import java.util.ArrayList; @@ -25,9 +26,7 @@ import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.support.SimpleHandlerProvider; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ @@ -58,6 +57,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag this.handlerProvider = handlerProvider; } + public void setSubProtocols(List subProtocols) { this.subProtocols.clear(); if (!CollectionUtils.isEmpty(subProtocols)) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java index 9bb7fe1d2a..ce03fb798a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java @@ -25,9 +25,7 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.support.BeanCreatingHandlerProvider; import org.springframework.websocket.support.SimpleHandlerProvider; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ @@ -47,6 +45,7 @@ public class AnnotatedEndpointConnectionManager extends EndpointConnectionManage this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); } + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (this.handlerProvider instanceof BeanFactoryAware) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java index 1307f55d9e..44162dacc5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java @@ -35,9 +35,7 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.support.BeanCreatingHandlerProvider; import org.springframework.websocket.support.SimpleHandlerProvider; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ @@ -60,6 +58,7 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); } + public void setSubProtocols(String... subprotocols) { this.configBuilder.preferredSubprotocols(Arrays.asList(subprotocols)); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java index ba4fdbcdfb..dfdee7c966 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java @@ -22,9 +22,7 @@ import javax.websocket.WebSocketContainer; import org.springframework.websocket.client.AbstractWebSocketConnectionManager; - /** - * * @author Rossen Stoyanchev * @since 4.0 */ @@ -39,6 +37,7 @@ public abstract class EndpointConnectionManagerSupport extends AbstractWebSocket super(uriTemplate, uriVariables); } + public void setWebSocketContainer(WebSocketContainer webSocketContainer) { this.webSocketContainer = webSocketContainer; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index 97655d29e9..25519dd2ea 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.client.endpoint; import java.net.URI; @@ -40,7 +41,6 @@ import org.springframework.websocket.endpoint.StandardWebSocketSession; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.support.SimpleHandlerProvider; - /** * A standard Java {@link WebSocketClient}. * diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java index a6d9d53ebf..944987eb2e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.client.endpoint; import javax.websocket.ContainerProvider; @@ -20,7 +21,6 @@ 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 diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java index 777e2ae7da..af6c74b7d2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java @@ -1,3 +1,19 @@ +/* + * 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. + */ + /** * Client-side classes for use with standard Java WebSocket endpoints including * {@link org.springframework.websocket.client.endpoint.EndpointConnectionManager} and diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java index 817c86c58d..e7860b5cea 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java @@ -1,6 +1,21 @@ +/* + * 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. + */ + /** * Server-side abstractions for WebSocket applications. - * */ package org.springframework.websocket.client; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java index 0da50203ee..e59ee85942 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java @@ -32,7 +32,6 @@ import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; - /** * A standard Java implementation of {@link WebSocketSession} that delegates to * {@link javax.websocket.Session}. @@ -52,6 +51,7 @@ public class StandardWebSocketSession implements WebSocketSession { this.session = session; } + @Override public String getId() { return this.session.getId(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java index 4d02cabd6b..528ac34ff1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java @@ -59,6 +59,7 @@ public class WebSocketHandlerEndpoint extends Endpoint { this.handlerProvider = handlerProvider; } + @Override public void onOpen(final javax.websocket.Session session, EndpointConfig config) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java index 99723de31d..64cfefd85e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java @@ -1,8 +1,22 @@ +/* + * 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. + */ /** * Classes for use with the standard Java WebSocket endpoints from both client and * server code. - * */ package org.springframework.websocket.endpoint; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/package-info.java index 8bf3b32aa5..c1f27cbcb7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/package-info.java @@ -1,7 +1,21 @@ +/* + * 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. + */ /** * Common abstractions and Spring configuration support for WebSocket applications. - * */ package org.springframework.websocket; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 79463681ea..c37ffef466 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -38,7 +38,6 @@ import org.springframework.util.StringUtils; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * TODO *

@@ -78,6 +77,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { this.requestUpgradeStrategy = upgradeStrategy; } + public void setSupportedProtocols(String... protocols) { this.supportedProtocols = Arrays.asList(protocols); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 1989f61ad0..227b6fefd4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -23,7 +23,6 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * Contract for processing a WebSocket handshake request. * diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index 359cf9dc47..9af17b4fff 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -23,7 +23,6 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; - /** * A strategy for performing container-specific steps to upgrade an HTTP request during a * WebSocket handshake. Intended for use within {@link HandshakeHandler} implementations. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java index 1d09241846..aa9d3829ee 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.server.endpoint; import java.lang.reflect.Method; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index c6aadd3028..61546fc252 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -94,6 +94,7 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw this.handlerProvider = new SimpleHandlerProvider(endpointBean); } + @Override public String getPath() { return this.path; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java index 427ca9fcbd..1d7c64c0e7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.server.endpoint; import javax.servlet.ServletContext; @@ -26,7 +27,6 @@ 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 @@ -46,6 +46,7 @@ public class ServletServerContainerFactoryBean private static final String SERVER_CONTAINER_ATTR_NAME = "javax.websocket.server.ServerContainer"; + private Long asyncSendTimeout; private Long maxSessionIdleTimeout; @@ -54,7 +55,6 @@ public class ServletServerContainerFactoryBean private Integer maxBinaryMessageBufferSize; - private ServerContainer serverContainer; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java index 1129cf6e66..e553f247cf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java @@ -26,7 +26,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; - /** * This should be used in conjuction with {@link ServerEndpoint @ServerEndpoint} classes. * diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java index 34a043af9c..e52ce874a1 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java @@ -1,3 +1,18 @@ +/* + * 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. + */ /** * Server classes for use with standard Java WebSocket endpoints including diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java index 8a44c0bc88..e70c9d1436 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java @@ -1,7 +1,21 @@ +/* + * 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. + */ /** * Server-side abstractions for WebSocket applications. - * */ package org.springframework.websocket.server; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index ef53d8e237..ec114934b5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -29,7 +29,6 @@ import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.server.RequestUpgradeStrategy; - /** * A {@link RequestUpgradeStrategy} that supports WebSocket handlers of type * {@link WebSocketHandler} as well as {@link javax.websocket.Endpoint}. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 22c186637a..2713266ff4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -92,6 +92,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { } } + @Override public String[] getSupportedVersions() { return new String[] { String.valueOf(HandshakeRFC6455.VERSION) }; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java index d90156b97f..0dcced9a12 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java @@ -34,7 +34,6 @@ import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.websocket.server.endpoint.EndpointRegistration; - /** * Tomcat support for upgrading an {@link HttpServletRequest} during a WebSocket handshake. * @@ -43,7 +42,6 @@ import org.springframework.websocket.server.endpoint.EndpointRegistration; */ public class TomcatRequestUpgradeStrategy extends AbstractEndpointUpgradeStrategy { - @Override public String[] getSupportedVersions() { return new String[] { "13" }; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 343b065f55..c5e622726d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -35,7 +35,6 @@ import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; import org.springframework.websocket.support.SimpleHandlerProvider; - /** * An {@link HttpRequestHandler} that wraps the invocation of a {@link HandshakeHandler}. * @@ -59,7 +58,6 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { public WebSocketHttpRequestHandler( HandlerProvider handlerProvider, HandshakeHandler handshakeHandler) { - Assert.notNull(handlerProvider, "handlerProvider is required"); Assert.notNull(handshakeHandler, "handshakeHandler is required"); this.handlerProvider = handlerProvider; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java index ac054317dd..8b29c9295c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java @@ -1,7 +1,22 @@ +/* + * 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. + */ /** - * Server-side support classes including container-specific strategies for upgrading a request. - * + * Server-side support classes including container-specific strategies for upgrading a + * request. */ package org.springframework.websocket.server.support; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java index 1c6567dc9a..858b440d2d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java @@ -26,7 +26,6 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.util.Assert; import org.springframework.websocket.HandlerProvider; - /** * A {@link HandlerProvider} that uses {@link AutowireCapableBeanFactory#createBean(Class) * creating a fresh instance every time #getHandler() is called. @@ -48,6 +47,7 @@ public class BeanCreatingHandlerProvider implements HandlerProvider, BeanF this.handlerClass = handlerClass; } + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof AutowireCapableBeanFactory) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java index c880a64802..aa9598dc2e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.websocket.support; import org.springframework.util.ClassUtils; import org.springframework.websocket.HandlerProvider; - /** * A {@link HandlerProvider} that returns a singleton instance. * @@ -34,6 +34,7 @@ public class SimpleHandlerProvider implements HandlerProvider { this.handler = handler; } + @Override public boolean isSingleton() { return true; From 5f22cf053279b4a692eac95b5fbfb9701110cd4e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 25 Apr 2013 23:12:21 -0400 Subject: [PATCH 38/51] Add WebSocketHandlerInvoker class --- .../sockjs/AbstractSockJsSession.java | 76 +----- .../websocket/WebSocketHandler.java | 10 +- .../JettyWebSocketListenerAdapter.java | 80 ++++++ .../adapter/JettyWebSocketSessionAdapter.java | 98 +++++++ .../adapter/StandardEndpointAdapter.java | 114 ++++++++ .../StandardWebSocketSessionAdapter.java} | 8 +- .../adapter/WebSocketHandlerInvoker.java | 161 ++++++++++++ .../{endpoint => adapter}/package-info.java | 6 +- .../endpoint/StandardWebSocketClient.java | 8 +- .../endpoint/WebSocketHandlerEndpoint.java | 188 -------------- .../server/endpoint/EndpointRegistration.java | 5 - .../AbstractEndpointUpgradeStrategy.java | 4 +- .../support/JettyRequestUpgradeStrategy.java | 244 ++---------------- 13 files changed, 502 insertions(+), 500 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java rename spring-websocket/src/main/java/org/springframework/websocket/{endpoint/StandardWebSocketSession.java => adapter/StandardWebSocketSessionAdapter.java} (92%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java rename spring-websocket/src/main/java/org/springframework/websocket/{endpoint => adapter}/package-info.java (76%) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 9f3248b8e0..9b1e07c99f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -27,6 +27,7 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.adapter.WebSocketHandlerInvoker; /** @@ -42,9 +43,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { private final String sessionId; - private final HandlerProvider handlerProvider; - - private WebSocketHandler handler; + private WebSocketHandlerInvoker handler; private State state = State.NEW; @@ -62,7 +61,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { Assert.notNull(sessionId, "sessionId is required"); Assert.notNull(handlerProvider, "handlerProvider is required"); this.sessionId = sessionId; - this.handlerProvider = handlerProvider; + this.handler = new WebSocketHandlerInvoker(handlerProvider).setLogger(logger); } public String getId() { @@ -124,42 +123,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { public void delegateConnectionEstablished() { this.state = State.OPEN; - this.handler = handlerProvider.getHandler(); - try { - this.handler.afterConnectionEstablished(this); - } - catch (Throwable ex) { - tryCloseWithError(ex, null); - } - } - - /** - * Close due to unhandled runtime error from WebSocketHandler. - * @param closeStatus TODO - */ - private void tryCloseWithError(Throwable ex, CloseStatus closeStatus) { - logger.error("Unhandled error for " + this, ex); - try { - closeStatus = (closeStatus != null) ? closeStatus : CloseStatus.SERVER_ERROR; - close(closeStatus); - } - catch (Throwable t) { - destroyHandler(); - } - } - - private void destroyHandler() { - try { - if (this.handler != null) { - this.handlerProvider.destroy(this.handler); - } - } - catch (Throwable t) { - logger.warn("Error while destroying handler", t); - } - finally { - this.handler = null; - } + this.handler.afterConnectionEstablished(this); } /** @@ -167,27 +131,17 @@ public abstract class AbstractSockJsSession implements WebSocketSession { */ protected void tryCloseWithSockJsTransportError(Throwable ex, CloseStatus closeStatus) { delegateError(ex); - tryCloseWithError(ex, closeStatus); + this.handler.tryCloseWithError(this, ex, closeStatus); } public void delegateMessages(String[] messages) { - try { - for (String message : messages) { - this.handler.handleTextMessage(new TextMessage(message), this); - } - } - catch (Throwable ex) { - tryCloseWithError(ex, null); + for (String message : messages) { + this.handler.handleTextMessage(new TextMessage(message), this); } } public void delegateError(Throwable ex) { - try { - this.handler.handleTransportError(ex, this); - } - catch (Throwable t) { - tryCloseWithError(t, null); - } + this.handler.handleTransportError(ex, this); } /** @@ -206,12 +160,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - try { - this.handler.afterConnectionClosed(status, this); - } - finally { - destroyHandler(); - } + this.handler.afterConnectionClosed(status, this); } } } @@ -241,12 +190,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - try { - this.handler.afterConnectionClosed(status, this); - } - finally { - destroyHandler(); - } + this.handler.afterConnectionClosed(status, this); } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index a16ab765bf..5984d49ae0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -29,11 +29,6 @@ public interface WebSocketHandler { */ void afterConnectionEstablished(WebSocketSession session); - /** - * A WebSocket connection has been closed. - */ - void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session); - /** * Handle an incoming text message. */ @@ -49,4 +44,9 @@ public interface WebSocketHandler { */ void handleTransportError(Throwable exception, WebSocketSession session); + /** + * A WebSocket connection has been closed. + */ + void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session); + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java new file mode 100644 index 0000000000..2ec1ec6f56 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -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.websocket.adapter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.springframework.util.Assert; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; + +/** + * Adapts Spring's {@link WebSocketHandler} to Jetty's {@link WebSocketListener}. + * + * @author Phillip Webb + * @since 4.0 + */ +public class JettyWebSocketListenerAdapter implements WebSocketListener { + + private static Log logger = LogFactory.getLog(JettyWebSocketListenerAdapter.class); + + private final WebSocketHandler handler; + + private WebSocketSession wsSession; + + + public JettyWebSocketListenerAdapter(HandlerProvider provider) { + Assert.notNull(provider, "provider is required"); + this.handler = new WebSocketHandlerInvoker(provider).setLogger(logger); + } + + + @Override + public void onWebSocketConnect(Session session) { + this.wsSession = new JettyWebSocketSessionAdapter(session); + this.handler.afterConnectionEstablished(this.wsSession); + } + + @Override + public void onWebSocketClose(int statusCode, String reason) { + CloseStatus closeStatus = new CloseStatus(statusCode, reason); + this.handler.afterConnectionClosed(closeStatus, this.wsSession); + } + + @Override + public void onWebSocketText(String payload) { + TextMessage message = new TextMessage(payload); + this.handler.handleTextMessage(message, this.wsSession); + } + + @Override + public void onWebSocketBinary(byte[] payload, int offset, int len) { + BinaryMessage message = new BinaryMessage(payload, offset, len); + this.handler.handleBinaryMessage(message, this.wsSession); + } + + @Override + public void onWebSocketError(Throwable cause) { + this.handler.handleTransportError(cause, this.wsSession); + } +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java new file mode 100644 index 0000000000..ef23f92427 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java @@ -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.websocket.adapter; + +import java.io.IOException; +import java.net.URI; + +import org.eclipse.jetty.websocket.api.Session; +import org.springframework.util.ObjectUtils; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + + +/** + * Adapts Jetty's {@link Session} to Spring's {@link WebSocketSession}. + * + * @author Phillip Webb + * @since 4.0 + */ +public class JettyWebSocketSessionAdapter implements WebSocketSession { + + private Session session; + + + public JettyWebSocketSessionAdapter(Session session) { + this.session = session; + } + + + @Override + public String getId() { + return ObjectUtils.getIdentityHexString(this.session); + } + + @Override + public boolean isOpen() { + return this.session.isOpen(); + } + + @Override + public boolean isSecure() { + return this.session.isSecure(); + } + + @Override + public URI getURI() { + return this.session.getUpgradeRequest().getRequestURI(); + } + + @Override + public void sendMessage(WebSocketMessage message) throws IOException { + if (message instanceof BinaryMessage) { + sendMessage((BinaryMessage) message); + } + else if (message instanceof TextMessage) { + sendMessage((TextMessage) message); + } + else { + throw new IllegalArgumentException("Unsupported message type"); + } + } + + private void sendMessage(BinaryMessage message) throws IOException { + this.session.getRemote().sendBytes(message.getPayload()); + } + + private void sendMessage(TextMessage message) throws IOException { + this.session.getRemote().sendString(message.getPayload()); + } + + @Override + public void close() throws IOException { + this.session.close(); + } + + @Override + public void close(CloseStatus status) throws IOException { + this.session.close(status.getCode(), status.getReason()); + } + +} \ No newline at end of file diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java new file mode 100644 index 0000000000..3ce78ab01f --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -0,0 +1,114 @@ +/* + * 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.adapter; + +import java.nio.ByteBuffer; + +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.PartialMessageHandler; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; + + +/** + * An {@link Endpoint} that delegates to a {@link WebSocketHandler}. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class StandardEndpointAdapter extends Endpoint { + + private static Log logger = LogFactory.getLog(StandardEndpointAdapter.class); + + private final WebSocketHandler handler; + + private final Class handlerClass; + + private WebSocketSession wsSession; + + + + public StandardEndpointAdapter(HandlerProvider provider) { + Assert.notNull(provider, "provider is required"); + this.handler = new WebSocketHandlerInvoker(provider).setLogger(logger); + this.handlerClass= provider.getHandlerType(); + } + + + @Override + public void onOpen(final javax.websocket.Session session, EndpointConfig config) { + + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + handleTextMessage(session, message); + } + }); + if (PartialMessageHandler.class.isAssignableFrom(this.handlerClass)) { + session.addMessageHandler(new MessageHandler.Partial() { + @Override + public void onMessage(ByteBuffer messagePart, boolean isLast) { + handleBinaryMessage(session, messagePart, isLast); + } + }); + } + else { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(ByteBuffer message) { + handleBinaryMessage(session, message, true); + } + }); + } + + this.wsSession = new StandardWebSocketSessionAdapter(session); + this.handler.afterConnectionEstablished(this.wsSession); + } + + private void handleTextMessage(javax.websocket.Session session, String payload) { + TextMessage message = new TextMessage(payload); + this.handler.handleTextMessage(message, this.wsSession); + } + + private void handleBinaryMessage(javax.websocket.Session session, ByteBuffer payload, boolean isLast) { + BinaryMessage message = new BinaryMessage(payload, isLast); + this.handler.handleBinaryMessage(message, this.wsSession); + } + + @Override + public void onClose(javax.websocket.Session session, CloseReason reason) { + CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); + this.handler.afterConnectionClosed(closeStatus, this.wsSession); + } + + @Override + public void onError(javax.websocket.Session session, Throwable exception) { + this.handler.handleTransportError(exception, this.wsSession); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java similarity index 92% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java rename to spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java index e59ee85942..30bc063baf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/StandardWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.endpoint; +package org.springframework.websocket.adapter; import java.io.IOException; import java.net.URI; @@ -39,14 +39,14 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class StandardWebSocketSession implements WebSocketSession { +public class StandardWebSocketSessionAdapter implements WebSocketSession { - private static Log logger = LogFactory.getLog(StandardWebSocketSession.class); + private static Log logger = LogFactory.getLog(StandardWebSocketSessionAdapter.class); private final javax.websocket.Session session; - public StandardWebSocketSession(javax.websocket.Session session) { + public StandardWebSocketSessionAdapter(javax.websocket.Session session) { Assert.notNull(session, "session is required"); this.session = session; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java new file mode 100644 index 0000000000..45abc18a52 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java @@ -0,0 +1,161 @@ +/* + * 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.adapter; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.HandlerProvider; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; + +/** + * A class for managing and delegating to a {@link WebSocketHandler} instance, applying + * initialization and destruction as necessary at the start and end of the WebSocket + * session, ensuring that any unhandled exceptions from its methods are caught and handled + * by closing the session, and also adding uniform logging. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class WebSocketHandlerInvoker implements WebSocketHandler { + + private Log logger = LogFactory.getLog(WebSocketHandlerInvoker.class); + + private final HandlerProvider handlerProvider; + + private WebSocketHandler handler; + + private final AtomicInteger sessionCount = new AtomicInteger(0); + + + public WebSocketHandlerInvoker(HandlerProvider handlerProvider) { + this.handlerProvider = handlerProvider; + } + + public WebSocketHandlerInvoker setLogger(Log logger) { + this.logger = logger; + return this; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + if (logger.isDebugEnabled()) { + logger.debug("Connection established, " + session + ", uri=" + session.getURI()); + } + try { + Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected new session"); + + this.handler = this.handlerProvider.getHandler(); + this.handler.afterConnectionEstablished(session); + } + catch (Throwable ex) { + tryCloseWithError(session, ex); + } + } + + public void tryCloseWithError(WebSocketSession session, Throwable ex) { + tryCloseWithError(session, ex, null); + } + + public void tryCloseWithError(WebSocketSession session, Throwable ex, CloseStatus status) { + logger.error("Unhandled error for " + session, ex); + if (session.isOpen()) { + try { + session.close(CloseStatus.SERVER_ERROR); + } + catch (Throwable t) { + destroyHandler(); + } + } + } + + private void destroyHandler() { + try { + if (this.handler != null) { + this.handlerProvider.destroy(this.handler); + } + } + catch (Throwable t) { + logger.warn("Error while destroying handler", t); + } + finally { + this.handler = null; + } + } + + @Override + public void handleTextMessage(TextMessage message, WebSocketSession session) { + if (logger.isTraceEnabled()) { + logger.trace("Received text message for " + session + ": " + message); + } + try { + this.handler.handleTextMessage(message, session); + } + catch (Throwable ex) { + tryCloseWithError(session,ex); + } + } + + @Override + public void handleBinaryMessage(BinaryMessage message, WebSocketSession session) { + if (logger.isTraceEnabled()) { + logger.trace("Received binary message for " + session); + } + try { + this.handler.handleBinaryMessage(message, session); + } + catch (Throwable ex) { + tryCloseWithError(session, ex); + } + } + + @Override + public void handleTransportError(Throwable exception, WebSocketSession session) { + if (logger.isDebugEnabled()) { + logger.debug("Transport error for " + session, exception); + } + try { + this.handler.handleTransportError(exception, session); + } + catch (Throwable ex) { + tryCloseWithError(session, ex); + } + } + + @Override + public void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session) { + if (logger.isDebugEnabled()) { + logger.debug("Connection closed for " + session + ", " + closeStatus); + } + try { + this.handler.afterConnectionClosed(closeStatus, session); + } + catch (Throwable ex) { + logger.error("Unhandled error for " + this, ex); + } + finally { + this.handlerProvider.destroy(this.handler); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java similarity index 76% rename from spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java rename to spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java index 64cfefd85e..695869a5ff 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java @@ -15,8 +15,8 @@ */ /** - * Classes for use with the standard Java WebSocket endpoints from both client and - * server code. + * Adapters for the {@link org.springframework.websocket.WebSocketHandler} and + * {@link org.springframework.websocket.WebSocketSession} contracts. */ -package org.springframework.websocket.endpoint; +package org.springframework.websocket.adapter; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index 25519dd2ea..ca0e103efb 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -35,10 +35,10 @@ import org.springframework.web.util.UriComponentsBuilder; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter; +import org.springframework.websocket.adapter.StandardEndpointAdapter; import org.springframework.websocket.client.WebSocketClient; import org.springframework.websocket.client.WebSocketConnectFailureException; -import org.springframework.websocket.endpoint.StandardWebSocketSession; -import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -78,7 +78,7 @@ public class StandardWebSocketClient implements WebSocketClient { public WebSocketSession doHandshake(HandlerProvider handler, final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { - Endpoint endpoint = new WebSocketHandlerEndpoint(handler); + Endpoint endpoint = new StandardEndpointAdapter(handler); ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create(); if (httpHeaders != null) { @@ -100,7 +100,7 @@ public class StandardWebSocketClient implements WebSocketClient { try { Session session = this.webSocketContainer.connectToServer(endpoint, configBuidler.build(), uri); - return new StandardWebSocketSession(session); + return new StandardWebSocketSessionAdapter(session); } catch (Exception e) { throw new WebSocketConnectFailureException("Failed to connect to " + uri, e); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java b/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java deleted file mode 100644 index 528ac34ff1..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/endpoint/WebSocketHandlerEndpoint.java +++ /dev/null @@ -1,188 +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.endpoint; - -import java.util.concurrent.atomic.AtomicInteger; - -import javax.websocket.CloseReason; -import javax.websocket.Endpoint; -import javax.websocket.EndpointConfig; -import javax.websocket.MessageHandler; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.util.Assert; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; -import org.springframework.websocket.PartialMessageHandler; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; - - -/** - * An {@link Endpoint} that delegates to a {@link WebSocketHandler}. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class WebSocketHandlerEndpoint extends Endpoint { - - private static Log logger = LogFactory.getLog(WebSocketHandlerEndpoint.class); - - private final HandlerProvider handlerProvider; - - private WebSocketHandler handler; - - private WebSocketSession webSocketSession; - - private final AtomicInteger sessionCount = new AtomicInteger(0); - - - public WebSocketHandlerEndpoint(HandlerProvider handlerProvider) { - Assert.notNull(handlerProvider, "handlerProvider is required"); - this.handlerProvider = handlerProvider; - } - - - @Override - public void onOpen(final javax.websocket.Session session, EndpointConfig config) { - - Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); - - if (logger.isDebugEnabled()) { - logger.debug("Connection established, javax.websocket.Session id=" - + session.getId() + ", uri=" + session.getRequestURI()); - } - - this.webSocketSession = new StandardWebSocketSession(session); - this.handler = handlerProvider.getHandler(); - - session.addMessageHandler(new MessageHandler.Whole() { - @Override - public void onMessage(String message) { - handleTextMessage(session, message); - } - }); - if (this.handler instanceof PartialMessageHandler) { - session.addMessageHandler(new MessageHandler.Partial() { - @Override - public void onMessage(byte[] messagePart, boolean isLast) { - handleBinaryMessage(session, messagePart, isLast); - } - }); - } - else { - session.addMessageHandler(new MessageHandler.Whole() { - @Override - public void onMessage(byte[] message) { - handleBinaryMessage(session, message, true); - } - }); - } - - try { - this.handler.afterConnectionEstablished(this.webSocketSession); - } - catch (Throwable ex) { - tryCloseWithError(ex); - } - } - - private void tryCloseWithError(Throwable ex) { - logger.error("Unhandled error for " + this.webSocketSession, ex); - if (this.webSocketSession.isOpen()) { - try { - this.webSocketSession.close(CloseStatus.SERVER_ERROR); - } - catch (Throwable t) { - destroyHandler(); - } - } - } - - private void destroyHandler() { - try { - if (this.handler != null) { - this.handlerProvider.destroy(this.handler); - } - } - catch (Throwable t) { - logger.warn("Error while destroying handler", t); - } - finally { - this.webSocketSession = null; - this.handler = null; - } - } - - private void handleTextMessage(javax.websocket.Session session, String message) { - if (logger.isTraceEnabled()) { - logger.trace("Received message for WebSocket session id=" + session.getId() + ": " + message); - } - try { - TextMessage textMessage = new TextMessage(message); - this.handler.handleTextMessage(textMessage, this.webSocketSession); - } - catch (Throwable ex) { - tryCloseWithError(ex); - } - } - - private void handleBinaryMessage(javax.websocket.Session session, byte[] message, boolean isLast) { - if (logger.isTraceEnabled()) { - logger.trace("Received binary data for WebSocket session id=" + session.getId()); - } - try { - BinaryMessage binaryMessage = new BinaryMessage(message, isLast); - this.handler.handleBinaryMessage(binaryMessage, this.webSocketSession); - } - catch (Throwable ex) { - tryCloseWithError(ex); - } - } - - @Override - public void onClose(javax.websocket.Session session, CloseReason reason) { - if (logger.isDebugEnabled()) { - logger.debug("Connection closed, WebSocket session id=" + session.getId() + ", " + reason); - } - try { - CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); - this.handler.afterConnectionClosed(closeStatus, this.webSocketSession); - } - catch (Throwable ex) { - logger.error("Unhandled error for " + this.webSocketSession, ex); - } - finally { - this.handlerProvider.destroy(this.handler); - } - } - - @Override - public void onError(javax.websocket.Session session, Throwable exception) { - logger.error("Error for WebSocket session id=" + session.getId(), exception); - try { - this.handler.handleTransportError(exception, this.webSocketSession); - } - catch (Throwable ex) { - tryCloseWithError(ex); - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index 61546fc252..ebde513034 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -29,14 +29,11 @@ import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; 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.util.Assert; import org.springframework.websocket.HandlerProvider; -import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; import org.springframework.websocket.support.BeanCreatingHandlerProvider; import org.springframework.websocket.support.SimpleHandlerProvider; @@ -44,8 +41,6 @@ import org.springframework.websocket.support.SimpleHandlerProvider; /** * An implementation of {@link javax.websocket.server.ServerEndpointConfig} that also * holds the target {@link javax.websocket.Endpoint} as a reference or a bean name. - * The target can also be {@link org.springframework.websocket.WebSocketHandler}, in - * which case it will be adapted via {@link WebSocketHandlerEndpoint}. * *

* Beans of this type are detected by {@link EndpointExporter} and diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index ec114934b5..e27ca8b66e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -26,7 +26,7 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.endpoint.WebSocketHandlerEndpoint; +import org.springframework.websocket.adapter.StandardEndpointAdapter; import org.springframework.websocket.server.RequestUpgradeStrategy; /** @@ -49,7 +49,7 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS } protected Endpoint adaptWebSocketHandler(HandlerProvider handler) { - return new WebSocketHandlerEndpoint(handler); + return new StandardEndpointAdapter(handler); } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 2713266ff4..95e54b34e4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -17,18 +17,12 @@ package org.springframework.websocket.server.support; import java.io.IOException; -import java.net.URI; -import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; -import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.server.HandshakeRFC6455; import org.eclipse.jetty.websocket.server.ServletWebSocketRequest; import org.eclipse.jetty.websocket.server.WebSocketServerFactory; @@ -38,14 +32,9 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; -import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter; import org.springframework.websocket.server.RequestUpgradeStrategy; /** @@ -53,11 +42,10 @@ import org.springframework.websocket.server.RequestUpgradeStrategy; * {@code org.eclipse.jetty.websocket.server.WebSocketHandler} class. * * @author Phillip Webb + * @since 4.0 */ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { - private static Log logger = LogFactory.getLog(JettyRequestUpgradeStrategy.class); - // FIXME jetty has options, timeouts etc. Do we need a common abstraction // FIXME need a way for someone to plug their own RequestUpgradeStrategy or override @@ -65,7 +53,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { // FIXME when to call factory.cleanup(); - private static final String HANDLER_PROVIDER = JettyRequestUpgradeStrategy.class.getName() + private static final String HANDLER_PROVIDER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName() + ".HANDLER_PROVIDER"; private WebSocketServerFactory factory; @@ -76,12 +64,13 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { this.factory.setCreator(new WebSocketCreator() { @Override @SuppressWarnings("unchecked") - public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) { - Assert.isInstanceOf(ServletWebSocketRequest.class, req); - ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) req; - HandlerProvider handlerProvider = (HandlerProvider) servletRequest.getServletAttributes().get( - HANDLER_PROVIDER); - return new WebSocketHandlerAdapter(handlerProvider); + public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) { + Assert.isInstanceOf(ServletWebSocketRequest.class, request); + ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) request; + HandlerProvider handlerProvider = + (HandlerProvider) servletRequest.getServletAttributes().get( + HANDLER_PROVIDER_ATTR_NAME); + return new JettyWebSocketListenerAdapter(handlerProvider); } }); try { @@ -100,215 +89,24 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, HandlerProvider handlerProvider) - throws IOException { + String selectedProtocol, HandlerProvider handlerProvider) throws IOException { + Assert.isInstanceOf(ServletServerHttpRequest.class, request); + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + Assert.isInstanceOf(ServletServerHttpResponse.class, response); - upgrade(((ServletServerHttpRequest) request).getServletRequest(), - ((ServletServerHttpResponse) response).getServletResponse(), - selectedProtocol, handlerProvider); + HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); + + upgrade(servletRequest, servletResponse, selectedProtocol, handlerProvider); } private void upgrade(HttpServletRequest request, HttpServletResponse response, - String selectedProtocol, final HandlerProvider handlerProvider) - throws IOException { - request.setAttribute(HANDLER_PROVIDER, handlerProvider); - Assert.state(factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); - Assert.state(factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); - } + String selectedProtocol, final HandlerProvider handlerProvider) throws IOException { + Assert.state(this.factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); + Assert.state(this.factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); - /** - * Adapts Spring's {@link WebSocketHandler} to Jetty's {@link WebSocketListener}. - */ - private static class WebSocketHandlerAdapter implements WebSocketListener { - - private final HandlerProvider provider; - - private WebSocketHandler handler; - - private WebSocketSession session; - - private final AtomicInteger sessionCount = new AtomicInteger(0); - - - public WebSocketHandlerAdapter(HandlerProvider provider) { - Assert.notNull(provider, "Provider must not be null"); - Assert.isAssignable(WebSocketHandler.class, provider.getHandlerType()); - this.provider = provider; - } - - - @Override - public void onWebSocketConnect(Session session) { - - Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); - - this.session = new WebSocketSessionAdapter(session); - if (logger.isDebugEnabled()) { - logger.debug("Connection established, WebSocket session id=" - + this.session.getId() + ", uri=" + this.session.getURI()); - } - this.handler = this.provider.getHandler(); - - try { - this.handler.afterConnectionEstablished(this.session); - } - catch (Throwable ex) { - tryCloseWithError(ex); - } - } - - private void tryCloseWithError(Throwable ex) { - logger.error("Unhandled error for " + this.session, ex); - if (this.session.isOpen()) { - try { - this.session.close(CloseStatus.SERVER_ERROR); - } - catch (Throwable t) { - destroyHandler(); - } - } - } - - private void destroyHandler() { - try { - if (this.handler != null) { - this.provider.destroy(this.handler); - } - } - catch (Throwable t) { - logger.warn("Error while destroying handler", t); - } - finally { - this.session = null; - this.handler = null; - } - } - - @Override - public void onWebSocketClose(int statusCode, String reason) { - try { - CloseStatus closeStatus = new CloseStatus(statusCode, reason); - if (logger.isDebugEnabled()) { - logger.debug("Connection closed, WebSocket session id=" - + this.session.getId() + ", " + closeStatus); - } - this.handler.afterConnectionClosed(closeStatus, this.session); - } - catch (Throwable ex) { - logger.error("Unhandled error for " + this.session, ex); - } - finally { - destroyHandler(); - } - } - - @Override - public void onWebSocketText(String payload) { - try { - TextMessage message = new TextMessage(payload); - if (logger.isTraceEnabled()) { - logger.trace("Received message for WebSocket session id=" - + this.session.getId() + ": " + message); - } - this.handler.handleTextMessage(message, this.session); - } - catch(Throwable ex) { - tryCloseWithError(ex); - } - } - - @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) { - try { - BinaryMessage message = new BinaryMessage(payload, offset, len); - if (logger.isTraceEnabled()) { - logger.trace("Received binary data for WebSocket session id=" - + this.session.getId() + ": " + message); - } - this.handler.handleBinaryMessage(message, this.session); - } - catch(Throwable ex) { - tryCloseWithError(ex); - } - } - - @Override - public void onWebSocketError(Throwable cause) { - try { - this.handler.handleTransportError(cause, this.session); - } - catch (Throwable ex) { - tryCloseWithError(ex); - } - } - } - - - /** - * Adapts Jetty's {@link Session} to Spring's {@link WebSocketSession}. - */ - private static class WebSocketSessionAdapter implements WebSocketSession { - - private Session session; - - - public WebSocketSessionAdapter(Session session) { - this.session = session; - } - - - @Override - public String getId() { - return ObjectUtils.getIdentityHexString(this.session); - } - - @Override - public boolean isOpen() { - return this.session.isOpen(); - } - - @Override - public boolean isSecure() { - return this.session.isSecure(); - } - - @Override - public URI getURI() { - return this.session.getUpgradeRequest().getRequestURI(); - } - - @Override - public void sendMessage(WebSocketMessage message) throws IOException { - if (message instanceof BinaryMessage) { - sendMessage((BinaryMessage) message); - } - else if (message instanceof TextMessage) { - sendMessage((TextMessage) message); - } - else { - throw new IllegalArgumentException("Unsupported message type"); - } - } - - private void sendMessage(BinaryMessage message) throws IOException { - this.session.getRemote().sendBytes(message.getPayload()); - } - - private void sendMessage(TextMessage message) throws IOException { - this.session.getRemote().sendString(message.getPayload()); - } - - @Override - public void close() throws IOException { - this.session.close(); - } - - @Override - public void close(CloseStatus status) throws IOException { - this.session.close(status.getCode(), status.getReason()); - } + request.setAttribute(HANDLER_PROVIDER_ATTR_NAME, handlerProvider); } } From 861ab900aeb9c61b0f7271f06ae06a27e66d7d46 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 26 Apr 2013 12:04:45 -0400 Subject: [PATCH 39/51] Switch to single message method for WebSocketHandler --- .../sockjs/AbstractSockJsSession.java | 10 +-- .../sockjs/SockJsSessionFactory.java | 2 +- .../server/AbstractServerSockJsSession.java | 2 +- .../sockjs/server/AbstractSockJsService.java | 6 +- .../sockjs/server/SockJsService.java | 2 +- .../sockjs/server/TransportHandler.java | 2 +- .../server/support/DefaultSockJsService.java | 6 +- .../support/SockJsHttpRequestHandler.java | 6 +- ...AbstractHttpReceivingTransportHandler.java | 2 +- .../AbstractHttpSendingTransportHandler.java | 2 +- .../AbstractHttpServerSockJsSession.java | 2 +- .../EventSourceTransportHandler.java | 2 +- .../transport/HtmlFileTransportHandler.java | 2 +- .../JsonpPollingTransportHandler.java | 2 +- .../transport/PollingServerSockJsSession.java | 2 +- .../transport/SockJsWebSocketHandler.java | 12 +-- .../StreamingServerSockJsSession.java | 2 +- .../transport/WebSocketTransportHandler.java | 8 +- .../transport/XhrPollingTransportHandler.java | 2 +- .../XhrStreamingTransportHandler.java | 2 +- .../websocket/BinaryMessage.java | 2 - .../BinaryWebSocketHandlerAdapter.java | 38 --------- .../TextWebSocketHandlerAdapter.java | 39 --------- .../websocket/WebSocketHandler.java | 21 +++-- .../websocket/WebSocketMessage.java | 8 +- .../AbstractWebSocketSesssionAdapter.java | 83 +++++++++++++++++++ .../BinaryWebSocketHandlerAdapter.java | 50 +++++++++++ .../JettyWebSocketListenerAdapter.java | 13 +-- .../adapter/JettyWebSocketSessionAdapter.java | 27 ++---- .../adapter/StandardEndpointAdapter.java | 36 ++++---- .../StandardWebSocketSessionAdapter.java | 39 ++------- .../TextWebSocketHandlerAdapter.java} | 23 ++--- .../adapter/WebSocketHandlerAdapter.java | 70 ++++++++++++++++ .../adapter/WebSocketHandlerInvoker.java | 63 +++++++------- .../websocket/adapter/package-info.java | 5 +- .../websocket/client/WebSocketClient.java | 4 +- .../client/WebSocketConnectionManager.java | 6 +- .../endpoint/StandardWebSocketClient.java | 6 +- .../server/DefaultHandshakeHandler.java | 2 +- .../websocket/server/HandshakeHandler.java | 2 +- .../server/RequestUpgradeStrategy.java | 2 +- .../AbstractEndpointUpgradeStrategy.java | 8 +- .../support/JettyRequestUpgradeStrategy.java | 8 +- .../support/WebSocketHttpRequestHandler.java | 8 +- 44 files changed, 358 insertions(+), 281 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java rename spring-websocket/src/main/java/org/springframework/websocket/{WebSocketHandlerAdapter.java => adapter/TextWebSocketHandlerAdapter.java} (54%) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 9b1e07c99f..d105a16d6a 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -57,7 +57,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * @param sessionId * @param handlerProvider the recipient of SockJS messages */ - public AbstractSockJsSession(String sessionId, HandlerProvider handlerProvider) { + public AbstractSockJsSession(String sessionId, HandlerProvider> handlerProvider) { Assert.notNull(sessionId, "sessionId is required"); Assert.notNull(handlerProvider, "handlerProvider is required"); this.sessionId = sessionId; @@ -136,12 +136,12 @@ public abstract class AbstractSockJsSession implements WebSocketSession { public void delegateMessages(String[] messages) { for (String message : messages) { - this.handler.handleTextMessage(new TextMessage(message), this); + this.handler.handleMessage(this, new TextMessage(message)); } } public void delegateError(Throwable ex) { - this.handler.handleTransportError(ex, this); + this.handler.handleTransportError(this, ex); } /** @@ -160,7 +160,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - this.handler.afterConnectionClosed(status, this); + this.handler.afterConnectionClosed(this, status); } } } @@ -190,7 +190,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - this.handler.afterConnectionClosed(status, this); + this.handler.afterConnectionClosed(this, status); } } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index d9be624791..2f8f66c3bb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -35,6 +35,6 @@ public interface SockJsSessionFactory{ * @param handler the underlying {@link WebSocketHandler} * @return a new non-null session */ - S createSession(String sessionId, HandlerProvider handler); + S createSession(String sessionId, HandlerProvider> handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 7f2e7bbeea..3ba7ae422c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -45,7 +45,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, - HandlerProvider handler) { + HandlerProvider> handler) { super(sessionId, handler); this.sockJsConfig = config; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 8e8999a049..dddd2c7320 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -200,7 +200,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * @throws Exception */ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, HandlerProvider handler) + String sockJsPath, HandlerProvider> handler) throws IOException, TransportErrorException { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -255,10 +255,10 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf } protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws IOException; + HandlerProvider> handler) throws IOException; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, HandlerProvider handler) + String sessionId, TransportType transportType, HandlerProvider> handler) throws IOException, TransportErrorException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 46d357cc6a..0e2a91cbf4 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -30,6 +30,6 @@ import org.springframework.websocket.WebSocketHandler; public interface SockJsService { void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, - HandlerProvider handler) throws IOException, TransportErrorException; + HandlerProvider> handler) throws IOException, TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index beac1aa1a3..80d95fbd91 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -31,6 +31,6 @@ public interface TransportHandler { TransportType getTransportType(); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler, AbstractSockJsSession session) throws TransportErrorException; + HandlerProvider> handler, AbstractSockJsSession session) throws TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index a0d61f79e2..28181ef124 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -143,7 +143,7 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws IOException { + HandlerProvider> handler) throws IOException { if (isWebSocketEnabled()) { TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); @@ -160,7 +160,7 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, HandlerProvider handler) + String sessionId, TransportType transportType, HandlerProvider> handler) throws IOException, TransportErrorException { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -210,7 +210,7 @@ public class DefaultSockJsService extends AbstractSockJsService { transportHandler.handleRequest(request, response, handler, session); } - public AbstractSockJsSession getSockJsSession(String sessionId, HandlerProvider handler, + public AbstractSockJsSession getSockJsSession(String sessionId, HandlerProvider> handler, TransportHandler transportHandler) { AbstractSockJsSession session = this.sessions.get(sessionId); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index cb583622d2..a90de74014 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -45,7 +45,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { private final SockJsService sockJsService; - private final HandlerProvider handlerProvider; + private final HandlerProvider> handlerProvider; private final UrlPathHelper urlPathHelper = new UrlPathHelper(); @@ -65,7 +65,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { this.prefix = prefix; this.sockJsService = sockJsService; - this.handlerProvider = new SimpleHandlerProvider(handler); + this.handlerProvider = new SimpleHandlerProvider>(handler); } /** @@ -76,7 +76,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { * Servlet container this is the path within the current servlet mapping. */ public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, - HandlerProvider handlerProvider) { + HandlerProvider> handlerProvider) { Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 0f1fd0d246..6da6af510e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -55,7 +55,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider webSocketHandler, AbstractSockJsSession session) + HandlerProvider> webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { if (session == null) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index ac53c77f58..f7bfcfeeeb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -58,7 +58,7 @@ public abstract class AbstractHttpSendingTransportHandler @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider webSocketHandler, AbstractSockJsSession session) + HandlerProvider> webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { // Set content type before writing diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 0492fee315..0a59625bcd 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -51,7 +51,7 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - HandlerProvider handler) { + HandlerProvider> handler) { super(sessionId, sockJsConfig, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index 8fb5a0313c..ec59fa8a26 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -47,7 +47,7 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan } @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index 3075aade2b..e2fda86dbc 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -81,7 +81,7 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle } @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index cc03645354..8fed9533c6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -51,7 +51,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + public PollingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index 743c369487..89aa292c14 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -26,7 +26,7 @@ import org.springframework.websocket.WebSocketHandler; public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { public PollingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - HandlerProvider handler) { + HandlerProvider> handler) { super(sessionId, sockJsConfig, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 95cb4a0522..b8da4c33f0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -27,9 +27,9 @@ import org.springframework.util.StringUtils; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.TextWebSocketHandlerAdapter; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.adapter.TextWebSocketHandlerAdapter; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,7 +45,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final SockJsConfiguration sockJsConfig; - private final HandlerProvider handlerProvider; + private final HandlerProvider> handlerProvider; private WebSocketServerSockJsSession sockJsSession; @@ -55,7 +55,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final ObjectMapper objectMapper = new ObjectMapper(); - public SockJsWebSocketHandler(SockJsConfiguration config, HandlerProvider handlerProvider) { + public SockJsWebSocketHandler(SockJsConfiguration config, HandlerProvider> handlerProvider) { Assert.notNull(config, "sockJsConfig is required"); Assert.notNull(handlerProvider, "handlerProvider is required"); this.sockJsConfig = config; @@ -74,17 +74,17 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { } @Override - public void handleTextMessage(TextMessage message, WebSocketSession wsSession) { + public void handleMessage(WebSocketSession wsSession, TextMessage message) { this.sockJsSession.handleMessage(message, wsSession); } @Override - public void afterConnectionClosed(CloseStatus status, WebSocketSession wsSession) { + public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) { this.sockJsSession.delegateConnectionClosed(status); } @Override - public void handleTransportError(Throwable exception, WebSocketSession webSocketSession) { + public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) { this.sockJsSession.delegateError(exception); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index 506985ba73..a5352e7f1c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -33,7 +33,7 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio public StreamingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - HandlerProvider handler) { + HandlerProvider> handler) { super(sessionId, sockJsConfig, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 8f615c913d..6d1b83955c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -65,11 +65,11 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler, AbstractSockJsSession session) throws TransportErrorException { + HandlerProvider> handler, AbstractSockJsSession session) throws TransportErrorException { try { - WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, handler); - this.handshakeHandler.doHandshake(request, response, new SimpleHandlerProvider(sockJsWrapper)); + WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, handler); + this.handshakeHandler.doHandshake(request, response, new SimpleHandlerProvider>(sockJsWrapper)); } catch (Throwable t) { throw new TransportErrorException("Failed to start handshake request", t, session.getId()); @@ -80,7 +80,7 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws IOException { + HandlerProvider> handler) throws IOException { return this.handshakeHandler.doHandshake(request, response, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index 06a6aaf37d..7c7709e0c2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -51,7 +51,7 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + public PollingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index 9e6c7457a4..ef31f69628 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -48,7 +48,7 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider handler) { + public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java index 8bc0e42007..90d3efd691 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java @@ -19,8 +19,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; -import org.springframework.util.Assert; - /** * A {@link WebSocketMessage} that contains a binary {@link ByteBuffer} payload. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java deleted file mode 100644 index acfeeab6d2..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryWebSocketHandlerAdapter.java +++ /dev/null @@ -1,38 +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; - -import java.io.IOException; - - -/** - * - * @author rossen - */ -public class BinaryWebSocketHandlerAdapter extends WebSocketHandlerAdapter { - - @Override - public void handleTextMessage(TextMessage message, WebSocketSession session) { - try { - session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Text messages not supported")); - } - catch (IOException e) { - // ignore - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java deleted file mode 100644 index 69941bfa14..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/TextWebSocketHandlerAdapter.java +++ /dev/null @@ -1,39 +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; - -import java.io.IOException; - - -/** - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class TextWebSocketHandlerAdapter extends WebSocketHandlerAdapter { - - @Override - public void handleBinaryMessage(BinaryMessage message, WebSocketSession session) { - try { - session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Binary messages not supported")); - } - catch (IOException e) { - // ignore - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 5984d49ae0..3b0b9476f9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -19,10 +19,14 @@ package org.springframework.websocket; /** * A handler for WebSocket sessions. * + * @param The type of message being handled {@link TextMessage}, {@link BinaryMessage} + * (or {@link WebSocketMessage} for both). + * * @author Rossen Stoyanchev + * @author Phillip Webb * @since 4.0 */ -public interface WebSocketHandler { +public interface WebSocketHandler> { /** * A new WebSocket connection has been opened and is ready to be used. @@ -30,23 +34,18 @@ public interface WebSocketHandler { void afterConnectionEstablished(WebSocketSession session); /** - * Handle an incoming text message. + * Handle an incoming WebSocket message. */ - void handleTextMessage(TextMessage message, WebSocketSession session); + void handleMessage(WebSocketSession session, T message); /** - * Handle an incoming binary message. + * Handle an error from the underlying WebSocket message transport. */ - void handleBinaryMessage(BinaryMessage message, WebSocketSession session); - - /** - * TODO - */ - void handleTransportError(Throwable exception, WebSocketSession session); + void handleTransportError(WebSocketSession session, Throwable exception); /** * A WebSocket connection has been closed. */ - void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session); + void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java index 61d4f11b7c..a5a06824b0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java @@ -20,9 +20,9 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * A message that can be sent or received over a WebSocket connection. A WebSocket - * message must be either a {@link BinaryMessage} or a {@link TextMessage} depending - * on the payload. No further subclasses are supported. + * A message that can be handled or sent during a WebSocket interaction. There are only + * two sub-classes {@link BinaryMessage} or a {@link TextMessage} with no further + * sub-classing expected. * * @author Rossen Stoyanchev * @since 4.0 @@ -35,7 +35,7 @@ public abstract class WebSocketMessage { /** - * Create a new {@link WebSocketMessage} instance. + * Create a new {@link WebSocketMessage} instance with the given payload. * @param payload a non-null payload */ WebSocketMessage(T payload) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java new file mode 100644 index 0000000000..122f2264c5 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java @@ -0,0 +1,83 @@ +/* + * 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.adapter; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + + +/** + * An base class for implementations adapting {@link WebSocketSession}. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public abstract class AbstractWebSocketSesssionAdapter implements WebSocketSession { + + protected final Log logger = LogFactory.getLog(getClass()); + + + @Override + public final void sendMessage(WebSocketMessage message) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Sending " + message + ", " + this); + } + Assert.isTrue(isOpen(), "Cannot send message after connection closed."); + if (message instanceof TextMessage) { + sendTextMessage((TextMessage) message); + } + else if (message instanceof BinaryMessage) { + sendBinaryMessage((BinaryMessage) message); + } + else { + throw new IllegalStateException("Unexpected WebSocketMessage type: " + message); + } + } + + protected abstract void sendTextMessage(TextMessage message) throws IOException ; + + protected abstract void sendBinaryMessage(BinaryMessage message) throws IOException ; + + @Override + public void close() throws IOException { + close(CloseStatus.NORMAL); + } + + @Override + public final void close(CloseStatus status) throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("Closing " + this); + } + closeInternal(status); + } + + protected abstract void closeInternal(CloseStatus status) throws IOException; + + @Override + public String toString() { + return "WebSocket session id=" + getId(); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java new file mode 100644 index 0000000000..d136dc9012 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.websocket.adapter; + +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; + + +/** + * A {@link WebSocketHandler} for binary messages with empty methods. + * + * @author Rossen Stoyanchev + * @author Phillip Webb + * @since 4.0 + */ +public class BinaryWebSocketHandlerAdapter implements WebSocketHandler { + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + } + + @Override + public void handleMessage(WebSocketSession session, BinaryMessage message) { + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java index 2ec1ec6f56..ccfe4b4048 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -26,6 +26,7 @@ import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; /** @@ -38,12 +39,12 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { private static Log logger = LogFactory.getLog(JettyWebSocketListenerAdapter.class); - private final WebSocketHandler handler; + private final WebSocketHandler> handler; private WebSocketSession wsSession; - public JettyWebSocketListenerAdapter(HandlerProvider provider) { + public JettyWebSocketListenerAdapter(HandlerProvider> provider) { Assert.notNull(provider, "provider is required"); this.handler = new WebSocketHandlerInvoker(provider).setLogger(logger); } @@ -58,23 +59,23 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { @Override public void onWebSocketClose(int statusCode, String reason) { CloseStatus closeStatus = new CloseStatus(statusCode, reason); - this.handler.afterConnectionClosed(closeStatus, this.wsSession); + this.handler.afterConnectionClosed(this.wsSession, closeStatus); } @Override public void onWebSocketText(String payload) { TextMessage message = new TextMessage(payload); - this.handler.handleTextMessage(message, this.wsSession); + this.handler.handleMessage(this.wsSession, message); } @Override public void onWebSocketBinary(byte[] payload, int offset, int len) { BinaryMessage message = new BinaryMessage(payload, offset, len); - this.handler.handleBinaryMessage(message, this.wsSession); + this.handler.handleMessage(this.wsSession, message); } @Override public void onWebSocketError(Throwable cause) { - this.handler.handleTransportError(cause, this.wsSession); + this.handler.handleTransportError(this.wsSession, cause); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java index ef23f92427..c48f1b7db3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java @@ -24,7 +24,6 @@ import org.springframework.util.ObjectUtils; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; @@ -34,7 +33,7 @@ import org.springframework.websocket.WebSocketSession; * @author Phillip Webb * @since 4.0 */ -public class JettyWebSocketSessionAdapter implements WebSocketSession { +public class JettyWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter { private Session session; @@ -65,33 +64,17 @@ public class JettyWebSocketSessionAdapter implements WebSocketSession { } @Override - public void sendMessage(WebSocketMessage message) throws IOException { - if (message instanceof BinaryMessage) { - sendMessage((BinaryMessage) message); - } - else if (message instanceof TextMessage) { - sendMessage((TextMessage) message); - } - else { - throw new IllegalArgumentException("Unsupported message type"); - } - } - - private void sendMessage(BinaryMessage message) throws IOException { - this.session.getRemote().sendBytes(message.getPayload()); - } - - private void sendMessage(TextMessage message) throws IOException { + protected void sendTextMessage(TextMessage message) throws IOException { this.session.getRemote().sendString(message.getPayload()); } @Override - public void close() throws IOException { - this.session.close(); + protected void sendBinaryMessage(BinaryMessage message) throws IOException { + this.session.getRemote().sendBytes(message.getPayload()); } @Override - public void close(CloseStatus status) throws IOException { + protected void closeInternal(CloseStatus status) throws IOException { this.session.close(status.getCode(), status.getReason()); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java index 3ce78ab01f..a0f06d0c20 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -32,6 +32,7 @@ import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.PartialMessageHandler; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; @@ -45,7 +46,7 @@ public class StandardEndpointAdapter extends Endpoint { private static Log logger = LogFactory.getLog(StandardEndpointAdapter.class); - private final WebSocketHandler handler; + private final WebSocketHandler> handler; private final Class handlerClass; @@ -53,7 +54,7 @@ public class StandardEndpointAdapter extends Endpoint { - public StandardEndpointAdapter(HandlerProvider provider) { + public StandardEndpointAdapter(HandlerProvider> provider) { Assert.notNull(provider, "provider is required"); this.handler = new WebSocketHandlerInvoker(provider).setLogger(logger); this.handlerClass= provider.getHandlerType(); @@ -69,15 +70,8 @@ public class StandardEndpointAdapter extends Endpoint { handleTextMessage(session, message); } }); - if (PartialMessageHandler.class.isAssignableFrom(this.handlerClass)) { - session.addMessageHandler(new MessageHandler.Partial() { - @Override - public void onMessage(ByteBuffer messagePart, boolean isLast) { - handleBinaryMessage(session, messagePart, isLast); - } - }); - } - else { + + if (!PartialMessageHandler.class.isAssignableFrom(this.handlerClass)) { session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(ByteBuffer message) { @@ -85,30 +79,38 @@ public class StandardEndpointAdapter extends Endpoint { } }); } + else { + session.addMessageHandler(new MessageHandler.Partial() { + @Override + public void onMessage(ByteBuffer messagePart, boolean isLast) { + handleBinaryMessage(session, messagePart, isLast); + } + }); + } this.wsSession = new StandardWebSocketSessionAdapter(session); this.handler.afterConnectionEstablished(this.wsSession); } private void handleTextMessage(javax.websocket.Session session, String payload) { - TextMessage message = new TextMessage(payload); - this.handler.handleTextMessage(message, this.wsSession); + TextMessage textMessage = new TextMessage(payload); + this.handler.handleMessage(this.wsSession, textMessage); } private void handleBinaryMessage(javax.websocket.Session session, ByteBuffer payload, boolean isLast) { - BinaryMessage message = new BinaryMessage(payload, isLast); - this.handler.handleBinaryMessage(message, this.wsSession); + BinaryMessage binaryMessage = new BinaryMessage(payload, isLast); + this.handler.handleMessage(this.wsSession, binaryMessage); } @Override public void onClose(javax.websocket.Session session, CloseReason reason) { CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); - this.handler.afterConnectionClosed(closeStatus, this.wsSession); + this.handler.afterConnectionClosed(this.wsSession, closeStatus); } @Override public void onError(javax.websocket.Session session, Throwable exception) { - this.handler.handleTransportError(exception, this.wsSession); + this.handler.handleTransportError(this.wsSession, exception); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java index 30bc063baf..811ebd93c5 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java @@ -21,15 +21,11 @@ import java.net.URI; import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCodes; -import javax.websocket.RemoteEndpoint.Basic; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; /** @@ -39,9 +35,7 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class StandardWebSocketSessionAdapter implements WebSocketSession { - - private static Log logger = LogFactory.getLog(StandardWebSocketSessionAdapter.class); +public class StandardWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter { private final javax.websocket.Session session; @@ -73,39 +67,18 @@ public class StandardWebSocketSessionAdapter implements WebSocketSession { } @Override - public void sendMessage(WebSocketMessage message) throws IOException { - if (logger.isTraceEnabled()) { - logger.trace("Sending: " + message + ", " + this); - } - Assert.isTrue(isOpen(), "Cannot send message after connection closed."); - Basic remote = this.session.getBasicRemote(); - if (message instanceof TextMessage) { - remote.sendText(((TextMessage) message).getPayload()); - } - else if (message instanceof BinaryMessage) { - remote.sendBinary(((BinaryMessage) message).getPayload()); - } - else { - throw new IllegalStateException("Unexpected WebSocketMessage type: " + message); - } + protected void sendTextMessage(TextMessage message) throws IOException { + this.session.getBasicRemote().sendText(message.getPayload()); } @Override - public void close() throws IOException { - close(CloseStatus.NORMAL); + protected void sendBinaryMessage(BinaryMessage message) throws IOException { + this.session.getBasicRemote().sendBinary(message.getPayload()); } @Override - public void close(CloseStatus status) throws IOException { - if (logger.isDebugEnabled()) { - logger.debug("Closing " + this); - } + protected void closeInternal(CloseStatus status) throws IOException { this.session.close(new CloseReason(CloseCodes.getCloseCode(status.getCode()), status.getReason())); } - @Override - public String toString() { - return "WebSocket session id=" + getId(); - } - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java similarity index 54% rename from spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java index b42f7000f0..6c54f653e8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java @@ -14,36 +14,37 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.websocket.adapter; + +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; /** - * A {@link WebSocketHandler} with empty methods. + * A {@link WebSocketHandler} for text messages with empty methods. * * @author Rossen Stoyanchev + * @author Phillip Webb * @since 4.0 - * @see WebSocketHandler */ -public class WebSocketHandlerAdapter implements WebSocketHandler { +public class TextWebSocketHandlerAdapter implements WebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession session) { } @Override - public void afterConnectionClosed(CloseStatus status, WebSocketSession session) { + public void handleMessage(WebSocketSession session, TextMessage message) { } @Override - public void handleTextMessage(TextMessage message, WebSocketSession session) { + public void handleTransportError(WebSocketSession session, Throwable exception) { } @Override - public void handleBinaryMessage(BinaryMessage message, WebSocketSession session) { - } - - @Override - public void handleTransportError(Throwable exception, WebSocketSession session) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java new file mode 100644 index 0000000000..8a4c64f630 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java @@ -0,0 +1,70 @@ +/* + * 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.adapter; + +import org.springframework.websocket.BinaryMessage; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + + +/** + * A {@link WebSocketHandler} for both text and binary messages with empty methods. + * + * @author Rossen Stoyanchev + * @author Phillip Webb + * @since 4.0 + * + * @see TextWebSocketHandlerAdapter + * @see BinaryWebSocketHandlerAdapter + */ +public class WebSocketHandlerAdapter implements WebSocketHandler> { + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + } + + @Override + public final void handleMessage(WebSocketSession session, WebSocketMessage message) { + if (message instanceof TextMessage) { + handleTextMessage(session, (TextMessage) message); + } + else if (message instanceof BinaryMessage) { + handleBinaryMessage(session, (BinaryMessage) message); + } + else { + throw new IllegalStateException("Unexpected WebSocket message type: " + message); + } + } + + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + } + + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java index 45abc18a52..7e9dce31b0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java @@ -20,36 +20,39 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.GenericTypeResolver; import org.springframework.util.Assert; -import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.HandlerProvider; -import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; /** - * A class for managing and delegating to a {@link WebSocketHandler} instance, applying - * initialization and destruction as necessary at the start and end of the WebSocket - * session, ensuring that any unhandled exceptions from its methods are caught and handled - * by closing the session, and also adding uniform logging. + * A class for managing and delegating to a {@link WebSocketHandler} instance, ensuring + * the handler is initialized and destroyed, that any unhandled exceptions from handler + * are caught (and handled by closing the session), as well as adding logging. * * @author Rossen Stoyanchev + * @author Phillip Webb * @since 4.0 */ -public class WebSocketHandlerInvoker implements WebSocketHandler { +public class WebSocketHandlerInvoker implements WebSocketHandler> { private Log logger = LogFactory.getLog(WebSocketHandlerInvoker.class); - private final HandlerProvider handlerProvider; + private final HandlerProvider> handlerProvider; - private WebSocketHandler handler; + private final Class supportedMessageType; + + private WebSocketHandler handler; private final AtomicInteger sessionCount = new AtomicInteger(0); - public WebSocketHandlerInvoker(HandlerProvider handlerProvider) { - this.handlerProvider = handlerProvider; + public WebSocketHandlerInvoker(HandlerProvider> provider) { + this.handlerProvider = provider; + this.supportedMessageType = GenericTypeResolver.resolveTypeArgument(provider.getHandlerType(), WebSocketHandler.class); } public WebSocketHandlerInvoker setLogger(Log logger) { @@ -77,8 +80,10 @@ public class WebSocketHandlerInvoker implements WebSocketHandler { tryCloseWithError(session, ex, null); } - public void tryCloseWithError(WebSocketSession session, Throwable ex, CloseStatus status) { - logger.error("Unhandled error for " + session, ex); + public void tryCloseWithError(WebSocketSession session, Throwable exeption, CloseStatus status) { + if (exeption != null) { + logger.error("Closing due to exception for " + session, exeption); + } if (session.isOpen()) { try { session.close(CloseStatus.SERVER_ERROR); @@ -103,13 +108,18 @@ public class WebSocketHandlerInvoker implements WebSocketHandler { } } + @SuppressWarnings("unchecked") @Override - public void handleTextMessage(TextMessage message, WebSocketSession session) { + public void handleMessage(WebSocketSession session, WebSocketMessage message) { if (logger.isTraceEnabled()) { - logger.trace("Received text message for " + session + ": " + message); + logger.trace("Received " + message + ", " + session); + } + if (!this.supportedMessageType.isAssignableFrom(message.getClass())) { + tryCloseWithError(session, null, CloseStatus.NOT_ACCEPTABLE.withReason("Message type not supported")); + return; } try { - this.handler.handleTextMessage(message, session); + ((WebSocketHandler) this.handler).handleMessage(session, message); } catch (Throwable ex) { tryCloseWithError(session,ex); @@ -117,25 +127,12 @@ public class WebSocketHandlerInvoker implements WebSocketHandler { } @Override - public void handleBinaryMessage(BinaryMessage message, WebSocketSession session) { - if (logger.isTraceEnabled()) { - logger.trace("Received binary message for " + session); - } - try { - this.handler.handleBinaryMessage(message, session); - } - catch (Throwable ex) { - tryCloseWithError(session, ex); - } - } - - @Override - public void handleTransportError(Throwable exception, WebSocketSession session) { + public void handleTransportError(WebSocketSession session, Throwable exception) { if (logger.isDebugEnabled()) { logger.debug("Transport error for " + session, exception); } try { - this.handler.handleTransportError(exception, session); + this.handler.handleTransportError(session, exception); } catch (Throwable ex) { tryCloseWithError(session, ex); @@ -143,12 +140,12 @@ public class WebSocketHandlerInvoker implements WebSocketHandler { } @Override - public void afterConnectionClosed(CloseStatus closeStatus, WebSocketSession session) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { if (logger.isDebugEnabled()) { logger.debug("Connection closed for " + session + ", " + closeStatus); } try { - this.handler.afterConnectionClosed(closeStatus, session); + this.handler.afterConnectionClosed(session, closeStatus); } catch (Throwable ex) { logger.error("Unhandled error for " + this, ex); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java index 695869a5ff..59f29a08a3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java @@ -15,8 +15,9 @@ */ /** - * Adapters for the {@link org.springframework.websocket.WebSocketHandler} and - * {@link org.springframework.websocket.WebSocketSession} contracts. + * Classes adapting Spring's WebSocket API classes to and from various WebSocket + * implementations. Also contains convenient base classes for + * {@link org.springframework.websocket.WebSocketHandler} implementations. */ package org.springframework.websocket.adapter; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java index 18260dfd9b..d77b252a78 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -38,10 +38,10 @@ public interface WebSocketClient { WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; - WebSocketSession doHandshake(HandlerProvider handler, + WebSocketSession doHandshake(HandlerProvider> handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; - WebSocketSession doHandshake(HandlerProvider handler, HttpHeaders headers, URI uri) + WebSocketSession doHandshake(HandlerProvider> handler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index 5b84998caf..d633545cbe 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -34,7 +34,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag private final WebSocketClient client; - private final HandlerProvider handlerProvider; + private final HandlerProvider> handlerProvider; private WebSocketSession webSocketSession; @@ -46,11 +46,11 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag super(uriTemplate, uriVariables); this.client = webSocketClient; - this.handlerProvider = new SimpleHandlerProvider(webSocketHandler); + this.handlerProvider = new SimpleHandlerProvider>(webSocketHandler); } public WebSocketConnectionManager(WebSocketClient webSocketClient, - HandlerProvider handlerProvider, String uriTemplate, Object... uriVariables) { + HandlerProvider> handlerProvider, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); this.client = webSocketClient; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index ca0e103efb..0b9340807c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -64,10 +64,10 @@ public class StandardWebSocketClient implements WebSocketClient { public WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { - return doHandshake(new SimpleHandlerProvider(handler), uriTemplate, uriVariables); + return doHandshake(new SimpleHandlerProvider>(handler), uriTemplate, uriVariables); } - public WebSocketSession doHandshake(HandlerProvider handler, + public WebSocketSession doHandshake(HandlerProvider> handler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); @@ -75,7 +75,7 @@ public class StandardWebSocketClient implements WebSocketClient { } @Override - public WebSocketSession doHandshake(HandlerProvider handler, + public WebSocketSession doHandshake(HandlerProvider> handler, final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { Endpoint endpoint = new StandardEndpointAdapter(handler); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index c37ffef466..46b043fd57 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -88,7 +88,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws IOException { + HandlerProvider> handler) throws IOException { logger.debug("Starting handshake for " + request.getURI()); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 227b6fefd4..532dd65501 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -33,6 +33,6 @@ public interface HandshakeHandler { boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider handler) throws IOException; + HandlerProvider> handler) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index 9af17b4fff..773d5863f7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -44,7 +44,7 @@ public interface RequestUpgradeStrategy { * @param handler the handler for WebSocket messages */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - HandlerProvider handlerProvider) throws IOException; + HandlerProvider> handlerProvider) throws IOException; // FIXME how to indicate failure to upgrade? } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index e27ca8b66e..cd34bfdfc7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -43,13 +43,9 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, HandlerProvider handler) throws IOException { + String protocol, HandlerProvider> handler) throws IOException { - upgradeInternal(request, response, protocol, adaptWebSocketHandler(handler)); - } - - protected Endpoint adaptWebSocketHandler(HandlerProvider handler) { - return new StandardEndpointAdapter(handler); + upgradeInternal(request, response, protocol, new StandardEndpointAdapter(handler)); } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 95e54b34e4..7448e66ac7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -67,8 +67,8 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) { Assert.isInstanceOf(ServletWebSocketRequest.class, request); ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) request; - HandlerProvider handlerProvider = - (HandlerProvider) servletRequest.getServletAttributes().get( + HandlerProvider> handlerProvider = + (HandlerProvider>) servletRequest.getServletAttributes().get( HANDLER_PROVIDER_ATTR_NAME); return new JettyWebSocketListenerAdapter(handlerProvider); } @@ -89,7 +89,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, HandlerProvider handlerProvider) throws IOException { + String selectedProtocol, HandlerProvider> handlerProvider) throws IOException { Assert.isInstanceOf(ServletServerHttpRequest.class, request); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -101,7 +101,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { } private void upgrade(HttpServletRequest request, HttpServletResponse response, - String selectedProtocol, final HandlerProvider handlerProvider) throws IOException { + String selectedProtocol, final HandlerProvider> handlerProvider) throws IOException { Assert.state(this.factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); Assert.state(this.factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index c5e622726d..6d8edc0c28 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -45,18 +45,18 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { private final HandshakeHandler handshakeHandler; - private final HandlerProvider handlerProvider; + private final HandlerProvider> handlerProvider; public WebSocketHttpRequestHandler(WebSocketHandler webSocketHandler) { - this(new SimpleHandlerProvider(webSocketHandler)); + this(new SimpleHandlerProvider>(webSocketHandler)); } - public WebSocketHttpRequestHandler( HandlerProvider handlerProvider) { + public WebSocketHttpRequestHandler( HandlerProvider> handlerProvider) { this(handlerProvider, new DefaultHandshakeHandler()); } - public WebSocketHttpRequestHandler( HandlerProvider handlerProvider, + public WebSocketHttpRequestHandler( HandlerProvider> handlerProvider, HandshakeHandler handshakeHandler) { Assert.notNull(handlerProvider, "handlerProvider is required"); Assert.notNull(handshakeHandler, "handshakeHandler is required"); From 0c1b329949a11d56271b09047acbda8d76a61e3b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 26 Apr 2013 21:59:04 -0400 Subject: [PATCH 40/51] Remove HandlerProvider --- .../sockjs/AbstractSockJsSession.java | 10 +- .../sockjs/SockJsSessionFactory.java | 5 +- .../server/AbstractServerSockJsSession.java | 5 +- .../sockjs/server/AbstractSockJsService.java | 14 +- .../sockjs/server/SockJsConfiguration.java | 1 - .../sockjs/server/SockJsService.java | 6 +- .../sockjs/server/TransportHandler.java | 3 +- .../server/support/DefaultSockJsService.java | 17 ++- .../support/SockJsHttpRequestHandler.java | 32 +---- ...AbstractHttpReceivingTransportHandler.java | 3 +- .../AbstractHttpSendingTransportHandler.java | 5 +- .../AbstractHttpServerSockJsSession.java | 9 +- .../EventSourceTransportHandler.java | 3 +- .../transport/HtmlFileTransportHandler.java | 3 +- .../JsonpPollingTransportHandler.java | 3 +- .../transport/PollingServerSockJsSession.java | 7 +- .../transport/SockJsWebSocketHandler.java | 11 +- .../StreamingServerSockJsSession.java | 7 +- .../transport/WebSocketTransportHandler.java | 12 +- .../transport/XhrPollingTransportHandler.java | 3 +- .../XhrStreamingTransportHandler.java | 3 +- .../websocket/HandlerProvider.java | 48 ------- .../JettyWebSocketListenerAdapter.java | 19 ++- .../adapter/StandardEndpointAdapter.java | 14 +- .../adapter/WebSocketHandlerInvoker.java | 35 ++--- .../websocket/client/WebSocketClient.java | 9 +- .../client/WebSocketConnectionManager.java | 17 +-- .../AnnotatedEndpointConnectionManager.java | 20 +-- .../endpoint/EndpointConnectionManager.java | 22 +-- .../endpoint/StandardWebSocketClient.java | 18 +-- .../server/DefaultHandshakeHandler.java | 7 +- .../websocket/server/HandshakeHandler.java | 3 +- .../server/RequestUpgradeStrategy.java | 4 +- .../server/endpoint/EndpointRegistration.java | 28 ++-- .../AbstractEndpointUpgradeStrategy.java | 5 +- .../support/JettyRequestUpgradeStrategy.java | 17 +-- .../support/WebSocketHttpRequestHandler.java | 19 +-- .../support/BeanCreatingHandlerProvider.java | 6 +- .../PerConnectionWebSocketHandlerProxy.java | 132 ++++++++++++++++++ .../support/SimpleHandlerProvider.java | 62 -------- 40 files changed, 280 insertions(+), 367 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index d105a16d6a..c7dd7a4875 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -23,7 +23,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -53,15 +52,14 @@ public abstract class AbstractSockJsSession implements WebSocketSession { /** - * * @param sessionId - * @param handlerProvider the recipient of SockJS messages + * @param webSocketHandler the recipient of SockJS messages */ - public AbstractSockJsSession(String sessionId, HandlerProvider> handlerProvider) { + public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(sessionId, "sessionId is required"); - Assert.notNull(handlerProvider, "handlerProvider is required"); + Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.sessionId = sessionId; - this.handler = new WebSocketHandlerInvoker(handlerProvider).setLogger(logger); + this.handler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); } public String getId() { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index 2f8f66c3bb..c9bc94519c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -16,7 +16,6 @@ package org.springframework.sockjs; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -32,9 +31,9 @@ public interface SockJsSessionFactory{ /** * Create a new SockJS session. * @param sessionId the ID of the session - * @param handler the underlying {@link WebSocketHandler} + * @param webSocketHandler the underlying {@link WebSocketHandler} * @return a new non-null session */ - S createSession(String sessionId, HandlerProvider> handler); + S createSession(String sessionId, WebSocketHandler webSocketHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index 3ba7ae422c..f9e57a9487 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -25,7 +25,6 @@ import java.util.concurrent.ScheduledFuture; import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketMessage; @@ -44,9 +43,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession private ScheduledFuture heartbeatTask; - public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, - HandlerProvider> handler) { - + public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { super(sessionId, handler); this.sockJsConfig = config; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index dddd2c7320..6e0504847b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -36,7 +36,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.DigestUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -200,8 +199,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * @throws Exception */ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, HandlerProvider> handler) - throws IOException, TransportErrorException { + String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -227,7 +225,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf return; } else if (sockJsPath.equals("/websocket")) { - handleRawWebSocketRequest(request, response, handler); + handleRawWebSocketRequest(request, response, webSocketHandler); return; } @@ -247,18 +245,18 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf return; } - handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), handler); + handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), webSocketHandler); } finally { response.flush(); } } - protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler) throws IOException; + protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, + ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, HandlerProvider> handler) + String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java index 4cec5cff6f..61de30afa7 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java @@ -17,7 +17,6 @@ package org.springframework.sockjs.server; import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 0e2a91cbf4..580d2afec6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -20,7 +20,6 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -29,7 +28,8 @@ import org.springframework.websocket.WebSocketHandler; */ public interface SockJsService { - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, String sockJsPath, - HandlerProvider> handler) throws IOException, TransportErrorException; + + void handleRequest(ServerHttpRequest request, ServerHttpResponse response, + String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index 80d95fbd91..af05a34357 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -19,7 +19,6 @@ package org.springframework.sockjs.server; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -31,6 +30,6 @@ public interface TransportHandler { TransportType getTransportType(); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler, AbstractSockJsSession session) throws TransportErrorException; + WebSocketHandler handler, AbstractSockJsSession session) throws TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 28181ef124..f070c1b94d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -51,7 +51,6 @@ 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.CollectionUtils; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; @@ -143,13 +142,13 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler) throws IOException { + WebSocketHandler webSocketHandler) throws IOException { if (isWebSocketEnabled()) { TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); if (transportHandler != null) { if (transportHandler instanceof HandshakeHandler) { - ((HandshakeHandler) transportHandler).doHandshake(request, response, handler); + ((HandshakeHandler) transportHandler).doHandshake(request, response, webSocketHandler); return; } } @@ -160,7 +159,7 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, HandlerProvider> handler) + String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -188,7 +187,7 @@ public class DefaultSockJsService extends AbstractSockJsService { return; } - AbstractSockJsSession session = getSockJsSession(sessionId, handler, transportHandler); + AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler, transportHandler); if (session != null) { if (transportType.setsNoCacheHeader()) { @@ -207,11 +206,11 @@ public class DefaultSockJsService extends AbstractSockJsService { } } - transportHandler.handleRequest(request, response, handler, session); + transportHandler.handleRequest(request, response, webSocketHandler, session); } - public AbstractSockJsSession getSockJsSession(String sessionId, HandlerProvider> handler, - TransportHandler transportHandler) { + public AbstractSockJsSession getSockJsSession(String sessionId, + WebSocketHandler webSocketHandler, TransportHandler transportHandler) { AbstractSockJsSession session = this.sessions.get(sessionId); if (session != null) { @@ -230,7 +229,7 @@ public class DefaultSockJsService extends AbstractSockJsService { scheduleSessionTask(); } logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, handler); + session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, webSocketHandler); this.sessions.put(sessionId, session); return session; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index a90de74014..ebf38e4cd7 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -31,9 +31,7 @@ import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; import org.springframework.web.util.UrlPathHelper; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.SimpleHandlerProvider; /** * @author Rossen Stoyanchev @@ -45,7 +43,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { private final SockJsService sockJsService; - private final HandlerProvider> handlerProvider; + private final WebSocketHandler webSocketHandler; private final UrlPathHelper urlPathHelper = new UrlPathHelper(); @@ -57,37 +55,17 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { * that begins with the specified prefix will be handled by this service. In a * Servlet container this is the path within the current servlet mapping. */ - public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler handler) { + public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler webSocketHandler) { Assert.hasText(prefix, "prefix is required"); Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(handler, "webSocketHandler is required"); + Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.prefix = prefix; this.sockJsService = sockJsService; - this.handlerProvider = new SimpleHandlerProvider>(handler); + this.webSocketHandler = webSocketHandler; } - /** - * Class constructor with {@link SockJsHandler} type (per request) ... - * - * @param prefix the path prefix for the SockJS service. All requests with a path - * that begins with the specified prefix will be handled by this service. In a - * Servlet container this is the path within the current servlet mapping. - */ - public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, - HandlerProvider> handlerProvider) { - - Assert.hasText(prefix, "prefix is required"); - Assert.notNull(sockJsService, "sockJsService is required"); - Assert.notNull(handlerProvider, "handlerProvider is required"); - - this.prefix = prefix; - this.sockJsService = sockJsService; - this.handlerProvider = handlerProvider; - } - - public String getPrefix() { return this.prefix; } @@ -111,7 +89,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, this.handlerProvider); + this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, this.webSocketHandler); } catch (Exception ex) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 6da6af510e..832ba537ba 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -29,7 +29,6 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportHandler; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import com.fasterxml.jackson.databind.JsonMappingException; @@ -55,7 +54,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> webSocketHandler, AbstractSockJsSession session) + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { if (session == null) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index f7bfcfeeeb..917b71b655 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -28,9 +28,8 @@ import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.websocket.HandlerProvider; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.websocket.WebSocketHandler; /** @@ -58,7 +57,7 @@ public abstract class AbstractHttpSendingTransportHandler @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> webSocketHandler, AbstractSockJsSession session) + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { // Set content type before writing diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 0a59625bcd..d5ab1a09e9 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -26,11 +26,10 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.server.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; +import org.springframework.sockjs.server.TransportErrorException; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -50,10 +49,8 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock private ServerHttpResponse response; - public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - HandlerProvider> handler) { - - super(sessionId, sockJsConfig, handler); + public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + super(sessionId, config, handler); } public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response, diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index ec59fa8a26..07fcaa8123 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -25,7 +25,6 @@ import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -47,7 +46,7 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan } @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index e2fda86dbc..88dc77a1f1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -30,7 +30,6 @@ import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -81,7 +80,7 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle } @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index 8fed9533c6..d098148b09 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -29,7 +29,6 @@ import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.JavaScriptUtils; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -51,7 +50,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { + public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index 89aa292c14..b8dffe7e33 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -19,16 +19,13 @@ import java.io.IOException; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { - public PollingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - HandlerProvider> handler) { - - super(sessionId, sockJsConfig, handler); + public PollingServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + super(sessionId, config, handler); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index b8da4c33f0..5a79779be2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -25,7 +25,6 @@ import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -45,7 +44,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final SockJsConfiguration sockJsConfig; - private final HandlerProvider> handlerProvider; + private final WebSocketHandler webSocketHandler; private WebSocketServerSockJsSession sockJsSession; @@ -55,11 +54,11 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final ObjectMapper objectMapper = new ObjectMapper(); - public SockJsWebSocketHandler(SockJsConfiguration config, HandlerProvider> handlerProvider) { + public SockJsWebSocketHandler(SockJsConfiguration config, WebSocketHandler webSocketHandler) { Assert.notNull(config, "sockJsConfig is required"); - Assert.notNull(handlerProvider, "handlerProvider is required"); + Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.sockJsConfig = config; - this.handlerProvider = handlerProvider; + this.webSocketHandler = webSocketHandler; } protected SockJsConfiguration getSockJsConfig() { @@ -103,7 +102,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config) { - super(sessionId, config, SockJsWebSocketHandler.this.handlerProvider); + super(sessionId, config, SockJsWebSocketHandler.this.webSocketHandler); } public void initWebSocketSession(WebSocketSession wsSession) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index a5352e7f1c..8e147c3091 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -24,7 +24,6 @@ import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.SockJsFrame; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSession { @@ -32,10 +31,8 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio private int byteCount; - public StreamingServerSockJsSession(String sessionId, SockJsConfiguration sockJsConfig, - HandlerProvider> handler) { - - super(sessionId, sockJsConfig, handler); + public StreamingServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + super(sessionId, config, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 6d1b83955c..12554ff676 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -27,10 +27,8 @@ import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportHandler; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -65,11 +63,11 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler, AbstractSockJsSession session) throws TransportErrorException { + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { try { - WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, handler); - this.handshakeHandler.doHandshake(request, response, new SimpleHandlerProvider>(sockJsWrapper)); + WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler); + this.handshakeHandler.doHandshake(request, response, sockJsWrapper); } catch (Throwable t) { throw new TransportErrorException("Failed to start handshake request", t, session.getId()); @@ -79,8 +77,8 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, // HandshakeHandler methods @Override - public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler) throws IOException { + public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) + throws IOException { return this.handshakeHandler.doHandshake(request, response, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index 7c7709e0c2..b1640a1eb0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -23,7 +23,6 @@ import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -51,7 +50,7 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { + public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index ef31f69628..642b719dff 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -24,7 +24,6 @@ import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; import org.springframework.sockjs.server.SockJsFrame.FrameFormat; import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; @@ -48,7 +47,7 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public StreamingServerSockJsSession createSession(String sessionId, HandlerProvider> handler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java deleted file mode 100644 index 5158a669a3..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/HandlerProvider.java +++ /dev/null @@ -1,48 +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; - -/** - * A strategy for obtaining a handler instance that is scoped to external lifecycle events - * such as the opening and closing of a WebSocket connection. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public interface HandlerProvider { - - /** - * Whether the provided handler is a shared instance or not. - */ - boolean isSingleton(); - - /** - * The type of handler provided. - */ - Class getHandlerType(); - - /** - * Obtain the handler instance, either shared or created every time. - */ - T getHandler(); - - /** - * Callback to destroy a previously created handler instance if it is not shared. - */ - void destroy(T handler); - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java index ccfe4b4048..3bf095785e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -23,7 +23,6 @@ import org.eclipse.jetty.websocket.api.WebSocketListener; import org.springframework.util.Assert; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketMessage; @@ -39,43 +38,43 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { private static Log logger = LogFactory.getLog(JettyWebSocketListenerAdapter.class); - private final WebSocketHandler> handler; + private final WebSocketHandler> webSocketHandler; private WebSocketSession wsSession; - public JettyWebSocketListenerAdapter(HandlerProvider> provider) { - Assert.notNull(provider, "provider is required"); - this.handler = new WebSocketHandlerInvoker(provider).setLogger(logger); + public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler) { + Assert.notNull(webSocketHandler, "webSocketHandler is required"); + this.webSocketHandler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); } @Override public void onWebSocketConnect(Session session) { this.wsSession = new JettyWebSocketSessionAdapter(session); - this.handler.afterConnectionEstablished(this.wsSession); + this.webSocketHandler.afterConnectionEstablished(this.wsSession); } @Override public void onWebSocketClose(int statusCode, String reason) { CloseStatus closeStatus = new CloseStatus(statusCode, reason); - this.handler.afterConnectionClosed(this.wsSession, closeStatus); + this.webSocketHandler.afterConnectionClosed(this.wsSession, closeStatus); } @Override public void onWebSocketText(String payload) { TextMessage message = new TextMessage(payload); - this.handler.handleMessage(this.wsSession, message); + this.webSocketHandler.handleMessage(this.wsSession, message); } @Override public void onWebSocketBinary(byte[] payload, int offset, int len) { BinaryMessage message = new BinaryMessage(payload, offset, len); - this.handler.handleMessage(this.wsSession, message); + this.webSocketHandler.handleMessage(this.wsSession, message); } @Override public void onWebSocketError(Throwable cause) { - this.handler.handleTransportError(this.wsSession, cause); + this.webSocketHandler.handleTransportError(this.wsSession, cause); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java index a0f06d0c20..cb9047a79f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -28,11 +28,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.PartialMessageHandler; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; @@ -46,7 +44,7 @@ public class StandardEndpointAdapter extends Endpoint { private static Log logger = LogFactory.getLog(StandardEndpointAdapter.class); - private final WebSocketHandler> handler; + private final WebSocketHandlerInvoker handler; private final Class handlerClass; @@ -54,10 +52,10 @@ public class StandardEndpointAdapter extends Endpoint { - public StandardEndpointAdapter(HandlerProvider> provider) { - Assert.notNull(provider, "provider is required"); - this.handler = new WebSocketHandlerInvoker(provider).setLogger(logger); - this.handlerClass= provider.getHandlerType(); + public StandardEndpointAdapter(WebSocketHandler webSocketHandler) { + Assert.notNull(webSocketHandler, "webSocketHandler is required"); + this.handler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); + this.handlerClass= webSocketHandler.getClass(); } @@ -71,6 +69,8 @@ public class StandardEndpointAdapter extends Endpoint { } }); + // TODO: per-connection proxy + if (!PartialMessageHandler.class.isAssignableFrom(this.handlerClass)) { session.addMessageHandler(new MessageHandler.Whole() { @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java index 7e9dce31b0..555f119013 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java @@ -23,7 +23,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.GenericTypeResolver; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; @@ -41,18 +40,18 @@ public class WebSocketHandlerInvoker implements WebSocketHandler> handlerProvider; + private final WebSocketHandler handler; private final Class supportedMessageType; - private WebSocketHandler handler; - private final AtomicInteger sessionCount = new AtomicInteger(0); - public WebSocketHandlerInvoker(HandlerProvider> provider) { - this.handlerProvider = provider; - this.supportedMessageType = GenericTypeResolver.resolveTypeArgument(provider.getHandlerType(), WebSocketHandler.class); + public WebSocketHandlerInvoker(WebSocketHandler webSocketHandler) { + Assert.notNull(webSocketHandler, "webSocketHandler is required"); + this.handler = webSocketHandler; + Class handlerType = webSocketHandler.getClass(); + this.supportedMessageType = GenericTypeResolver.resolveTypeArgument(handlerType, WebSocketHandler.class); } public WebSocketHandlerInvoker setLogger(Log logger) { @@ -68,7 +67,6 @@ public class WebSocketHandlerInvoker implements WebSocketHandler message) { @@ -150,9 +134,6 @@ public class WebSocketHandlerInvoker implements WebSocketHandler> handler, - String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; - - WebSocketSession doHandshake(HandlerProvider> handler, HttpHeaders headers, URI uri) + WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index d633545cbe..bb3fb63f1e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -21,10 +21,8 @@ import java.util.List; import org.springframework.http.HttpHeaders; import org.springframework.util.CollectionUtils; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.support.SimpleHandlerProvider; /** * @author Rossen Stoyanchev @@ -34,7 +32,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag private final WebSocketClient client; - private final HandlerProvider> handlerProvider; + private final WebSocketHandler webSocketHandler; private WebSocketSession webSocketSession; @@ -46,18 +44,9 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag super(uriTemplate, uriVariables); this.client = webSocketClient; - this.handlerProvider = new SimpleHandlerProvider>(webSocketHandler); + this.webSocketHandler = webSocketHandler; } - public WebSocketConnectionManager(WebSocketClient webSocketClient, - HandlerProvider> handlerProvider, String uriTemplate, Object... uriVariables) { - - super(uriTemplate, uriVariables); - this.client = webSocketClient; - this.handlerProvider = handlerProvider; - } - - public void setSubProtocols(List subProtocols) { this.subProtocols.clear(); if (!CollectionUtils.isEmpty(subProtocols)) { @@ -73,7 +62,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag protected void openConnection() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setSecWebSocketProtocol(this.subProtocols); - this.webSocketSession = this.client.doHandshake(this.handlerProvider, headers, getUri()); + this.webSocketSession = this.client.doHandshake(this.webSocketHandler, headers, getUri()); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java index ce03fb798a..2674744013 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java @@ -21,9 +21,7 @@ import javax.websocket.Session; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.support.BeanCreatingHandlerProvider; -import org.springframework.websocket.support.SimpleHandlerProvider; /** * @author Rossen Stoyanchev @@ -32,30 +30,34 @@ import org.springframework.websocket.support.SimpleHandlerProvider; public class AnnotatedEndpointConnectionManager extends EndpointConnectionManagerSupport implements BeanFactoryAware { - private final HandlerProvider handlerProvider; + private final BeanCreatingHandlerProvider endpointProvider; + + private final Object endpoint; - public AnnotatedEndpointConnectionManager(Object endpointBean, String uriTemplate, Object... uriVariables) { + public AnnotatedEndpointConnectionManager(Object endpoint, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - this.handlerProvider = new SimpleHandlerProvider(endpointBean); + this.endpointProvider = null; + this.endpoint = endpoint; } public AnnotatedEndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); + this.endpointProvider = new BeanCreatingHandlerProvider(endpointClass); + this.endpoint = null; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (this.handlerProvider instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.handlerProvider).setBeanFactory(beanFactory); + if (this.endpointProvider != null) { + this.endpointProvider.setBeanFactory(beanFactory); } } @Override protected void openConnection() throws Exception { - Object endpoint = this.handlerProvider.getHandler(); + Object endpoint = (this.endpoint != null) ? this.endpoint : this.endpointProvider.getHandler(); Session session = getWebSocketContainer().connectToServer(endpoint, getUri()); updateSession(session); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java index 44162dacc5..3c2dcdefa7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java @@ -31,9 +31,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.support.BeanCreatingHandlerProvider; -import org.springframework.websocket.support.SimpleHandlerProvider; /** * @author Rossen Stoyanchev @@ -43,19 +41,23 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); - private final HandlerProvider handlerProvider; + private final BeanCreatingHandlerProvider endpointProvider; + + private final Endpoint endpoint; - public EndpointConnectionManager(Endpoint endpointBean, String uriTemplate, Object... uriVariables) { + public EndpointConnectionManager(Endpoint endpoint, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - Assert.notNull(endpointBean, "endpointBean is required"); - this.handlerProvider = new SimpleHandlerProvider(endpointBean); + Assert.notNull(endpoint, "endpoint is required"); + this.endpointProvider = null; + this.endpoint = endpoint; } public EndpointConnectionManager(Class endpointClass, String uriTemplate, Object... uriVars) { super(uriTemplate, uriVars); Assert.notNull(endpointClass, "endpointClass is required"); - this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); + this.endpointProvider = new BeanCreatingHandlerProvider(endpointClass); + this.endpoint = null; } @@ -81,14 +83,14 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (this.handlerProvider instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.handlerProvider).setBeanFactory(beanFactory); + if (this.endpointProvider != null) { + this.endpointProvider.setBeanFactory(beanFactory); } } @Override protected void openConnection() throws Exception { - Endpoint endpoint = this.handlerProvider.getHandler(); + Endpoint endpoint = (this.endpoint != null) ? this.endpoint : this.endpointProvider.getHandler(); ClientEndpointConfig endpointConfig = this.configBuilder.build(); Session session = getWebSocketContainer().connectToServer(endpoint, endpointConfig, getUri()); updateSession(session); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index 0b9340807c..cadd72cb7c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -32,14 +32,12 @@ import javax.websocket.WebSocketContainer; import org.springframework.http.HttpHeaders; import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter; import org.springframework.websocket.adapter.StandardEndpointAdapter; +import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter; import org.springframework.websocket.client.WebSocketClient; import org.springframework.websocket.client.WebSocketConnectFailureException; -import org.springframework.websocket.support.SimpleHandlerProvider; /** * A standard Java {@link WebSocketClient}. @@ -61,24 +59,18 @@ public class StandardWebSocketClient implements WebSocketClient { } @Override - public WebSocketSession doHandshake(WebSocketHandler handler, String uriTemplate, Object... uriVariables) + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { - return doHandshake(new SimpleHandlerProvider>(handler), uriTemplate, uriVariables); - } - - public WebSocketSession doHandshake(HandlerProvider> handler, - String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { - URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); - return doHandshake(handler, null, uri); + return doHandshake(webSocketHandler, null, uri); } @Override - public WebSocketSession doHandshake(HandlerProvider> handler, + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { - Endpoint endpoint = new StandardEndpointAdapter(handler); + Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler); ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create(); if (httpHeaders != null) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 46b043fd57..9995881883 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -35,7 +35,6 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -88,7 +87,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler) throws IOException { + WebSocketHandler webSocketHandler) throws IOException { logger.debug("Starting handshake for " + request.getURI()); @@ -136,10 +135,10 @@ public class DefaultHandshakeHandler implements HandshakeHandler { response.flush(); if (logger.isTraceEnabled()) { - logger.trace("Upgrading with " + handler); + logger.trace("Upgrading with " + webSocketHandler); } - this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, handler); + this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, webSocketHandler); return true; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 532dd65501..368568d6b0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -20,7 +20,6 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -33,6 +32,6 @@ public interface HandshakeHandler { boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - HandlerProvider> handler) throws IOException; + WebSocketHandler webSocketHandler) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index 773d5863f7..c99835165e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -20,7 +20,6 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; /** @@ -44,7 +43,6 @@ public interface RequestUpgradeStrategy { * @param handler the handler for WebSocket messages */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - HandlerProvider> handlerProvider) throws IOException; - // FIXME how to indicate failure to upgrade? + WebSocketHandler webSocketHandler) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java index ebde513034..628243166e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java @@ -33,9 +33,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.support.BeanCreatingHandlerProvider; -import org.springframework.websocket.support.SimpleHandlerProvider; /** @@ -53,7 +51,9 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw private final String path; - private final HandlerProvider handlerProvider; + private final BeanCreatingHandlerProvider endpointProvider; + + private final Endpoint endpoint; private List> encoders = new ArrayList>(); @@ -70,7 +70,6 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw /** * Class constructor with the {@code javax.webscoket.Endpoint} class. - * TODO * * @param path * @param endpointClass @@ -79,14 +78,16 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw Assert.hasText(path, "path must not be empty"); Assert.notNull(endpointClass, "endpointClass is required"); this.path = path; - this.handlerProvider = new BeanCreatingHandlerProvider(endpointClass); + this.endpointProvider = new BeanCreatingHandlerProvider(endpointClass); + this.endpoint = null; } - public EndpointRegistration(String path, Endpoint endpointBean) { + public EndpointRegistration(String path, Endpoint endpoint) { Assert.hasText(path, "path must not be empty"); - Assert.notNull(endpointBean, "endpointBean is required"); + Assert.notNull(endpoint, "endpoint is required"); this.path = path; - this.handlerProvider = new SimpleHandlerProvider(endpointBean); + this.endpointProvider = null; + this.endpoint = endpoint; } @@ -96,13 +97,13 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw } @Override - @SuppressWarnings("unchecked") public Class getEndpointClass() { - return (Class) this.handlerProvider.getHandlerType(); + return (this.endpoint != null) ? + this.endpoint.getClass() : ((Class) this.endpointProvider.getHandlerType()); } public Endpoint getEndpoint() { - return this.handlerProvider.getHandler(); + return (this.endpoint != null) ? this.endpoint : this.endpointProvider.getHandler(); } public void setSubprotocols(List subprotocols) { @@ -115,7 +116,6 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw } public void setExtensions(List extensions) { - // TODO: verify against ServerContainer.getInstalledExtensions() this.extensions = extensions; } @@ -188,8 +188,8 @@ public class EndpointRegistration implements ServerEndpointConfig, BeanFactoryAw @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - if (this.handlerProvider instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.handlerProvider).setBeanFactory(beanFactory); + if (this.endpointProvider != null) { + this.endpointProvider.setBeanFactory(beanFactory); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index cd34bfdfc7..71e2d5fbaf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -24,7 +24,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.adapter.StandardEndpointAdapter; import org.springframework.websocket.server.RequestUpgradeStrategy; @@ -43,9 +42,9 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, HandlerProvider> handler) throws IOException { + String protocol, WebSocketHandler webSocketHandler) throws IOException { - upgradeInternal(request, response, protocol, new StandardEndpointAdapter(handler)); + upgradeInternal(request, response, protocol, new StandardEndpointAdapter(webSocketHandler)); } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 7448e66ac7..7704196f6f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -32,7 +32,6 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter; import org.springframework.websocket.server.RequestUpgradeStrategy; @@ -63,14 +62,12 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { this.factory = new WebSocketServerFactory(); this.factory.setCreator(new WebSocketCreator() { @Override - @SuppressWarnings("unchecked") public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) { Assert.isInstanceOf(ServletWebSocketRequest.class, request); ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) request; - HandlerProvider> handlerProvider = - (HandlerProvider>) servletRequest.getServletAttributes().get( - HANDLER_PROVIDER_ATTR_NAME); - return new JettyWebSocketListenerAdapter(handlerProvider); + WebSocketHandler webSocketHandler = + (WebSocketHandler) servletRequest.getServletAttributes().get(HANDLER_PROVIDER_ATTR_NAME); + return new JettyWebSocketListenerAdapter(webSocketHandler); } }); try { @@ -89,7 +86,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, HandlerProvider> handlerProvider) throws IOException { + String selectedProtocol, WebSocketHandler webSocketHandler) throws IOException { Assert.isInstanceOf(ServletServerHttpRequest.class, request); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -97,16 +94,16 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { Assert.isInstanceOf(ServletServerHttpResponse.class, response); HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); - upgrade(servletRequest, servletResponse, selectedProtocol, handlerProvider); + upgrade(servletRequest, servletResponse, selectedProtocol, webSocketHandler); } private void upgrade(HttpServletRequest request, HttpServletResponse response, - String selectedProtocol, final HandlerProvider> handlerProvider) throws IOException { + String selectedProtocol, final WebSocketHandler webSocketHandler) throws IOException { Assert.state(this.factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); Assert.state(this.factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); - request.setAttribute(HANDLER_PROVIDER_ATTR_NAME, handlerProvider); + request.setAttribute(HANDLER_PROVIDER_ATTR_NAME, webSocketHandler); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 6d8edc0c28..eb82dd6b7c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -29,11 +29,9 @@ import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; -import org.springframework.websocket.HandlerProvider; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.support.SimpleHandlerProvider; /** * An {@link HttpRequestHandler} that wraps the invocation of a {@link HandshakeHandler}. @@ -45,22 +43,17 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { private final HandshakeHandler handshakeHandler; - private final HandlerProvider> handlerProvider; + private final WebSocketHandler webSocketHandler; public WebSocketHttpRequestHandler(WebSocketHandler webSocketHandler) { - this(new SimpleHandlerProvider>(webSocketHandler)); + this(webSocketHandler, new DefaultHandshakeHandler()); } - public WebSocketHttpRequestHandler( HandlerProvider> handlerProvider) { - this(handlerProvider, new DefaultHandshakeHandler()); - } - - public WebSocketHttpRequestHandler( HandlerProvider> handlerProvider, - HandshakeHandler handshakeHandler) { - Assert.notNull(handlerProvider, "handlerProvider is required"); + public WebSocketHttpRequestHandler( WebSocketHandler webSocketHandler, HandshakeHandler handshakeHandler) { + Assert.notNull(webSocketHandler, "webSocketHandler is required"); Assert.notNull(handshakeHandler, "handshakeHandler is required"); - this.handlerProvider = handlerProvider; + this.webSocketHandler = webSocketHandler; this.handshakeHandler = new DefaultHandshakeHandler(); } @@ -72,7 +65,7 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); try { - this.handshakeHandler.doHandshake(httpRequest, httpResponse, this.handlerProvider); + this.handshakeHandler.doHandshake(httpRequest, httpResponse, this.webSocketHandler); } catch (Exception e) { // TODO diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java index 858b440d2d..c4c5b1b7da 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java @@ -24,16 +24,13 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.util.Assert; -import org.springframework.websocket.HandlerProvider; /** - * A {@link HandlerProvider} that uses {@link AutowireCapableBeanFactory#createBean(Class) - * creating a fresh instance every time #getHandler() is called. * * @author Rossen Stoyanchev * @since 4.0 */ -public class BeanCreatingHandlerProvider implements HandlerProvider, BeanFactoryAware { +public class BeanCreatingHandlerProvider implements BeanFactoryAware { private static final Log logger = LogFactory.getLog(BeanCreatingHandlerProvider.class); @@ -76,7 +73,6 @@ public class BeanCreatingHandlerProvider implements HandlerProvider, BeanF } } - @Override public void destroy(T handler) { if (this.beanFactory != null) { if (logger.isTraceEnabled()) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java new file mode 100644 index 0000000000..01e259fb9b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java @@ -0,0 +1,132 @@ +/* + * 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.support; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +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.core.GenericTypeResolver; +import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + +/** + * A {@link WebSocketHandler} that initializes and destroys a {@link WebSocketHandler} + * instance for each WebSocket connection and delegates all other methods to it. + * + *

+ * Essentially create an instance of this class once, providing the type of + * {@link WebSocketHandler} class to create for each connection, and then pass it to any + * API method that expects a {@link WebSocketHandler}. + * + *

+ * If initializing the target {@link WebSocketHandler} type requires a Spring BeanFctory, + * then the {@link #setBeanFactory(BeanFactory)} property accordingly. Simply declaring + * this class as a Spring bean will do that. Otherwise, {@link WebSocketHandler} instances + * of the target type will be created using the default constructor. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler>, BeanFactoryAware { + + private Log logger = LogFactory.getLog(PerConnectionWebSocketHandlerProxy.class); + + private final BeanCreatingHandlerProvider> provider; + + private Map> handlers = + new ConcurrentHashMap>(); + + private final Class supportedMessageType; + + + public PerConnectionWebSocketHandlerProxy(Class> handlerType) { + this.provider = new BeanCreatingHandlerProvider>(handlerType); + this.supportedMessageType = GenericTypeResolver.resolveTypeArgument(handlerType, WebSocketHandler.class); + } + + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.provider.setBeanFactory(beanFactory); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + WebSocketHandler handler = this.provider.getHandler(); + this.handlers.put(session, handler); + handler.afterConnectionEstablished(session); + } + + @SuppressWarnings("unchecked") + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + if (!this.supportedMessageType.isAssignableFrom(message.getClass())) { + try { + session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Message type not supported")); + } + catch (IOException e) { + destroy(session); + } + } + else { + ((WebSocketHandler) getHandler(session)).handleMessage(session, message); + } + } + + private WebSocketHandler getHandler(WebSocketSession session) { + WebSocketHandler handler = this.handlers.get(session); + Assert.isTrue(handler != null, "WebSocketHandler not found for " + session); + return handler; + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + getHandler(session).handleTransportError(session, exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + try { + getHandler(session).afterConnectionClosed(session, closeStatus); + } + finally { + destroy(session); + } + } + + private void destroy(WebSocketSession session) { + WebSocketHandler handler = this.handlers.remove(session); + try { + if (handler != null) { + this.provider.destroy(handler); + } + } + catch (Throwable t) { + logger.warn("Error while destroying handler", t); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java deleted file mode 100644 index aa9598dc2e..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/SimpleHandlerProvider.java +++ /dev/null @@ -1,62 +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.support; - -import org.springframework.util.ClassUtils; -import org.springframework.websocket.HandlerProvider; - -/** - * A {@link HandlerProvider} that returns a singleton instance. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class SimpleHandlerProvider implements HandlerProvider { - - private final T handler; - - - public SimpleHandlerProvider(T handler) { - this.handler = handler; - } - - - @Override - public boolean isSingleton() { - return true; - } - - @Override - public Class getHandlerType() { - return ClassUtils.getUserClass(this.handler); - } - - @Override - public T getHandler() { - return this.handler; - } - - @Override - public void destroy(T handler) { - } - - @Override - public String toString() { - return "SimpleHandlerProvider [handler=" + handler + "]"; - } - -} From f347988428b50286ab9d5a7d19553917def76883 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sat, 27 Apr 2013 05:56:46 -0400 Subject: [PATCH 41/51] Remove parameterized type from WebSocketHandler --- .../sockjs/AbstractSockJsSession.java | 2 +- .../sockjs/SockJsSessionFactory.java | 2 +- .../server/AbstractServerSockJsSession.java | 2 +- .../sockjs/server/AbstractSockJsService.java | 6 +-- .../sockjs/server/SockJsService.java | 2 +- .../sockjs/server/TransportHandler.java | 2 +- .../server/support/DefaultSockJsService.java | 6 +-- .../support/SockJsHttpRequestHandler.java | 2 +- ...AbstractHttpReceivingTransportHandler.java | 2 +- .../AbstractHttpSendingTransportHandler.java | 2 +- .../AbstractHttpServerSockJsSession.java | 2 +- .../EventSourceTransportHandler.java | 2 +- .../transport/HtmlFileTransportHandler.java | 2 +- .../JsonpPollingTransportHandler.java | 2 +- .../transport/PollingServerSockJsSession.java | 2 +- .../transport/SockJsWebSocketHandler.java | 6 +-- .../StreamingServerSockJsSession.java | 2 +- .../transport/WebSocketTransportHandler.java | 6 +-- .../transport/XhrPollingTransportHandler.java | 2 +- .../XhrStreamingTransportHandler.java | 2 +- .../websocket/WebSocketHandler.java | 4 +- .../BinaryWebSocketHandlerAdapter.java | 27 ++++++------- .../JettyWebSocketListenerAdapter.java | 5 +-- .../adapter/StandardEndpointAdapter.java | 2 +- .../adapter/TextWebSocketHandlerAdapter.java | 27 ++++++------- .../adapter/WebSocketHandlerAdapter.java | 3 +- .../adapter/WebSocketHandlerInvoker.java | 22 +++-------- .../websocket/client/WebSocketClient.java | 2 +- .../client/WebSocketConnectionManager.java | 2 +- .../endpoint/StandardWebSocketClient.java | 2 +- .../server/DefaultHandshakeHandler.java | 2 +- .../websocket/server/HandshakeHandler.java | 4 +- .../server/RequestUpgradeStrategy.java | 4 +- .../AbstractEndpointUpgradeStrategy.java | 2 +- .../support/JettyRequestUpgradeStrategy.java | 8 ++-- .../support/WebSocketHttpRequestHandler.java | 4 +- .../PerConnectionWebSocketHandlerProxy.java | 38 ++++++------------- 37 files changed, 90 insertions(+), 124 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index c7dd7a4875..8c8a28406b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -55,7 +55,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * @param sessionId * @param webSocketHandler the recipient of SockJS messages */ - public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) { + public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(sessionId, "sessionId is required"); Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.sessionId = sessionId; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index c9bc94519c..977cc9f0a3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -34,6 +34,6 @@ public interface SockJsSessionFactory{ * @param webSocketHandler the underlying {@link WebSocketHandler} * @return a new non-null session */ - S createSession(String sessionId, WebSocketHandler webSocketHandler); + S createSession(String sessionId, WebSocketHandler webSocketHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index f9e57a9487..fdd9f15330 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -43,7 +43,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession private ScheduledFuture heartbeatTask; - public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + public AbstractServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { super(sessionId, handler); this.sockJsConfig = config; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 6e0504847b..16fdfc4214 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -199,7 +199,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * @throws Exception */ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException { + String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException { logger.debug(request.getMethod() + " [" + sockJsPath + "]"); @@ -253,10 +253,10 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf } protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, - ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException; + ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException; protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) + String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java index 580d2afec6..91c8a3d52e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java @@ -30,6 +30,6 @@ public interface SockJsService { void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException; + String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java index af05a34357..e01af545be 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java @@ -30,6 +30,6 @@ public interface TransportHandler { TransportType getTransportType(); void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler handler, AbstractSockJsSession session) throws TransportErrorException; + WebSocketHandler handler, AbstractSockJsSession session) throws TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index f070c1b94d..92dddbed59 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -142,7 +142,7 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws IOException { + WebSocketHandler webSocketHandler) throws IOException { if (isWebSocketEnabled()) { TransportHandler transportHandler = this.transportHandlers.get(TransportType.WEBSOCKET); @@ -159,7 +159,7 @@ public class DefaultSockJsService extends AbstractSockJsService { @Override protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response, - String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) + String sessionId, TransportType transportType, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException { TransportHandler transportHandler = this.transportHandlers.get(transportType); @@ -210,7 +210,7 @@ public class DefaultSockJsService extends AbstractSockJsService { } public AbstractSockJsSession getSockJsSession(String sessionId, - WebSocketHandler webSocketHandler, TransportHandler transportHandler) { + WebSocketHandler webSocketHandler, TransportHandler transportHandler) { AbstractSockJsSession session = this.sessions.get(sessionId); if (session != null) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index ebf38e4cd7..3c90f9db6d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -43,7 +43,7 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { private final SockJsService sockJsService; - private final WebSocketHandler webSocketHandler; + private final WebSocketHandler webSocketHandler; private final UrlPathHelper urlPathHelper = new UrlPathHelper(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 832ba537ba..0f8d492eb1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -54,7 +54,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { if (session == null) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index 917b71b655..f0c92e72f8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -57,7 +57,7 @@ public abstract class AbstractHttpSendingTransportHandler @Override public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { // Set content type before writing diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index d5ab1a09e9..055102b681 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -49,7 +49,7 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock private ServerHttpResponse response; - public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + public AbstractHttpServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { super(sessionId, config, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java index 07fcaa8123..bae88528b8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java @@ -46,7 +46,7 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan } @Override - public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { @Override diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java index 88dc77a1f1..7070a88fea 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java @@ -80,7 +80,7 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle } @Override - public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java index d098148b09..fde554f0d1 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java @@ -50,7 +50,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { + public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java index b8dffe7e33..7a70a5791f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java @@ -24,7 +24,7 @@ import org.springframework.websocket.WebSocketHandler; public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { - public PollingServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + public PollingServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { super(sessionId, config, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 5a79779be2..13a0b9e12e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -44,7 +44,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final SockJsConfiguration sockJsConfig; - private final WebSocketHandler webSocketHandler; + private final WebSocketHandler webSocketHandler; private WebSocketServerSockJsSession sockJsSession; @@ -54,7 +54,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final ObjectMapper objectMapper = new ObjectMapper(); - public SockJsWebSocketHandler(SockJsConfiguration config, WebSocketHandler webSocketHandler) { + public SockJsWebSocketHandler(SockJsConfiguration config, WebSocketHandler webSocketHandler) { Assert.notNull(config, "sockJsConfig is required"); Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.sockJsConfig = config; @@ -73,7 +73,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { } @Override - public void handleMessage(WebSocketSession wsSession, TextMessage message) { + public void handleTextMessage(WebSocketSession wsSession, TextMessage message) { this.sockJsSession.handleMessage(message, wsSession); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java index 8e147c3091..bc5be9cab8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java @@ -31,7 +31,7 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio private int byteCount; - public StreamingServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + public StreamingServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { super(sessionId, config, handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 12554ff676..81d82c4833 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -63,10 +63,10 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { + WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { try { - WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler); + WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler); this.handshakeHandler.doHandshake(request, response, sockJsWrapper); } catch (Throwable t) { @@ -77,7 +77,7 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, // HandshakeHandler methods @Override - public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) + public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) throws IOException { return this.handshakeHandler.doHandshake(request, response, handler); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java index b1640a1eb0..b25818c67f 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java @@ -50,7 +50,7 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand return new DefaultFrameFormat("%s\n"); } - public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { + public PollingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new PollingServerSockJsSession(sessionId, getSockJsConfig(), handler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java index 642b719dff..84228029ab 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java @@ -47,7 +47,7 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa } @Override - public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { + public StreamingServerSockJsSession createSession(String sessionId, WebSocketHandler handler) { Assert.notNull(getSockJsConfig(), "This transport requires SockJsConfiguration"); return new StreamingServerSockJsSession(sessionId, getSockJsConfig(), handler) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 3b0b9476f9..1ea26cd3fe 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -26,7 +26,7 @@ package org.springframework.websocket; * @author Phillip Webb * @since 4.0 */ -public interface WebSocketHandler> { +public interface WebSocketHandler { /** * A new WebSocket connection has been opened and is ready to be used. @@ -36,7 +36,7 @@ public interface WebSocketHandler> { /** * Handle an incoming WebSocket message. */ - void handleMessage(WebSocketSession session, T message); + void handleMessage(WebSocketSession session, WebSocketMessage message); /** * Handle an error from the underlying WebSocket message transport. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java index d136dc9012..7e467593e9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java @@ -16,8 +16,10 @@ package org.springframework.websocket.adapter; -import org.springframework.websocket.BinaryMessage; +import java.io.IOException; + import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -29,22 +31,17 @@ import org.springframework.websocket.WebSocketSession; * @author Phillip Webb * @since 4.0 */ -public class BinaryWebSocketHandlerAdapter implements WebSocketHandler { +public class BinaryWebSocketHandlerAdapter extends WebSocketHandlerAdapter { + @Override - public void afterConnectionEstablished(WebSocketSession session) { - } - - @Override - public void handleMessage(WebSocketSession session, BinaryMessage message) { - } - - @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + try { + session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Text messages not supported")); + } + catch (IOException e) { + // ignore + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java index 3bf095785e..d8aa4f2d13 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -25,7 +25,6 @@ import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; import org.springframework.websocket.WebSocketSession; /** @@ -38,12 +37,12 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { private static Log logger = LogFactory.getLog(JettyWebSocketListenerAdapter.class); - private final WebSocketHandler> webSocketHandler; + private final WebSocketHandler webSocketHandler; private WebSocketSession wsSession; - public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler) { + public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.webSocketHandler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java index cb9047a79f..c076eebe42 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -52,7 +52,7 @@ public class StandardEndpointAdapter extends Endpoint { - public StandardEndpointAdapter(WebSocketHandler webSocketHandler) { + public StandardEndpointAdapter(WebSocketHandler webSocketHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.handler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); this.handlerClass= webSocketHandler.getClass(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java index 6c54f653e8..6fe21f7725 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java @@ -16,8 +16,10 @@ package org.springframework.websocket.adapter; +import java.io.IOException; + +import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -29,22 +31,17 @@ import org.springframework.websocket.WebSocketSession; * @author Phillip Webb * @since 4.0 */ -public class TextWebSocketHandlerAdapter implements WebSocketHandler { +public class TextWebSocketHandlerAdapter extends WebSocketHandlerAdapter { + @Override - public void afterConnectionEstablished(WebSocketSession session) { - } - - @Override - public void handleMessage(WebSocketSession session, TextMessage message) { - } - - @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { + try { + session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Binary messages not supported")); + } + catch (IOException e) { + // ignore + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java index 8a4c64f630..a9b464f015 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java @@ -34,7 +34,7 @@ import org.springframework.websocket.WebSocketSession; * @see TextWebSocketHandlerAdapter * @see BinaryWebSocketHandlerAdapter */ -public class WebSocketHandlerAdapter implements WebSocketHandler> { +public class WebSocketHandlerAdapter implements WebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession session) { @@ -49,6 +49,7 @@ public class WebSocketHandlerAdapter implements WebSocketHandler> { +public class WebSocketHandlerInvoker implements WebSocketHandler { private Log logger = LogFactory.getLog(WebSocketHandlerInvoker.class); - private final WebSocketHandler handler; - - private final Class supportedMessageType; + private final WebSocketHandler handler; private final AtomicInteger sessionCount = new AtomicInteger(0); - public WebSocketHandlerInvoker(WebSocketHandler webSocketHandler) { + public WebSocketHandlerInvoker(WebSocketHandler webSocketHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.handler = webSocketHandler; - Class handlerType = webSocketHandler.getClass(); - this.supportedMessageType = GenericTypeResolver.resolveTypeArgument(handlerType, WebSocketHandler.class); } public WebSocketHandlerInvoker setLogger(Log logger) { @@ -79,9 +74,7 @@ public class WebSocketHandlerInvoker implements WebSocketHandler message) { if (logger.isTraceEnabled()) { logger.trace("Received " + message + ", " + session); } - if (!this.supportedMessageType.isAssignableFrom(message.getClass())) { - tryCloseWithError(session, null, CloseStatus.NOT_ACCEPTABLE.withReason("Message type not supported")); - return; - } try { - ((WebSocketHandler) this.handler).handleMessage(session, message); + this.handler.handleMessage(session, message); } catch (Throwable ex) { tryCloseWithError(session,ex); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java index e1d3dc6eb0..5aaa0380f9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -38,7 +38,7 @@ public interface WebSocketClient { WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException; - WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) + WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index bb3fb63f1e..0e5090e584 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -32,7 +32,7 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag private final WebSocketClient client; - private final WebSocketHandler webSocketHandler; + private final WebSocketHandler webSocketHandler; private WebSocketSession webSocketSession; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index cadd72cb7c..ffb043c340 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -67,7 +67,7 @@ public class StandardWebSocketClient implements WebSocketClient { } @Override - public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 9995881883..e557a0cc12 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -87,7 +87,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws IOException { + WebSocketHandler webSocketHandler) throws IOException { logger.debug("Starting handshake for " + request.getURI()); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index 368568d6b0..fbdf2ca1a6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -31,7 +31,7 @@ import org.springframework.websocket.WebSocketHandler; public interface HandshakeHandler { - boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws IOException; + boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) + throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index c99835165e..70a7653af4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -40,9 +40,9 @@ public interface RequestUpgradeStrategy { * Perform runtime specific steps to complete the upgrade. * Invoked only if the handshake is successful. * - * @param handler the handler for WebSocket messages + * @param webSocketHandler the handler for WebSocket messages */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - WebSocketHandler webSocketHandler) throws IOException; + WebSocketHandler webSocketHandler) throws IOException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index 71e2d5fbaf..096ab251e8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -42,7 +42,7 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, WebSocketHandler webSocketHandler) throws IOException { + String protocol, WebSocketHandler webSocketHandler) throws IOException { upgradeInternal(request, response, protocol, new StandardEndpointAdapter(webSocketHandler)); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 7704196f6f..9819a63991 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -65,8 +65,8 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) { Assert.isInstanceOf(ServletWebSocketRequest.class, request); ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) request; - WebSocketHandler webSocketHandler = - (WebSocketHandler) servletRequest.getServletAttributes().get(HANDLER_PROVIDER_ATTR_NAME); + WebSocketHandler webSocketHandler = + (WebSocketHandler) servletRequest.getServletAttributes().get(HANDLER_PROVIDER_ATTR_NAME); return new JettyWebSocketListenerAdapter(webSocketHandler); } }); @@ -86,7 +86,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, WebSocketHandler webSocketHandler) throws IOException { + String selectedProtocol, WebSocketHandler webSocketHandler) throws IOException { Assert.isInstanceOf(ServletServerHttpRequest.class, request); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -98,7 +98,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { } private void upgrade(HttpServletRequest request, HttpServletResponse response, - String selectedProtocol, final WebSocketHandler webSocketHandler) throws IOException { + String selectedProtocol, final WebSocketHandler webSocketHandler) throws IOException { Assert.state(this.factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); Assert.state(this.factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index eb82dd6b7c..5d3b815810 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -43,14 +43,14 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { private final HandshakeHandler handshakeHandler; - private final WebSocketHandler webSocketHandler; + private final WebSocketHandler webSocketHandler; public WebSocketHttpRequestHandler(WebSocketHandler webSocketHandler) { this(webSocketHandler, new DefaultHandshakeHandler()); } - public WebSocketHttpRequestHandler( WebSocketHandler webSocketHandler, HandshakeHandler handshakeHandler) { + public WebSocketHttpRequestHandler( WebSocketHandler webSocketHandler, HandshakeHandler handshakeHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); Assert.notNull(handshakeHandler, "handshakeHandler is required"); this.webSocketHandler = webSocketHandler; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java index 01e259fb9b..ee4091178f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java @@ -16,7 +16,6 @@ package org.springframework.websocket.support; -import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -25,7 +24,6 @@ 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.core.GenericTypeResolver; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.WebSocketHandler; @@ -50,21 +48,18 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler>, BeanFactoryAware { +public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler, BeanFactoryAware { private Log logger = LogFactory.getLog(PerConnectionWebSocketHandlerProxy.class); - private final BeanCreatingHandlerProvider> provider; + private final BeanCreatingHandlerProvider provider; - private Map> handlers = - new ConcurrentHashMap>(); - - private final Class supportedMessageType; + private Map handlers = + new ConcurrentHashMap(); - public PerConnectionWebSocketHandlerProxy(Class> handlerType) { - this.provider = new BeanCreatingHandlerProvider>(handlerType); - this.supportedMessageType = GenericTypeResolver.resolveTypeArgument(handlerType, WebSocketHandler.class); + public PerConnectionWebSocketHandlerProxy(Class handlerType) { + this.provider = new BeanCreatingHandlerProvider(handlerType); } @@ -75,29 +70,18 @@ public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler handler = this.provider.getHandler(); + WebSocketHandler handler = this.provider.getHandler(); this.handlers.put(session, handler); handler.afterConnectionEstablished(session); } - @SuppressWarnings("unchecked") @Override public void handleMessage(WebSocketSession session, WebSocketMessage message) { - if (!this.supportedMessageType.isAssignableFrom(message.getClass())) { - try { - session.close(CloseStatus.NOT_ACCEPTABLE.withReason("Message type not supported")); - } - catch (IOException e) { - destroy(session); - } - } - else { - ((WebSocketHandler) getHandler(session)).handleMessage(session, message); - } + getHandler(session).handleMessage(session, message); } - private WebSocketHandler getHandler(WebSocketSession session) { - WebSocketHandler handler = this.handlers.get(session); + private WebSocketHandler getHandler(WebSocketSession session) { + WebSocketHandler handler = this.handlers.get(session); Assert.isTrue(handler != null, "WebSocketHandler not found for " + session); return handler; } @@ -118,7 +102,7 @@ public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler handler = this.handlers.remove(session); + WebSocketHandler handler = this.handlers.remove(session); try { if (handler != null) { this.provider.destroy(handler); From ba87743087d12306ea3d74b4f50d13a13bed3614 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 29 Apr 2013 10:17:57 -0400 Subject: [PATCH 42/51] Polish (minor) --- .../adapter/WebSocketHandlerInvoker.java | 1 - .../support/BeanCreatingHandlerProvider.java | 22 ++++++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java index d67bce9c07..f2090b5d6c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java @@ -61,7 +61,6 @@ public class WebSocketHandlerInvoker implements WebSocketHandler { } try { Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected new session"); - this.handler.afterConnectionEstablished(session); } catch (Throwable ex) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java index c4c5b1b7da..4ed2092447 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java @@ -34,14 +34,14 @@ public class BeanCreatingHandlerProvider implements BeanFactoryAware { private static final Log logger = LogFactory.getLog(BeanCreatingHandlerProvider.class); - private final Class handlerClass; + private final Class handlerType; private AutowireCapableBeanFactory beanFactory; - public BeanCreatingHandlerProvider(Class handlerClass) { - Assert.notNull(handlerClass, "handlerClass is required"); - this.handlerClass = handlerClass; + public BeanCreatingHandlerProvider(Class handlerType) { + Assert.notNull(handlerType, "handlerType is required"); + this.handlerType = handlerType; } @@ -52,24 +52,20 @@ public class BeanCreatingHandlerProvider implements BeanFactoryAware { } } - public boolean isSingleton() { - return false; - } - public Class getHandlerType() { - return this.handlerClass; + return this.handlerType; } public T getHandler() { if (logger.isTraceEnabled()) { - logger.trace("Creating instance for handler type " + this.handlerClass); + logger.trace("Creating instance for handler type " + this.handlerType); } if (this.beanFactory == null) { logger.warn("No BeanFactory available, attempting to use default constructor"); - return BeanUtils.instantiate(this.handlerClass); + return BeanUtils.instantiate(this.handlerType); } else { - return this.beanFactory.createBean(this.handlerClass); + return this.beanFactory.createBean(this.handlerType); } } @@ -84,7 +80,7 @@ public class BeanCreatingHandlerProvider implements BeanFactoryAware { @Override public String toString() { - return "BeanCreatingHandlerProvider [handlerClass=" + handlerClass + "]"; + return "BeanCreatingHandlerProvider [handlerClass=" + handlerType + "]"; } } From f45ef75f95be581275a6a6b0923cee9b26a8917e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 29 Apr 2013 22:36:20 -0400 Subject: [PATCH 43/51] Add WebSocketHandlerDecorator --- .../sockjs/AbstractSockJsSession.java | 13 +- .../support/SockJsHttpRequestHandler.java | 15 ++- .../websocket/WebSocketHandler.java | 5 + .../JettyWebSocketListenerAdapter.java | 7 +- .../adapter/StandardEndpointAdapter.java | 22 +-- .../adapter/WebSocketHandlerAdapter.java | 5 + .../adapter/WebSocketHandlerInvoker.java | 126 ------------------ .../client/WebSocketConnectionManager.java | 15 ++- .../support/WebSocketHttpRequestHandler.java | 15 ++- .../ExceptionWebSocketHandlerDecorator.java | 92 +++++++++++++ .../LoggingWebSocketHandlerDecorator.java | 73 ++++++++++ .../PerConnectionWebSocketHandlerProxy.java | 14 +- .../support/WebSocketHandlerDecorator.java | 70 ++++++++++ 13 files changed, 317 insertions(+), 155 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 8c8a28406b..3912dcb268 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -26,7 +26,6 @@ import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.adapter.WebSocketHandlerInvoker; /** @@ -42,7 +41,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { private final String sessionId; - private WebSocketHandlerInvoker handler; + private WebSocketHandler handler; private State state = State.NEW; @@ -59,7 +58,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { Assert.notNull(sessionId, "sessionId is required"); Assert.notNull(webSocketHandler, "webSocketHandler is required"); this.sessionId = sessionId; - this.handler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); + this.handler = webSocketHandler; } public String getId() { @@ -129,7 +128,13 @@ public abstract class AbstractSockJsSession implements WebSocketSession { */ protected void tryCloseWithSockJsTransportError(Throwable ex, CloseStatus closeStatus) { delegateError(ex); - this.handler.tryCloseWithError(this, ex, closeStatus); + try { + logger.error("Closing due to transport error for " + this, ex); + close(closeStatus); + } + catch (Throwable t) { + // ignore + } } public void delegateMessages(String[] messages) { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java index 3c90f9db6d..241e550a38 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java @@ -32,6 +32,8 @@ import org.springframework.web.HttpRequestHandler; import org.springframework.web.util.NestedServletException; import org.springframework.web.util.UrlPathHelper; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; /** * @author Rossen Stoyanchev @@ -63,7 +65,18 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { this.prefix = prefix; this.sockJsService = sockJsService; - this.webSocketHandler = webSocketHandler; + this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); + } + + /** + * Decorate the WebSocketHandler provided to the class constructor. + *

+ * By default {@link ExceptionWebSocketHandlerDecorator} and + * {@link LoggingWebSocketHandlerDecorator} are applied are added. + */ + protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) { + handler = new ExceptionWebSocketHandlerDecorator(handler); + return new LoggingWebSocketHandlerDecorator(handler); } public String getPrefix() { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 1ea26cd3fe..4220facf78 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -48,4 +48,9 @@ public interface WebSocketHandler { */ void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus); + /** + * Whether this WebSocketHandler wishes to receive messages broken up in parts. + */ + boolean isStreaming(); + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java index d8aa4f2d13..163ffa057f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -16,8 +16,6 @@ package org.springframework.websocket.adapter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.springframework.util.Assert; @@ -35,8 +33,6 @@ import org.springframework.websocket.WebSocketSession; */ public class JettyWebSocketListenerAdapter implements WebSocketListener { - private static Log logger = LogFactory.getLog(JettyWebSocketListenerAdapter.class); - private final WebSocketHandler webSocketHandler; private WebSocketSession wsSession; @@ -44,7 +40,7 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.webSocketHandler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); + this.webSocketHandler = webSocketHandler; } @@ -76,4 +72,5 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { public void onWebSocketError(Throwable cause) { this.webSocketHandler.handleTransportError(this.wsSession, cause); } + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java index c076eebe42..a36249078e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -23,12 +23,9 @@ import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.PartialMessageHandler; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -42,26 +39,23 @@ import org.springframework.websocket.WebSocketSession; */ public class StandardEndpointAdapter extends Endpoint { - private static Log logger = LogFactory.getLog(StandardEndpointAdapter.class); - - private final WebSocketHandlerInvoker handler; - - private final Class handlerClass; + private final WebSocketHandler handler; private WebSocketSession wsSession; - public StandardEndpointAdapter(WebSocketHandler webSocketHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.handler = new WebSocketHandlerInvoker(webSocketHandler).setLogger(logger); - this.handlerClass= webSocketHandler.getClass(); + this.handler = webSocketHandler; } @Override public void onOpen(final javax.websocket.Session session, EndpointConfig config) { + this.wsSession = new StandardWebSocketSessionAdapter(session); + this.handler.afterConnectionEstablished(this.wsSession); + session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(String message) { @@ -69,9 +63,7 @@ public class StandardEndpointAdapter extends Endpoint { } }); - // TODO: per-connection proxy - - if (!PartialMessageHandler.class.isAssignableFrom(this.handlerClass)) { + if (!this.handler.isStreaming()) { session.addMessageHandler(new MessageHandler.Whole() { @Override public void onMessage(ByteBuffer message) { @@ -88,8 +80,6 @@ public class StandardEndpointAdapter extends Endpoint { }); } - this.wsSession = new StandardWebSocketSessionAdapter(session); - this.handler.afterConnectionEstablished(this.wsSession); } private void handleTextMessage(javax.websocket.Session session, String payload) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java index a9b464f015..850cb2cacb 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java @@ -68,4 +68,9 @@ public class WebSocketHandlerAdapter implements WebSocketHandler { public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { } + @Override + public boolean isStreaming() { + return false; + } + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java deleted file mode 100644 index f2090b5d6c..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerInvoker.java +++ /dev/null @@ -1,126 +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.adapter; - -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; - -/** - * A class for managing and delegating to a {@link WebSocketHandler} instance, ensuring - * the handler is initialized and destroyed, that any unhandled exceptions from handler - * are caught (and handled by closing the session), as well as adding logging. - * - * @author Rossen Stoyanchev - * @author Phillip Webb - * @since 4.0 - */ -public class WebSocketHandlerInvoker implements WebSocketHandler { - - private Log logger = LogFactory.getLog(WebSocketHandlerInvoker.class); - - private final WebSocketHandler handler; - - private final AtomicInteger sessionCount = new AtomicInteger(0); - - - public WebSocketHandlerInvoker(WebSocketHandler webSocketHandler) { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.handler = webSocketHandler; - } - - public WebSocketHandlerInvoker setLogger(Log logger) { - this.logger = logger; - return this; - } - - @Override - public void afterConnectionEstablished(WebSocketSession session) { - if (logger.isDebugEnabled()) { - logger.debug("Connection established, " + session + ", uri=" + session.getURI()); - } - try { - Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected new session"); - this.handler.afterConnectionEstablished(session); - } - catch (Throwable ex) { - tryCloseWithError(session, ex); - } - } - - public void tryCloseWithError(WebSocketSession session, Throwable ex) { - tryCloseWithError(session, ex, null); - } - - public void tryCloseWithError(WebSocketSession session, Throwable exeption, CloseStatus status) { - logger.error("Closing due to exception for " + session, exeption); - if (session.isOpen()) { - try { - session.close((status != null) ? status : CloseStatus.SERVER_ERROR); - } - catch (Throwable t) { - // ignore - } - } - } - - @Override - public void handleMessage(WebSocketSession session, WebSocketMessage message) { - if (logger.isTraceEnabled()) { - logger.trace("Received " + message + ", " + session); - } - try { - this.handler.handleMessage(session, message); - } - catch (Throwable ex) { - tryCloseWithError(session,ex); - } - } - - @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { - if (logger.isDebugEnabled()) { - logger.debug("Transport error for " + session, exception); - } - try { - this.handler.handleTransportError(session, exception); - } - catch (Throwable ex) { - tryCloseWithError(session, ex); - } - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { - if (logger.isDebugEnabled()) { - logger.debug("Connection closed for " + session + ", " + closeStatus); - } - try { - this.handler.afterConnectionClosed(session, closeStatus); - } - catch (Throwable ex) { - logger.error("Unhandled error for " + this, ex); - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index 0e5090e584..ea49fc0c6b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -23,6 +23,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.util.CollectionUtils; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; /** * @author Rossen Stoyanchev @@ -44,7 +46,18 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag super(uriTemplate, uriVariables); this.client = webSocketClient; - this.webSocketHandler = webSocketHandler; + this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); + } + + /** + * Decorate the WebSocketHandler provided to the class constructor. + *

+ * By default {@link ExceptionWebSocketHandlerDecorator} and + * {@link LoggingWebSocketHandlerDecorator} are applied are added. + */ + protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) { + handler = new ExceptionWebSocketHandlerDecorator(handler); + return new LoggingWebSocketHandlerDecorator(handler); } public void setSubProtocols(List subProtocols) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 5d3b815810..6bb1bfa171 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -32,6 +32,8 @@ import org.springframework.web.util.NestedServletException; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; /** * An {@link HttpRequestHandler} that wraps the invocation of a {@link HandshakeHandler}. @@ -53,10 +55,21 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { public WebSocketHttpRequestHandler( WebSocketHandler webSocketHandler, HandshakeHandler handshakeHandler) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); Assert.notNull(handshakeHandler, "handshakeHandler is required"); - this.webSocketHandler = webSocketHandler; + this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); this.handshakeHandler = new DefaultHandshakeHandler(); } + /** + * Decorate the WebSocketHandler provided to the class constructor. + *

+ * By default {@link ExceptionWebSocketHandlerDecorator} and + * {@link LoggingWebSocketHandlerDecorator} are applied are added. + */ + protected WebSocketHandler decorateWebSocketHandler(WebSocketHandler handler) { + handler = new ExceptionWebSocketHandlerDecorator(handler); + return new LoggingWebSocketHandlerDecorator(handler); + } + @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java new file mode 100644 index 0000000000..8d45636d19 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java @@ -0,0 +1,92 @@ +/* + * 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorator { + + private Log logger = LogFactory.getLog(ExceptionWebSocketHandlerDecorator.class); + + + public ExceptionWebSocketHandlerDecorator(WebSocketHandler delegate) { + super(delegate); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + try { + getDelegate().afterConnectionEstablished(session); + } + catch (Throwable ex) { + tryCloseWithError(session, ex); + } + } + + private void tryCloseWithError(WebSocketSession session, Throwable exception) { + logger.error("Closing due to exception for " + session, exception); + if (session.isOpen()) { + try { + session.close(CloseStatus.SERVER_ERROR); + } + catch (Throwable t) { + // ignore + } + } + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + try { + getDelegate().handleMessage(session, message); + } + catch (Throwable ex) { + tryCloseWithError(session,ex); + } + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + try { + getDelegate().handleTransportError(session, exception); + } + catch (Throwable ex) { + tryCloseWithError(session, ex); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + try { + getDelegate().afterConnectionClosed(session, closeStatus); + } + catch (Throwable ex) { + logger.error("Unhandled error for " + this, ex); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java new file mode 100644 index 0000000000..8cb0e0e8c2 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java @@ -0,0 +1,73 @@ +/* + * 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.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator { + + private Log logger = LogFactory.getLog(LoggingWebSocketHandlerDecorator.class); + + + public LoggingWebSocketHandlerDecorator(WebSocketHandler delegate) { + super(delegate); + } + + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + if (logger.isDebugEnabled()) { + logger.debug("Connection established, " + session + ", uri=" + session.getURI()); + } + super.afterConnectionEstablished(session); + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + if (logger.isTraceEnabled()) { + logger.trace("Received " + message + ", " + session); + } + super.handleMessage(session, message); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + if (logger.isDebugEnabled()) { + logger.debug("Transport error for " + session, exception); + } + super.handleTransportError(session, exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + if (logger.isDebugEnabled()) { + logger.debug("Connection closed for " + session + ", " + closeStatus); + } + super.afterConnectionClosed(session, closeStatus); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java index ee4091178f..7d39bd0caf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java @@ -57,11 +57,23 @@ public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler, Bea private Map handlers = new ConcurrentHashMap(); + private boolean streaming; + public PerConnectionWebSocketHandlerProxy(Class handlerType) { - this.provider = new BeanCreatingHandlerProvider(handlerType); + this(handlerType, false); } + public PerConnectionWebSocketHandlerProxy(Class handlerType, boolean isStreaming) { + this.provider = new BeanCreatingHandlerProvider(handlerType); + this.streaming = isStreaming; + } + + + @Override + public boolean isStreaming() { + return this.streaming; + } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java new file mode 100644 index 0000000000..cff8b7202d --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java @@ -0,0 +1,70 @@ +/* + * 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.support; + +import org.springframework.util.Assert; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketMessage; +import org.springframework.websocket.WebSocketSession; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class WebSocketHandlerDecorator implements WebSocketHandler { + + private final WebSocketHandler delegate; + + + public WebSocketHandlerDecorator(WebSocketHandler delegate) { + Assert.notNull(delegate, "delegate is required"); + this.delegate = delegate; + } + + + protected WebSocketHandler getDelegate() { + return this.delegate; + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + this.delegate.afterConnectionEstablished(session); + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + this.delegate.handleMessage(session, message); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + this.delegate.handleTransportError(session, exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + this.delegate.afterConnectionClosed(session, closeStatus); + } + + @Override + public boolean isStreaming() { + return this.delegate.isStreaming(); + } + +} From 46bcffcf30d5de9a149337e1421d5df26bf2ef7d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 29 Apr 2013 17:00:52 -0400 Subject: [PATCH 44/51] Add JettyWebSocketClient Also split out JSR-356 related configuration and load it conditionally. --- README-WEBSOCKET.md | 11 ++ build.gradle | 1 + .../AbstractWebSocketConnectionManager.java | 91 ++++++----- .../websocket/client/WebSocketClient.java | 8 +- .../client/WebSocketConnectionManager.java | 24 ++- .../client/jetty/JettyWebSocketClient.java | 150 ++++++++++++++++++ .../support/JettyRequestUpgradeStrategy.java | 8 +- .../PerConnectionWebSocketHandlerProxy.java | 5 + .../support/WebSocketHandlerDecorator.java | 5 + 9 files changed, 259 insertions(+), 44 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java diff --git a/README-WEBSOCKET.md b/README-WEBSOCKET.md index cd9a707771..27c1e641b6 100644 --- a/README-WEBSOCKET.md +++ b/README-WEBSOCKET.md @@ -54,6 +54,17 @@ Run the ant build: A usable Tomcat installation can be found in `output/build` +### Jetty 9 + +Download and use the latest Jetty (currently 9.0.2.v20130417). It does not support JSR-356 yet but that's not an issue, since we're using the Jetty 9 native WebSocket API. + +If using Java-based Servlet configuration instead of web.xml, add the following options to Jetty's start.ini: + + OPTIONS=plus + etc/jetty-plus.xml + OPTIONS=annotations + etc/jetty-annotations.xml + ### Glassfish Glassfish also provides JSR-356 support based on Tyrus (the reference implementation). diff --git a/build.gradle b/build.gradle index 72eebf8059..ea959552c8 100644 --- a/build.gradle +++ b/build.gradle @@ -532,6 +532,7 @@ project("spring-websocket") { exclude group: "org.eclipse.jetty.orbit", module: "javax.servlet" } optional("org.eclipse.jetty.websocket:websocket-server:9.0.1.v20130408") + optional("org.eclipse.jetty.websocket:websocket-client:9.0.1.v20130408") optional("com.fasterxml.jackson.core:jackson-databind:2.0.1") // required for SockJS support currently diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java index ab696d9c8f..a67a9e7350 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java @@ -40,6 +40,8 @@ public abstract class AbstractWebSocketConnectionManager implements SmartLifecyc private boolean autoStartup = false; + private boolean isRunning = false; + private int phase = Integer.MAX_VALUE; private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("EndpointConnectionManager-"); @@ -92,6 +94,15 @@ public abstract class AbstractWebSocketConnectionManager implements SmartLifecyc return this.uri; } + /** + * Return whether this ConnectionManager has been started. + */ + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.isRunning; + } + } + /** * Connect to the configured {@link #setDefaultUri(URI) default URI}. If already * connected, the method has no impact. @@ -99,61 +110,69 @@ public abstract class AbstractWebSocketConnectionManager implements SmartLifecyc public final void start() { synchronized (this.lifecycleMonitor) { if (!isRunning()) { - this.taskExecutor.execute(new Runnable() { - @Override - public void run() { - synchronized (lifecycleMonitor) { - try { - logger.info("Connecting to WebSocket at " + uri); - openConnection(); - logger.info("Successfully connected"); - } - catch (Throwable ex) { - logger.error("Failed to connect", ex); - } - } - } - }); + startInternal(); } } } + protected void startInternal() { + if (logger.isDebugEnabled()) { + logger.debug("Starting " + this.getClass().getSimpleName()); + } + this.isRunning = true; + this.taskExecutor.execute(new Runnable() { + @Override + public void run() { + synchronized (lifecycleMonitor) { + try { + logger.info("Connecting to WebSocket at " + uri); + openConnection(); + logger.info("Successfully connected"); + } + catch (Throwable ex) { + logger.error("Failed to connect", ex); + } + } + } + }); + } + protected abstract void openConnection() throws Exception; - /** - * Closes the configured message WebSocket connection. - */ public final void stop() { synchronized (this.lifecycleMonitor) { if (isRunning()) { - try { - closeConnection(); - } - catch (Throwable e) { - logger.error("Failed to stop WebSocket connection", e); - } + stopInternal(); } } } + protected void stopInternal() { + if (logger.isDebugEnabled()) { + logger.debug("Stopping " + this.getClass().getSimpleName()); + } + try { + if (isConnected()) { + closeConnection(); + } + } + catch (Throwable e) { + logger.error("Failed to stop WebSocket connection", e); + } + finally { + this.isRunning = false; + } + } + + protected abstract boolean isConnected(); + protected abstract void closeConnection() throws Exception; - public void stop(Runnable callback) { + public final void stop(Runnable callback) { synchronized (this.lifecycleMonitor) { this.stop(); callback.run(); } } - /** - * Return whether the configured message endpoint is currently active. - */ - public boolean isRunning() { - synchronized (this.lifecycleMonitor) { - return isConnected(); - } - } - - protected abstract boolean isConnected(); - } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java index 5aaa0380f9..6348f34dc9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java @@ -22,10 +22,10 @@ import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; /** - * Contract for starting a WebSocket handshake request. - * - *

To automatically start a WebSocket connection when the application starts, see - * {@link WebSocketConnectionManager}. + * Contract for programmatically starting a WebSocket handshake request. For most cases it + * would be more convenient to use the declarative style + * {@link WebSocketConnectionManager} that starts a WebSocket connection to a + * pre-configured URI when the application starts. * * @author Rossen Stoyanchev * @since 4.0 diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index ea49fc0c6b..09c0d06cf2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -19,6 +19,7 @@ package org.springframework.websocket.client; import java.util.ArrayList; import java.util.List; +import org.springframework.context.SmartLifecycle; import org.springframework.http.HttpHeaders; import org.springframework.util.CollectionUtils; import org.springframework.websocket.WebSocketHandler; @@ -40,13 +41,16 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag private final List subProtocols = new ArrayList(); + private final boolean syncClientLifecycle; - public WebSocketConnectionManager(WebSocketClient webSocketClient, + + public WebSocketConnectionManager(WebSocketClient client, WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) { super(uriTemplate, uriVariables); - this.client = webSocketClient; + this.client = client; this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); + this.syncClientLifecycle = ((client instanceof SmartLifecycle) && !((SmartLifecycle) client).isRunning()); } /** @@ -71,6 +75,22 @@ public class WebSocketConnectionManager extends AbstractWebSocketConnectionManag return this.subProtocols; } + @Override + public void startInternal() { + if (this.syncClientLifecycle) { + ((SmartLifecycle) this.client).start(); + } + super.startInternal(); + } + + @Override + public void stopInternal() { + if (this.syncClientLifecycle) { + ((SmartLifecycle) client).stop(); + } + super.stopInternal(); + } + @Override protected void openConnection() throws Exception { HttpHeaders headers = new HttpHeaders(); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java new file mode 100644 index 0000000000..210e0dfc13 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java @@ -0,0 +1,150 @@ +/* + * 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.jetty; + +import java.net.URI; +import java.util.concurrent.Future; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.websocket.api.Session; +import org.springframework.context.SmartLifecycle; +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.adapter.JettyWebSocketListenerAdapter; +import org.springframework.websocket.adapter.JettyWebSocketSessionAdapter; +import org.springframework.websocket.client.WebSocketClient; +import org.springframework.websocket.client.WebSocketConnectFailureException; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle { + + private static final Log logger = LogFactory.getLog(JettyWebSocketClient.class); + + private final org.eclipse.jetty.websocket.client.WebSocketClient client; + + private boolean autoStartup = true; + + private int phase = Integer.MAX_VALUE; + + private final Object lifecycleMonitor = new Object(); + + + public JettyWebSocketClient() { + this.client = new org.eclipse.jetty.websocket.client.WebSocketClient(); + } + + + // TODO: configure Jetty WebSocketClient properties + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + public void setPhase(int phase) { + this.phase = phase; + } + + @Override + public int getPhase() { + return this.phase; + } + + @Override + public boolean isRunning() { + synchronized (this.lifecycleMonitor) { + return this.client.isStarted(); + } + } + + @Override + public void start() { + synchronized (this.lifecycleMonitor) { + if (!isRunning()) { + try { + if (logger.isDebugEnabled()) { + logger.debug("Starting Jetty WebSocketClient"); + } + this.client.start(); + } + catch (Exception e) { + throw new IllegalStateException("Failed to start Jetty client", e); + } + } + } + } + + @Override + public void stop() { + synchronized (this.lifecycleMonitor) { + if (isRunning()) { + try { + if (logger.isDebugEnabled()) { + logger.debug("Stopping Jetty WebSocketClient"); + } + this.client.stop(); + } + catch (Exception e) { + logger.error("Error stopping Jetty WebSocketClient", e); + } + } + } + } + + @Override + public void stop(Runnable callback) { + this.stop(); + callback.run(); + } + + @Override + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) + throws WebSocketConnectFailureException { + + URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); + return doHandshake(webSocketHandler, null, uri); + } + + @Override + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) + throws WebSocketConnectFailureException { + + JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler); + + try { + // block for now + Future future = this.client.connect(listener, uri); + Session session = future.get(); + return new JettyWebSocketSessionAdapter(session); + } + catch (Exception e) { + throw new WebSocketConnectFailureException("Failed to connect to " + uri, e); + } + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index 9819a63991..ef5674c6bd 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -100,10 +100,14 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { private void upgrade(HttpServletRequest request, HttpServletResponse response, String selectedProtocol, final WebSocketHandler webSocketHandler) throws IOException { - Assert.state(this.factory.isUpgradeRequest(request, response), "Not a suitable WebSocket upgrade request"); - Assert.state(this.factory.acceptWebSocket(request, response), "Unable to accept WebSocket"); + Assert.state(this.factory.isUpgradeRequest(request, response), "Expected websocket upgrade request"); request.setAttribute(HANDLER_PROVIDER_ATTR_NAME, webSocketHandler); + + if (!this.factory.acceptWebSocket(request, response)) { + // should never happen + throw new IllegalStateException("WebSocket request not accepted by Jetty"); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java index 7d39bd0caf..350c23bc05 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java @@ -125,4 +125,9 @@ public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler, Bea } } + @Override + public String toString() { + return "PerConnectionWebSocketHandlerProxy [handlerType=" + this.provider.getHandlerType() + "]"; + } + } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java index cff8b7202d..a94fbb8b51 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java @@ -67,4 +67,9 @@ public class WebSocketHandlerDecorator implements WebSocketHandler { return this.delegate.isStreaming(); } + @Override + public String toString() { + return getClass().getSimpleName() + " [delegate=" + this.delegate + "]"; + } + } From 9ca4672300aa59949ac1073c56feedee783af6ec Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 1 May 2013 14:18:25 -0400 Subject: [PATCH 45/51] Fix handshake handling issue --- .../websocket/CloseStatus.java | 4 ++++ .../endpoint/StandardWebSocketClient.java | 20 ++++++++++++++++++- .../client/jetty/JettyWebSocketClient.java | 2 ++ .../server/DefaultHandshakeHandler.java | 9 ++++++--- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java index dafcb988e7..8774fe46f9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java @@ -198,6 +198,10 @@ public final class CloseStatus { return (this.code == otherStatus.code && ObjectUtils.nullSafeEquals(this.reason, otherStatus.reason)); } + public boolean equalsCode(CloseStatus other) { + return this.code == other.code; + } + @Override public String toString() { return "CloseStatus [code=" + this.code + ", reason=" + this.reason + "]"; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index ffb043c340..100ef64164 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -27,9 +27,12 @@ import javax.websocket.ClientEndpointConfig; import javax.websocket.ClientEndpointConfig.Configurator; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; +import javax.websocket.HandshakeResponse; import javax.websocket.Session; import javax.websocket.WebSocketContainer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpHeaders; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.websocket.WebSocketHandler; @@ -47,6 +50,8 @@ import org.springframework.websocket.client.WebSocketConnectFailureException; */ public class StandardWebSocketClient implements WebSocketClient { + private static final Log logger = LogFactory.getLog(StandardWebSocketClient.class); + private static final Set EXCLUDED_HEADERS = new HashSet( Arrays.asList("Sec-WebSocket-Accept", "Sec-WebSocket-Extensions", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol", "Sec-WebSocket-Version")); @@ -83,9 +88,22 @@ public class StandardWebSocketClient implements WebSocketClient { public void beforeRequest(Map> headers) { for (String headerName : httpHeaders.keySet()) { if (!EXCLUDED_HEADERS.contains(headerName)) { - headers.put(headerName, httpHeaders.get(headerName)); + List value = httpHeaders.get(headerName); + if (logger.isTraceEnabled()) { + logger.trace("Adding header [" + headerName + "=" + value + "]"); + } + headers.put(headerName, value); } } + if (logger.isTraceEnabled()) { + logger.trace("Handshake request headers: " + headers); + } + } + @Override + public void afterResponse(HandshakeResponse handshakeResponse) { + if (logger.isTraceEnabled()) { + logger.trace("Handshake response headers: " + handshakeResponse.getHeaders()); + } } }); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java index 210e0dfc13..ae1cc25cc9 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java @@ -134,6 +134,8 @@ public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException { + // TODO: populate headers + JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler); try { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index e557a0cc12..b369aca085 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -34,6 +35,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.websocket.WebSocketHandler; @@ -53,7 +55,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { protected Log logger = LogFactory.getLog(getClass()); - private List supportedProtocols; + private List supportedProtocols = new ArrayList(); private RequestUpgradeStrategy requestUpgradeStrategy; @@ -101,7 +103,8 @@ public class DefaultHandshakeHandler implements HandshakeHandler { handleInvalidUpgradeHeader(request, response); return false; } - if (!request.getHeaders().getConnection().contains("Upgrade")) { + if (!request.getHeaders().getConnection().contains("Upgrade") && + !request.getHeaders().getConnection().contains("upgrade")) { handleInvalidConnectHeader(request, response); return false; } @@ -188,7 +191,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { } protected String selectProtocol(List requestedProtocols) { - if (requestedProtocols != null) { + if (CollectionUtils.isEmpty(requestedProtocols)) { for (String protocol : requestedProtocols) { if (this.supportedProtocols.contains(protocol)) { return protocol; From 166ca7a5a3254c6d63551ad286a9e55375276e85 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 1 May 2013 15:57:55 -0400 Subject: [PATCH 46/51] Update exception handling Allow WebSocketHandler methods to raise an exception. By default we install ExceptionWebSocketHandlerDecorator, which logs unhandled exceptions and closes the session. That decorator can be extended or replaced. Any exceptions that remain unhandled still (i.e. no exception handling decorator), are caught in the lowest level before propagating to the WebSocket engine or a SockJS transport handler and handled the same way. That means default behavior is guaranteed but also fully customizable. --- .../sockjs/AbstractSockJsSession.java | 30 +++++++---- .../server/AbstractServerSockJsSession.java | 2 +- ...ption.java => SockJsRuntimeException.java} | 6 +-- ...AbstractHttpReceivingTransportHandler.java | 10 +++- .../AbstractHttpServerSockJsSession.java | 8 ++- .../transport/SockJsWebSocketHandler.java | 24 ++++++--- .../websocket/PartialMessageHandler.java | 28 ----------- .../websocket/WebSocketHandler.java | 40 ++++++++++++--- .../JettyWebSocketListenerAdapter.java | 50 +++++++++++++++---- .../adapter/StandardEndpointAdapter.java | 41 +++++++++++++-- .../adapter/WebSocketHandlerAdapter.java | 12 ++--- .../WebSocketConnectFailureException.java | 1 + .../server/DefaultHandshakeHandler.java | 12 ++--- .../server/HandshakeFailureException.java | 48 ++++++++++++++++++ .../websocket/server/HandshakeHandler.java | 16 +++++- .../server/RequestUpgradeStrategy.java | 11 ++-- .../GlassfishRequestUpgradeStrategy.java | 7 +-- .../support/JettyRequestUpgradeStrategy.java | 3 +- .../support/TomcatRequestUpgradeStrategy.java | 3 +- .../support/WebSocketHttpRequestHandler.java | 10 ++-- .../ExceptionWebSocketHandlerDecorator.java | 12 ++--- .../LoggingWebSocketHandlerDecorator.java | 8 +-- .../PerConnectionWebSocketHandlerProxy.java | 8 +-- .../support/WebSocketHandlerDecorator.java | 8 +-- 24 files changed, 277 insertions(+), 121 deletions(-) rename spring-websocket/src/main/java/org/springframework/sockjs/server/{NestedSockJsRuntimeException.java => SockJsRuntimeException.java} (81%) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 3912dcb268..30514835ad 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -118,7 +118,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { this.timeLastActive = System.currentTimeMillis(); } - public void delegateConnectionEstablished() { + public void delegateConnectionEstablished() throws Exception { this.state = State.OPEN; this.handler.afterConnectionEstablished(this); } @@ -127,23 +127,28 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * Close due to error arising from SockJS transport handling. */ protected void tryCloseWithSockJsTransportError(Throwable ex, CloseStatus closeStatus) { - delegateError(ex); + logger.error("Closing due to transport error for " + this, ex); try { - logger.error("Closing due to transport error for " + this, ex); - close(closeStatus); + delegateError(ex); } - catch (Throwable t) { - // ignore + catch (Throwable delegateEx) { + logger.error("Unhandled error for " + this, delegateEx); + try { + close(closeStatus); + } + catch (Throwable closeEx) { + logger.error("Unhandled error for " + this, closeEx); + } } } - public void delegateMessages(String[] messages) { + public void delegateMessages(String[] messages) throws Exception { for (String message : messages) { this.handler.handleMessage(this, new TextMessage(message)); } } - public void delegateError(Throwable ex) { + public void delegateError(Throwable ex) throws Exception { this.handler.handleTransportError(this, ex); } @@ -153,7 +158,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { * {@link TextMessageHandler}. This is in contrast to {@link #close()} that pro-actively * closes the connection. */ - public final void delegateConnectionClosed(CloseStatus status) { + public final void delegateConnectionClosed(CloseStatus status) throws Exception { if (!isClosed()) { if (logger.isDebugEnabled()) { logger.debug(this + " was closed, " + status); @@ -193,7 +198,12 @@ public abstract class AbstractSockJsSession implements WebSocketSession { } finally { this.state = State.CLOSED; - this.handler.afterConnectionClosed(this, status); + try { + this.handler.afterConnectionClosed(this, status); + } + catch (Throwable t) { + logger.error("Unhandled error for " + this, t); + } } } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java index fdd9f15330..554d813a55 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java @@ -111,7 +111,7 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession catch (Throwable ex) { logger.warn("Terminating connection due to failure to send message: " + ex.getMessage()); close(); - throw new NestedSockJsRuntimeException("Failed to write " + frame, ex); + throw new SockJsRuntimeException("Failed to write " + frame, ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsRuntimeException.java similarity index 81% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java rename to spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsRuntimeException.java index 63f82da257..e9026012bb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/NestedSockJsRuntimeException.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsRuntimeException.java @@ -24,13 +24,13 @@ import org.springframework.core.NestedRuntimeException; * @since 4.0 */ @SuppressWarnings("serial") -public class NestedSockJsRuntimeException extends NestedRuntimeException { +public class SockJsRuntimeException extends NestedRuntimeException { - public NestedSockJsRuntimeException(String msg) { + public SockJsRuntimeException(String msg) { super(msg); } - public NestedSockJsRuntimeException(String msg, Throwable cause) { + public SockJsRuntimeException(String msg, Throwable cause) { super(msg, cause); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java index 0f8d492eb1..078c354eee 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java @@ -27,9 +27,11 @@ import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; +import org.springframework.sockjs.server.SockJsRuntimeException; import org.springframework.sockjs.server.TransportErrorException; import org.springframework.sockjs.server.TransportHandler; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -89,7 +91,13 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport logger.trace("Received messages: " + Arrays.asList(messages)); } - session.delegateMessages(messages); + try { + session.delegateMessages(messages); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(session, t, logger); + throw new SockJsRuntimeException("Unhandled WebSocketHandler error in " + this, t); + } response.setStatusCode(getResponseStatus()); response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java index 055102b681..856296ceb6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java @@ -31,6 +31,7 @@ import org.springframework.sockjs.server.TransportErrorException; import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; /** * An abstract base class for use with HTTP-based transports. @@ -65,7 +66,12 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock tryCloseWithSockJsTransportError(t, null); throw new TransportErrorException("Failed open SockJS session", t, getId()); } - delegateConnectionEstablished(); + try { + delegateConnectionEstablished(); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this, t, logger); + } } protected void writePrelude() throws IOException { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index 13a0b9e12e..e1978fe226 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -34,8 +34,16 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * A wrapper around a {@link WebSocketHandler} instance that parses as well as adds SockJS - * messages frames as well as sends SockJS heartbeat messages. + * A wrapper around a {@link WebSocketHandler} instance that parses and adds SockJS + * messages frames and also sends SockJS heartbeat messages. + * + *

+ * Implementations of the {@link WebSocketHandler} interface in this class allow + * exceptions from the wrapped {@link WebSocketHandler} to propagate. However, any + * exceptions resulting from SockJS message handling (e.g. while sending SockJS frames or + * heartbeat messages) are caught and treated as transport errors, i.e. routed to the + * {@link WebSocketHandler#handleTransportError(WebSocketSession, Throwable) + * handleTransportError} method of the wrapped handler and the session closed. * * @author Rossen Stoyanchev * @since 4.0 @@ -66,24 +74,24 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { } @Override - public void afterConnectionEstablished(WebSocketSession wsSession) { + public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); this.sockJsSession = new WebSocketServerSockJsSession(getSockJsSessionId(wsSession), getSockJsConfig()); this.sockJsSession.initWebSocketSession(wsSession); } @Override - public void handleTextMessage(WebSocketSession wsSession, TextMessage message) { + public void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception { this.sockJsSession.handleMessage(message, wsSession); } @Override - public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) { + public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception { this.sockJsSession.delegateConnectionClosed(status); } @Override - public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) { + public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception { this.sockJsSession.delegateError(exception); } @@ -105,7 +113,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { super(sessionId, config, SockJsWebSocketHandler.this.webSocketHandler); } - public void initWebSocketSession(WebSocketSession wsSession) { + public void initWebSocketSession(WebSocketSession wsSession) throws Exception { this.wsSession = wsSession; try { TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); @@ -124,7 +132,7 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { return this.wsSession.isOpen(); } - public void handleMessage(TextMessage message, WebSocketSession wsSession) { + public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception { String payload = message.getPayload(); if (StringUtils.isEmpty(payload)) { logger.trace("Ignoring empty message"); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java deleted file mode 100644 index 30524be5fa..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/PartialMessageHandler.java +++ /dev/null @@ -1,28 +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; - -/** - * A "marker" interface for {@link BinaryMessageHandler} types that wish to handle partial - * messages. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public interface PartialMessageHandler { - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 4220facf78..21dc215097 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -17,7 +17,15 @@ package org.springframework.websocket; /** - * A handler for WebSocket sessions. + * A handler for WebSocket messages and lifecycle events. + * + *

Implementations of this interface are encouraged to handle exceptions locally where + * it makes sense or alternatively let the exception bubble up in which case the exception + * is logged and the session closed with {@link CloseStatus#SERVER_ERROR SERVER_ERROR(101)} by default. + * The exception handling strategy is provided by + * {@link org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator ExceptionWebSocketHandlerDecorator}, + * which can be customized or replaced by decorating the {@link WebSocketHandler} with a + * different decorator. * * @param The type of message being handled {@link TextMessage}, {@link BinaryMessage} * (or {@link WebSocketMessage} for both). @@ -29,24 +37,40 @@ package org.springframework.websocket; public interface WebSocketHandler { /** - * A new WebSocket connection has been opened and is ready to be used. + * Invoked after WebSocket negotiation has succeeded and the WebSocket connection is + * opened and ready for use. + * + * @throws Exception this method can handle or propagate exceptions; see class-level + * Javadoc for details. */ - void afterConnectionEstablished(WebSocketSession session); + void afterConnectionEstablished(WebSocketSession session) throws Exception; /** - * Handle an incoming WebSocket message. + * Invoked when a new WebSocket message arrives. + * + * @throws Exception this method can handle or propagate exceptions; see class-level + * Javadoc for details. */ - void handleMessage(WebSocketSession session, WebSocketMessage message); + void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception; /** * Handle an error from the underlying WebSocket message transport. + * + * @throws Exception this method can handle or propagate exceptions; see class-level + * Javadoc for details. */ - void handleTransportError(WebSocketSession session, Throwable exception); + void handleTransportError(WebSocketSession session, Throwable exception) throws Exception; /** - * A WebSocket connection has been closed. + * Invoked after the WebSocket connection has been closed by either side, or after a + * transport error has occurred. Although the session may technically still be open, + * depending on the underlying implementation, sending messages at this point is + * discouraged and most likely will not succeed. + * + * @throws Exception this method can handle or propagate exceptions; see class-level + * Javadoc for details. */ - void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus); + void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception; /** * Whether this WebSocketHandler wishes to receive messages broken up in parts. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java index 163ffa057f..04458b3560 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -16,6 +16,8 @@ package org.springframework.websocket.adapter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.springframework.util.Assert; @@ -24,6 +26,7 @@ import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; /** * Adapts Spring's {@link WebSocketHandler} to Jetty's {@link WebSocketListener}. @@ -33,6 +36,8 @@ import org.springframework.websocket.WebSocketSession; */ public class JettyWebSocketListenerAdapter implements WebSocketListener { + private static final Log logger = LogFactory.getLog(JettyWebSocketListenerAdapter.class); + private final WebSocketHandler webSocketHandler; private WebSocketSession wsSession; @@ -47,30 +52,55 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { @Override public void onWebSocketConnect(Session session) { this.wsSession = new JettyWebSocketSessionAdapter(session); - this.webSocketHandler.afterConnectionEstablished(this.wsSession); - } - - @Override - public void onWebSocketClose(int statusCode, String reason) { - CloseStatus closeStatus = new CloseStatus(statusCode, reason); - this.webSocketHandler.afterConnectionClosed(this.wsSession, closeStatus); + try { + this.webSocketHandler.afterConnectionEstablished(this.wsSession); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } } @Override public void onWebSocketText(String payload) { TextMessage message = new TextMessage(payload); - this.webSocketHandler.handleMessage(this.wsSession, message); + try { + this.webSocketHandler.handleMessage(this.wsSession, message); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } } @Override public void onWebSocketBinary(byte[] payload, int offset, int len) { BinaryMessage message = new BinaryMessage(payload, offset, len); - this.webSocketHandler.handleMessage(this.wsSession, message); + try { + this.webSocketHandler.handleMessage(this.wsSession, message); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } + } + + @Override + public void onWebSocketClose(int statusCode, String reason) { + CloseStatus closeStatus = new CloseStatus(statusCode, reason); + try { + this.webSocketHandler.afterConnectionClosed(this.wsSession, closeStatus); + } + catch (Throwable t) { + logger.error("Unhandled error for " + this.wsSession, t); + } } @Override public void onWebSocketError(Throwable cause) { - this.webSocketHandler.handleTransportError(this.wsSession, cause); + try { + this.webSocketHandler.handleTransportError(this.wsSession, cause); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java index a36249078e..4f6f731281 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -23,12 +23,15 @@ import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; /** @@ -39,6 +42,8 @@ import org.springframework.websocket.WebSocketSession; */ public class StandardEndpointAdapter extends Endpoint { + private static final Log logger = LogFactory.getLog(StandardEndpointAdapter.class); + private final WebSocketHandler handler; private WebSocketSession wsSession; @@ -54,7 +59,13 @@ public class StandardEndpointAdapter extends Endpoint { public void onOpen(final javax.websocket.Session session, EndpointConfig config) { this.wsSession = new StandardWebSocketSessionAdapter(session); - this.handler.afterConnectionEstablished(this.wsSession); + try { + this.handler.afterConnectionEstablished(this.wsSession); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + return; + } session.addMessageHandler(new MessageHandler.Whole() { @Override @@ -84,23 +95,43 @@ public class StandardEndpointAdapter extends Endpoint { private void handleTextMessage(javax.websocket.Session session, String payload) { TextMessage textMessage = new TextMessage(payload); - this.handler.handleMessage(this.wsSession, textMessage); + try { + this.handler.handleMessage(this.wsSession, textMessage); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } } private void handleBinaryMessage(javax.websocket.Session session, ByteBuffer payload, boolean isLast) { BinaryMessage binaryMessage = new BinaryMessage(payload, isLast); - this.handler.handleMessage(this.wsSession, binaryMessage); + try { + this.handler.handleMessage(this.wsSession, binaryMessage); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } } @Override public void onClose(javax.websocket.Session session, CloseReason reason) { CloseStatus closeStatus = new CloseStatus(reason.getCloseCode().getCode(), reason.getReasonPhrase()); - this.handler.afterConnectionClosed(this.wsSession, closeStatus); + try { + this.handler.afterConnectionClosed(this.wsSession, closeStatus); + } + catch (Throwable t) { + logger.error("Unhandled error for " + this.wsSession, t); + } } @Override public void onError(javax.websocket.Session session, Throwable exception) { - this.handler.handleTransportError(this.wsSession, exception); + try { + this.handler.handleTransportError(this.wsSession, exception); + } + catch (Throwable t) { + ExceptionWebSocketHandlerDecorator.tryCloseWithError(this.wsSession, t, logger); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java index 850cb2cacb..fd88b052d6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java @@ -37,11 +37,11 @@ import org.springframework.websocket.WebSocketSession; public class WebSocketHandlerAdapter implements WebSocketHandler { @Override - public void afterConnectionEstablished(WebSocketSession session) { + public void afterConnectionEstablished(WebSocketSession session) throws Exception { } @Override - public final void handleMessage(WebSocketSession session, WebSocketMessage message) { + public final void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { if (message instanceof TextMessage) { handleTextMessage(session, (TextMessage) message); } @@ -54,18 +54,18 @@ public class WebSocketHandlerAdapter implements WebSocketHandler { } } - protected void handleTextMessage(WebSocketSession session, TextMessage message) { + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { } - protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { } @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { } @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java index a6f904b1ef..0ed7c32e8e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java @@ -25,6 +25,7 @@ import org.springframework.core.NestedRuntimeException; @SuppressWarnings("serial") public class WebSocketConnectFailureException extends NestedRuntimeException { + public WebSocketConnectFailureException(String msg, Throwable cause) { super(msg, cause); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index b369aca085..5e658feccc 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -89,7 +89,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler) throws IOException { + WebSocketHandler webSocketHandler) throws IOException, HandshakeFailureException { logger.debug("Starting handshake for " + request.getURI()); @@ -201,14 +201,14 @@ public class DefaultHandshakeHandler implements HandshakeHandler { return null; } - private String getWebSocketKeyHash(String key) { + private String getWebSocketKeyHash(String key) throws HandshakeFailureException { try { - MessageDigest digest = MessageDigest.getInstance("SHA1"); - byte[] bytes = digest.digest((key + GUID).getBytes(Charset.forName("ISO-8859-1"))); - return DatatypeConverter.printBase64Binary(bytes); + MessageDigest digest = MessageDigest.getInstance("SHA1"); + byte[] bytes = digest.digest((key + GUID).getBytes(Charset.forName("ISO-8859-1"))); + return DatatypeConverter.printBase64Binary(bytes); } catch (NoSuchAlgorithmException ex) { - throw new IllegalStateException("Failed to generate value for Sec-WebSocket-Key header", ex); + throw new HandshakeFailureException("Failed to generate value for Sec-WebSocket-Key header", ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java new file mode 100644 index 0000000000..0c17392d39 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java @@ -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.server; + +import org.springframework.core.NestedRuntimeException; + + +/** + * Thrown when handshake processing failed to complete due to an internal, unrecoverable + * error. This implies a server error (HTTP status code 500) as opposed to a failure in + * the handshake negotiation. + * + *

+ * By contrast, when handshake negotiation fails, the response status code will be 200 and + * the response headers and body will have been updated to reflect the cause for the + * failure. A {@link HandshakeHandler} implementation will have protected methods to + * customize updates to the response in those cases. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +@SuppressWarnings("serial") +public class HandshakeFailureException extends NestedRuntimeException { + + + public HandshakeFailureException(String msg, Throwable cause) { + super(msg, cause); + } + + public HandshakeFailureException(String msg) { + super(msg); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java index fbdf2ca1a6..4a2df4928c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java @@ -31,7 +31,21 @@ import org.springframework.websocket.WebSocketHandler; public interface HandshakeHandler { + /** + * + * @param request + * @param response + * @param webSocketHandler + * @return + * + * @throws IOException thrown when accessing or setting the response + * + * @throws HandshakeFailureException thrown when handshake processing failed to + * complete due to an internal, unrecoverable error, i.e. a server error as + * opposed to a failure to successfully negotiate the requirements of the + * handshake request. + */ boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) - throws IOException; + throws IOException, HandshakeFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java index 70a7653af4..f717ac81e3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java @@ -37,12 +37,17 @@ public interface RequestUpgradeStrategy { String[] getSupportedVersions(); /** - * Perform runtime specific steps to complete the upgrade. - * Invoked only if the handshake is successful. + * Perform runtime specific steps to complete the upgrade. Invoked after successful + * negotiation of the handshake request. * * @param webSocketHandler the handler for WebSocket messages + * + * @throws HandshakeFailureException thrown when handshake processing failed to + * complete due to an internal, unrecoverable error, i.e. a server error as + * opposed to a failure to successfully negotiate the requirements of the + * handshake request. */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String selectedProtocol, - WebSocketHandler webSocketHandler) throws IOException; + WebSocketHandler webSocketHandler) throws IOException, HandshakeFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java index a5ca56c5b5..358813af80 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java @@ -48,6 +48,7 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; +import org.springframework.websocket.server.HandshakeFailureException; import org.springframework.websocket.server.endpoint.EndpointRegistration; /** @@ -69,7 +70,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, Endpoint endpoint) throws IOException { + String selectedProtocol, Endpoint endpoint) throws IOException, HandshakeFailureException { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -85,12 +86,12 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra engine.register(tyrusEndpoint); } catch (DeploymentException ex) { - throw new IllegalStateException("Failed to deploy endpoint in Glassfish", ex); + throw new HandshakeFailureException("Failed to deploy endpoint in Glassfish", ex); } try { if (!performUpgrade(servletRequest, servletResponse, request.getHeaders(), tyrusEndpoint)) { - throw new IllegalStateException("Failed to upgrade HttpServletRequest"); + throw new HandshakeFailureException("Failed to upgrade HttpServletRequest"); } } finally { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index ef5674c6bd..b536138e24 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -34,6 +34,7 @@ import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter; +import org.springframework.websocket.server.HandshakeFailureException; import org.springframework.websocket.server.RequestUpgradeStrategy; /** @@ -106,7 +107,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { if (!this.factory.acceptWebSocket(request, response)) { // should never happen - throw new IllegalStateException("WebSocket request not accepted by Jetty"); + throw new HandshakeFailureException("WebSocket request not accepted by Jetty"); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java index 0dcced9a12..c5a262b3a8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java @@ -32,6 +32,7 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; +import org.springframework.websocket.server.HandshakeFailureException; import org.springframework.websocket.server.endpoint.EndpointRegistration; /** @@ -63,7 +64,7 @@ public class TomcatRequestUpgradeStrategy extends AbstractEndpointUpgradeStrateg method.invoke(webSocketRequest); } catch (Exception ex) { - throw new IllegalStateException("Failed to upgrade HttpServletRequest", ex); + throw new HandshakeFailureException("Failed to upgrade HttpServletRequest", ex); } // TODO: use ServletContext attribute when Tomcat is updated diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java index 6bb1bfa171..0b8b8c2d8b 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java @@ -28,7 +28,6 @@ import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; -import org.springframework.web.util.NestedServletException; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; @@ -79,14 +78,11 @@ public class WebSocketHttpRequestHandler implements HttpRequestHandler { try { this.handshakeHandler.doHandshake(httpRequest, httpResponse, this.webSocketHandler); - } - catch (Exception e) { - // TODO - throw new NestedServletException("HandshakeHandler failure", e); - } - finally { httpResponse.flush(); } + catch (IOException ex) { + throw ex; + } } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java index 8d45636d19..0e82d03249 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java @@ -43,11 +43,11 @@ public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorato getDelegate().afterConnectionEstablished(session); } catch (Throwable ex) { - tryCloseWithError(session, ex); + tryCloseWithError(session, ex, logger); } } - private void tryCloseWithError(WebSocketSession session, Throwable exception) { + public static void tryCloseWithError(WebSocketSession session, Throwable exception, Log logger) { logger.error("Closing due to exception for " + session, exception); if (session.isOpen()) { try { @@ -65,7 +65,7 @@ public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorato getDelegate().handleMessage(session, message); } catch (Throwable ex) { - tryCloseWithError(session,ex); + tryCloseWithError(session, ex, logger); } } @@ -75,7 +75,7 @@ public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorato getDelegate().handleTransportError(session, exception); } catch (Throwable ex) { - tryCloseWithError(session, ex); + tryCloseWithError(session, ex, logger); } } @@ -84,8 +84,8 @@ public class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorato try { getDelegate().afterConnectionClosed(session, closeStatus); } - catch (Throwable ex) { - logger.error("Unhandled error for " + this, ex); + catch (Throwable t) { + logger.error("Unhandled error for " + this, t); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java index 8cb0e0e8c2..6dbad07312 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java @@ -39,7 +39,7 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator @Override - public void afterConnectionEstablished(WebSocketSession session) { + public void afterConnectionEstablished(WebSocketSession session) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Connection established, " + session + ", uri=" + session.getURI()); } @@ -47,7 +47,7 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator } @Override - public void handleMessage(WebSocketSession session, WebSocketMessage message) { + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Received " + message + ", " + session); } @@ -55,7 +55,7 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator } @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Transport error for " + session, exception); } @@ -63,7 +63,7 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator } @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { if (logger.isDebugEnabled()) { logger.debug("Connection closed for " + session + ", " + closeStatus); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java index 350c23bc05..64ed5c4bec 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java @@ -81,14 +81,14 @@ public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler, Bea } @Override - public void afterConnectionEstablished(WebSocketSession session) { + public void afterConnectionEstablished(WebSocketSession session) throws Exception { WebSocketHandler handler = this.provider.getHandler(); this.handlers.put(session, handler); handler.afterConnectionEstablished(session); } @Override - public void handleMessage(WebSocketSession session, WebSocketMessage message) { + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { getHandler(session).handleMessage(session, message); } @@ -99,12 +99,12 @@ public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler, Bea } @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { getHandler(session).handleTransportError(session, exception); } @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { try { getHandler(session).afterConnectionClosed(session, closeStatus); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java index a94fbb8b51..5fb9d6d7d3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java @@ -43,22 +43,22 @@ public class WebSocketHandlerDecorator implements WebSocketHandler { } @Override - public void afterConnectionEstablished(WebSocketSession session) { + public void afterConnectionEstablished(WebSocketSession session) throws Exception { this.delegate.afterConnectionEstablished(session); } @Override - public void handleMessage(WebSocketSession session, WebSocketMessage message) { + public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { this.delegate.handleMessage(session, message); } @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { this.delegate.handleTransportError(session, exception); } @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) { + public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { this.delegate.afterConnectionClosed(session, closeStatus); } From 2a7935a9132e7b8cd0efcd81b1a250527306c9b2 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 2 May 2013 13:47:18 -0400 Subject: [PATCH 47/51] Add WebSocketSession attributes + initialization In addition to adding the attributes, there is now mechanism for initializing WebSocketSession instances from attributes of the handshake request. --- .../http/server/ServerHttpRequest.java | 19 ++++ .../http/server/ServletServerHttpRequest.java | 16 +++ .../sockjs/AbstractSockJsSession.java | 60 ++++++++-- .../sockjs/SockJsSessionFactory.java | 5 +- .../sockjs/server/AbstractSockJsService.java | 2 +- .../sockjs/server/TransportType.java | 4 +- .../server/support/DefaultSockJsService.java | 19 ++-- .../AbstractHttpSendingTransportHandler.java | 2 +- .../transport/SockJsWebSocketHandler.java | 105 ++--------------- .../WebSocketServerSockJsSession.java | 107 ++++++++++++++++++ .../transport/WebSocketTransportHandler.java | 12 +- .../websocket/WebSocketSession.java | 27 ++++- .../AbstractWebSocketSesssionAdapter.java | 5 +- .../adapter/ConfigurableWebSocketSession.java | 39 +++++++ .../JettyWebSocketListenerAdapter.java | 9 +- .../adapter/JettyWebSocketSessionAdapter.java | 61 ++++++++-- .../adapter/StandardEndpointAdapter.java | 16 +-- .../StandardWebSocketSessionAdapter.java | 58 ++++++++-- .../endpoint/StandardWebSocketClient.java | 24 +++- .../client/jetty/JettyWebSocketClient.java | 28 +++-- .../AbstractEndpointUpgradeStrategy.java | 13 ++- .../support/JettyRequestUpgradeStrategy.java | 29 ++--- .../ServerWebSocketSessionInitializer.java | 38 +++++++ .../LoggingWebSocketHandlerDecorator.java | 2 +- 24 files changed, 510 insertions(+), 190 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java create mode 100644 spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java diff --git a/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java index 669835afc1..c765fd3492 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServerHttpRequest.java @@ -16,6 +16,8 @@ package org.springframework.http.server; +import java.security.Principal; + import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpRequest; import org.springframework.util.MultiValueMap; @@ -33,4 +35,21 @@ public interface ServerHttpRequest extends HttpRequest, HttpInputMessage { */ MultiValueMap getQueryParams(); + /** + * Return a {@link java.security.Principal} instance containing the name of the + * authenticated user. If the user has not been authenticated, the method returns + * null. + */ + Principal getPrincipal(); + + /** + * Return the host name of the endpoint on the other end. + */ + String getRemoteHostName(); + + /** + * Return the IP address of the endpoint on the other end. + */ + String getRemoteAddress(); + } diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index 5f4f90e9a8..2236df64cd 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -26,6 +26,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.Charset; +import java.security.Principal; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; @@ -131,6 +132,21 @@ public class ServletServerHttpRequest implements ServerHttpRequest { return this.headers; } + @Override + public Principal getPrincipal() { + return this.servletRequest.getUserPrincipal(); + } + + @Override + public String getRemoteHostName() { + return this.servletRequest.getRemoteHost(); + } + + @Override + public String getRemoteAddress() { + return this.servletRequest.getRemoteAddr(); + } + public Cookies getCookies() { if (this.cookies == null) { this.cookies = new Cookies(); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java index 30514835ad..671f4d993b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java @@ -18,6 +18,7 @@ package org.springframework.sockjs; import java.io.IOException; import java.net.URI; +import java.security.Principal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,7 +26,7 @@ import org.springframework.util.Assert; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; +import org.springframework.websocket.adapter.ConfigurableWebSocketSession; /** @@ -34,12 +35,20 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractSockJsSession implements WebSocketSession { +public abstract class AbstractSockJsSession implements ConfigurableWebSocketSession { protected final Log logger = LogFactory.getLog(getClass()); - private final String sessionId; + private final String id; + + private URI uri; + + private String remoteHostName; + + private String remoteAddress; + + private Principal principal; private WebSocketHandler handler; @@ -57,24 +66,51 @@ public abstract class AbstractSockJsSession implements WebSocketSession { public AbstractSockJsSession(String sessionId, WebSocketHandler webSocketHandler) { Assert.notNull(sessionId, "sessionId is required"); Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.sessionId = sessionId; + this.id = sessionId; this.handler = webSocketHandler; } public String getId() { - return this.sessionId; + return this.id; + } + + @Override + public URI getUri() { + return this.uri; + } + + @Override + public void setUri(URI uri) { + this.uri = uri; } @Override public boolean isSecure() { - // TODO - return false; + return "wss".equals(this.uri.getSchemeSpecificPart()); } - @Override - public URI getURI() { - // TODO - return null; + public String getRemoteHostName() { + return this.remoteHostName; + } + + public void setRemoteHostName(String remoteHostName) { + this.remoteHostName = remoteHostName; + } + + public String getRemoteAddress() { + return this.remoteAddress; + } + + public void setRemoteAddress(String remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public Principal getPrincipal() { + return this.principal; + } + + public void setPrincipal(Principal principal) { + this.principal = principal; } public boolean isNew() { @@ -213,7 +249,7 @@ public abstract class AbstractSockJsSession implements WebSocketSession { @Override public String toString() { - return "SockJS session id=" + this.sessionId; + return "SockJS session id=" + this.id; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java index 977cc9f0a3..f565808dd7 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java @@ -17,7 +17,6 @@ package org.springframework.sockjs; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; /** * A factory for creating a SockJS session. @@ -26,7 +25,7 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public interface SockJsSessionFactory{ +public interface SockJsSessionFactory { /** * Create a new SockJS session. @@ -34,6 +33,6 @@ public interface SockJsSessionFactory{ * @param webSocketHandler the underlying {@link WebSocketHandler} * @return a new non-null session */ - S createSession(String sessionId, WebSocketHandler webSocketHandler); + AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java index 16fdfc4214..15107e5a05 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java @@ -51,7 +51,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf private static final int ONE_YEAR = 365 * 24 * 60 * 60; - private String name = getClass().getSimpleName() + "@" + ObjectUtils.getIdentityHexString(this); + private String name = "SockJS Service " + ObjectUtils.getIdentityHexString(this); private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js"; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java index a71775534c..f3c9e5c6e2 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java @@ -68,7 +68,7 @@ public enum TransportType { return this.httpMethod; } - public boolean setsNoCacheHeader() { + public boolean setsNoCache() { return this.headerHints.contains("no_cache"); } @@ -76,7 +76,7 @@ public enum TransportType { return this.headerHints.contains("cors"); } - public boolean setsJsessionIdCookie() { + public boolean setsJsessionId() { return this.headerHints.contains("jsessionid"); } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java index 92dddbed59..53edca9c5d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java @@ -54,6 +54,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.server.DefaultHandshakeHandler; import org.springframework.websocket.server.HandshakeHandler; +import org.springframework.websocket.server.support.ServerWebSocketSessionInitializer; /** @@ -69,6 +70,8 @@ public class DefaultSockJsService extends AbstractSockJsService { private final Map sessions = new ConcurrentHashMap(); + private final ServerWebSocketSessionInitializer sessionInitializer = new ServerWebSocketSessionInitializer(); + private ScheduledFuture sessionCleanupTask; @@ -187,14 +190,15 @@ public class DefaultSockJsService extends AbstractSockJsService { return; } - AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler, transportHandler); + AbstractSockJsSession session = getSockJsSession(sessionId, webSocketHandler, + transportHandler, request, response); if (session != null) { - if (transportType.setsNoCacheHeader()) { + if (transportType.setsNoCache()) { addNoCacheHeaders(response); } - if (transportType.setsJsessionIdCookie() && isJsessionIdCookieRequired()) { + if (transportType.setsJsessionId() && isJsessionIdCookieRequired()) { Cookie cookie = request.getCookies().getCookie("JSESSIONID"); String jsid = (cookie != null) ? cookie.getValue() : "dummy"; // TODO: bypass use of Cookie object (causes Jetty to set Expires header) @@ -209,8 +213,8 @@ public class DefaultSockJsService extends AbstractSockJsService { transportHandler.handleRequest(request, response, webSocketHandler, session); } - public AbstractSockJsSession getSockJsSession(String sessionId, - WebSocketHandler webSocketHandler, TransportHandler transportHandler) { + protected AbstractSockJsSession getSockJsSession(String sessionId, WebSocketHandler handler, + TransportHandler transportHandler, ServerHttpRequest request, ServerHttpResponse response) { AbstractSockJsSession session = this.sessions.get(sessionId); if (session != null) { @@ -218,7 +222,7 @@ public class DefaultSockJsService extends AbstractSockJsService { } if (transportHandler instanceof SockJsSessionFactory) { - SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; + SockJsSessionFactory sessionFactory = (SockJsSessionFactory) transportHandler; synchronized (this.sessions) { session = this.sessions.get(sessionId); @@ -229,7 +233,8 @@ public class DefaultSockJsService extends AbstractSockJsService { scheduleSessionTask(); } logger.debug("Creating new session with session id \"" + sessionId + "\""); - session = (AbstractSockJsSession) sessionFactory.createSession(sessionId, webSocketHandler); + session = sessionFactory.createSession(sessionId, handler); + this.sessionInitializer.initialize(request, response, session); this.sessions.put(sessionId, session); return session; } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java index f0c92e72f8..681f3c66ce 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java @@ -39,7 +39,7 @@ import org.springframework.websocket.WebSocketHandler; * @since 4.0 */ public abstract class AbstractHttpSendingTransportHandler - implements ConfigurableTransportHandler, SockJsSessionFactory { + implements ConfigurableTransportHandler, SockJsSessionFactory { protected final Log logger = LogFactory.getLog(this.getClass()); diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java index e1978fe226..a5f41dd5c8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java @@ -16,22 +16,16 @@ package org.springframework.sockjs.server.transport; -import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.sockjs.server.AbstractServerSockJsSession; import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.adapter.TextWebSocketHandlerAdapter; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * A wrapper around a {@link WebSocketHandler} instance that parses and adds SockJS @@ -52,21 +46,20 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { private final SockJsConfiguration sockJsConfig; - private final WebSocketHandler webSocketHandler; - - private WebSocketServerSockJsSession sockJsSession; + private WebSocketServerSockJsSession session; private final AtomicInteger sessionCount = new AtomicInteger(0); - // TODO: JSON library used must be configurable - private final ObjectMapper objectMapper = new ObjectMapper(); + public SockJsWebSocketHandler(SockJsConfiguration config, + WebSocketHandler webSocketHandler, WebSocketServerSockJsSession session) { - public SockJsWebSocketHandler(SockJsConfiguration config, WebSocketHandler webSocketHandler) { Assert.notNull(config, "sockJsConfig is required"); Assert.notNull(webSocketHandler, "webSocketHandler is required"); + Assert.notNull(session, "session is required"); + this.sockJsConfig = config; - this.webSocketHandler = webSocketHandler; + this.session = session; } protected SockJsConfiguration getSockJsConfig() { @@ -76,100 +69,22 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter { @Override public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection"); - this.sockJsSession = new WebSocketServerSockJsSession(getSockJsSessionId(wsSession), getSockJsConfig()); - this.sockJsSession.initWebSocketSession(wsSession); + this.session.initWebSocketSession(wsSession); } @Override public void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception { - this.sockJsSession.handleMessage(message, wsSession); + this.session.handleMessage(message, wsSession); } @Override public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception { - this.sockJsSession.delegateConnectionClosed(status); + this.session.delegateConnectionClosed(status); } @Override public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception { - this.sockJsSession.delegateError(exception); - } - - private static String getSockJsSessionId(WebSocketSession wsSession) { - Assert.notNull(wsSession, "wsSession is required"); - String path = wsSession.getURI().getPath(); - String[] segments = StringUtils.tokenizeToStringArray(path, "/"); - Assert.isTrue(segments.length > 3, "SockJS request should have at least 3 path segments: " + path); - return segments[segments.length-2]; - } - - - private class WebSocketServerSockJsSession extends AbstractServerSockJsSession { - - private WebSocketSession wsSession; - - - public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config) { - super(sessionId, config, SockJsWebSocketHandler.this.webSocketHandler); - } - - public void initWebSocketSession(WebSocketSession wsSession) throws Exception { - this.wsSession = wsSession; - try { - TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); - this.wsSession.sendMessage(message); - } - catch (IOException ex) { - tryCloseWithSockJsTransportError(ex, null); - return; - } - scheduleHeartbeat(); - delegateConnectionEstablished(); - } - - @Override - public boolean isActive() { - return this.wsSession.isOpen(); - } - - public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception { - String payload = message.getPayload(); - if (StringUtils.isEmpty(payload)) { - logger.trace("Ignoring empty message"); - return; - } - String[] messages; - try { - messages = objectMapper.readValue(payload, String[].class); - } - catch (IOException ex) { - logger.error("Broken data received. Terminating WebSocket connection abruptly", ex); - tryCloseWithSockJsTransportError(ex, CloseStatus.BAD_DATA); - return; - } - delegateMessages(messages); - } - - @Override - public void sendMessageInternal(String message) throws IOException { - cancelHeartbeat(); - writeFrame(SockJsFrame.messageFrame(message)); - scheduleHeartbeat(); - } - - @Override - protected void writeFrameInternal(SockJsFrame frame) throws IOException { - if (logger.isTraceEnabled()) { - logger.trace("Write " + frame); - } - TextMessage message = new TextMessage(frame.getContent()); - this.wsSession.sendMessage(message); - } - - @Override - protected void disconnect(CloseStatus status) throws IOException { - this.wsSession.close(status); - } + this.session.delegateError(exception); } } diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java new file mode 100644 index 0000000000..5e45d6a269 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java @@ -0,0 +1,107 @@ +/* + * 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.server.AbstractServerSockJsSession; +import org.springframework.sockjs.server.SockJsConfiguration; +import org.springframework.sockjs.server.SockJsFrame; +import org.springframework.util.StringUtils; +import org.springframework.websocket.CloseStatus; +import org.springframework.websocket.TextMessage; +import org.springframework.websocket.WebSocketHandler; +import org.springframework.websocket.WebSocketSession; + +import com.fasterxml.jackson.databind.ObjectMapper; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class WebSocketServerSockJsSession extends AbstractServerSockJsSession { + + private WebSocketSession webSocketSession; + + // TODO: JSON library used must be configurable + private final ObjectMapper objectMapper = new ObjectMapper(); + + + public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) { + super(sessionId, config, handler); + } + + public void initWebSocketSession(WebSocketSession session) throws Exception { + this.webSocketSession = session; + try { + TextMessage message = new TextMessage(SockJsFrame.openFrame().getContent()); + this.webSocketSession.sendMessage(message); + } + catch (IOException ex) { + tryCloseWithSockJsTransportError(ex, null); + return; + } + scheduleHeartbeat(); + delegateConnectionEstablished(); + } + + @Override + public boolean isActive() { + return this.webSocketSession.isOpen(); + } + + public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception { + String payload = message.getPayload(); + if (StringUtils.isEmpty(payload)) { + logger.trace("Ignoring empty message"); + return; + } + String[] messages; + try { + messages = objectMapper.readValue(payload, String[].class); + } + catch (IOException ex) { + logger.error("Broken data received. Terminating WebSocket connection abruptly", ex); + tryCloseWithSockJsTransportError(ex, CloseStatus.BAD_DATA); + return; + } + delegateMessages(messages); + } + + @Override + public void sendMessageInternal(String message) throws IOException { + cancelHeartbeat(); + writeFrame(SockJsFrame.messageFrame(message)); + scheduleHeartbeat(); + } + + @Override + protected void writeFrameInternal(SockJsFrame frame) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Write " + frame); + } + TextMessage message = new TextMessage(frame.getContent()); + this.webSocketSession.sendMessage(message); + } + + @Override + protected void disconnect(CloseStatus status) throws IOException { + this.webSocketSession.close(status); + } +} + diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java index 81d82c4833..de077f2e17 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java @@ -21,6 +21,7 @@ import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.sockjs.AbstractSockJsSession; +import org.springframework.sockjs.SockJsSessionFactory; import org.springframework.sockjs.server.ConfigurableTransportHandler; import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.sockjs.server.TransportErrorException; @@ -39,7 +40,8 @@ import org.springframework.websocket.server.HandshakeHandler; * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketTransportHandler implements ConfigurableTransportHandler, HandshakeHandler { +public class WebSocketTransportHandler implements ConfigurableTransportHandler, + HandshakeHandler, SockJsSessionFactory { private final HandshakeHandler handshakeHandler; @@ -61,12 +63,18 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler, this.sockJsConfig = sockJsConfig; } + @Override + public AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) { + return new WebSocketServerSockJsSession(sessionId, this.sockJsConfig, webSocketHandler); + } + @Override public void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, AbstractSockJsSession session) throws TransportErrorException { try { - WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler); + WebSocketServerSockJsSession wsSession = (WebSocketServerSockJsSession) session; + WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler, wsSession); this.handshakeHandler.doHandshake(request, response, sockJsWrapper); } catch (Throwable t) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java index e1ef76b61d..19b4cc56dd 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java @@ -17,7 +17,9 @@ package org.springframework.websocket; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.URI; +import java.security.Principal; /** * Allows sending messages over a WebSocket connection as well as closing it. @@ -33,9 +35,9 @@ public interface WebSocketSession { String getId(); /** - * Return whether the connection is still open. + * Return the URI used to open the WebSocket connection. */ - boolean isOpen(); + URI getUri(); /** * Return whether the underlying socket is using a secure transport. @@ -43,9 +45,26 @@ public interface WebSocketSession { boolean isSecure(); /** - * Return the URI used to open the WebSocket connection. + * Return a {@link java.security.Principal} instance containing the name of the + * authenticated user. If the user has not been authenticated, the method returns + * null. */ - URI getURI(); + Principal getPrincipal(); + + /** + * Return the host name of the endpoint on the other end. + */ + String getRemoteHostName(); + + /** + * Return the IP address of the endpoint on the other end. + */ + String getRemoteAddress(); + + /** + * Return whether the connection is still open. + */ + boolean isOpen(); /** * Send a WebSocket message either {@link TextMessage} or diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java index 122f2264c5..4b44badd96 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java @@ -1,5 +1,4 @@ /* - * 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. @@ -34,11 +33,13 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractWebSocketSesssionAdapter implements WebSocketSession { +public abstract class AbstractWebSocketSesssionAdapter implements ConfigurableWebSocketSession { protected final Log logger = LogFactory.getLog(getClass()); + public abstract void initSession(T session); + @Override public final void sendMessage(WebSocketMessage message) throws IOException { if (logger.isTraceEnabled()) { diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java new file mode 100644 index 0000000000..63f724e558 --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java @@ -0,0 +1,39 @@ +/* + * 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.adapter; + +import java.net.URI; +import java.security.Principal; + +import org.springframework.websocket.WebSocketSession; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public interface ConfigurableWebSocketSession extends WebSocketSession { + + void setUri(URI uri); + + void setRemoteHostName(String name); + + void setRemoteAddress(String address); + + void setPrincipal(Principal principal); + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java index 04458b3560..4720d25784 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java @@ -25,7 +25,6 @@ import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; /** @@ -40,18 +39,20 @@ public class JettyWebSocketListenerAdapter implements WebSocketListener { private final WebSocketHandler webSocketHandler; - private WebSocketSession wsSession; + private JettyWebSocketSessionAdapter wsSession; - public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler) { + public JettyWebSocketListenerAdapter(WebSocketHandler webSocketHandler, JettyWebSocketSessionAdapter wsSession) { Assert.notNull(webSocketHandler, "webSocketHandler is required"); + Assert.notNull(wsSession, "wsSession is required"); this.webSocketHandler = webSocketHandler; + this.wsSession = wsSession; } @Override public void onWebSocketConnect(Session session) { - this.wsSession = new JettyWebSocketSessionAdapter(session); + this.wsSession.initSession(session); try { this.webSocketHandler.afterConnectionEstablished(this.wsSession); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java index c48f1b7db3..d7eeb411ed 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java @@ -17,9 +17,12 @@ package org.springframework.websocket.adapter; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.URI; +import java.security.Principal; import org.eclipse.jetty.websocket.api.Session; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; @@ -31,38 +34,78 @@ import org.springframework.websocket.WebSocketSession; * Adapts Jetty's {@link Session} to Spring's {@link WebSocketSession}. * * @author Phillip Webb + * @author Rossen Stoyanchev * @since 4.0 */ -public class JettyWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter { +public class JettyWebSocketSessionAdapter + extends AbstractWebSocketSesssionAdapter { private Session session; + private Principal principal; - public JettyWebSocketSessionAdapter(Session session) { + + @Override + public void initSession(Session session) { + Assert.notNull(session, "session is required"); this.session = session; } - @Override public String getId() { return ObjectUtils.getIdentityHexString(this.session); } - @Override - public boolean isOpen() { - return this.session.isOpen(); - } - @Override public boolean isSecure() { return this.session.isSecure(); } @Override - public URI getURI() { + public URI getUri() { return this.session.getUpgradeRequest().getRequestURI(); } + @Override + public void setUri(URI uri) { + } + + @Override + public Principal getPrincipal() { + return this.principal; + } + + @Override + public void setPrincipal(Principal principal) { + this.principal = principal; + } + + @Override + public String getRemoteHostName() { + return this.session.getRemoteAddress().getHostName(); + } + + @Override + public void setRemoteHostName(String address) { + // ignore + } + + @Override + public String getRemoteAddress() { + InetSocketAddress address = this.session.getRemoteAddress(); + return address.isUnresolved() ? null : address.getAddress().getHostAddress(); + } + + @Override + public void setRemoteAddress(String address) { + // ignore + } + + @Override + public boolean isOpen() { + return this.session.isOpen(); + } + @Override protected void sendTextMessage(TextMessage message) throws IOException { this.session.getRemote().sendString(message.getPayload()); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java index 4f6f731281..f7fd00b720 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java @@ -30,12 +30,11 @@ import org.springframework.websocket.BinaryMessage; import org.springframework.websocket.CloseStatus; import org.springframework.websocket.TextMessage; import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; /** - * An {@link Endpoint} that delegates to a {@link WebSocketHandler}. + * A wrapper around a {@link WebSocketHandler} that adapts it to {@link Endpoint}. * * @author Rossen Stoyanchev * @since 4.0 @@ -46,19 +45,22 @@ public class StandardEndpointAdapter extends Endpoint { private final WebSocketHandler handler; - private WebSocketSession wsSession; + private final StandardWebSocketSessionAdapter wsSession; - public StandardEndpointAdapter(WebSocketHandler webSocketHandler) { - Assert.notNull(webSocketHandler, "webSocketHandler is required"); - this.handler = webSocketHandler; + public StandardEndpointAdapter(WebSocketHandler handler, StandardWebSocketSessionAdapter wsSession) { + Assert.notNull(handler, "handler is required"); + Assert.notNull(wsSession, "wsSession is required"); + this.handler = handler; + this.wsSession = wsSession; } @Override public void onOpen(final javax.websocket.Session session, EndpointConfig config) { - this.wsSession = new StandardWebSocketSessionAdapter(session); + this.wsSession.initSession(session); + try { this.handler.afterConnectionEstablished(this.wsSession); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java index 811ebd93c5..ad98adb90d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java @@ -18,6 +18,7 @@ package org.springframework.websocket.adapter; import java.io.IOException; import java.net.URI; +import java.security.Principal; import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCodes; @@ -35,35 +36,76 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class StandardWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter { +public class StandardWebSocketSessionAdapter extends AbstractWebSocketSesssionAdapter { - private final javax.websocket.Session session; + private javax.websocket.Session session; + + private URI uri; + + private String remoteHostName; + + private String remoteAddress; - public StandardWebSocketSessionAdapter(javax.websocket.Session session) { + public void initSession(javax.websocket.Session session) { Assert.notNull(session, "session is required"); this.session = session; } - @Override public String getId() { return this.session.getId(); } @Override - public boolean isOpen() { - return this.session.isOpen(); + public URI getUri() { + return this.uri; } + @Override + public void setUri(URI uri) { + this.uri = uri; + } + + @Override public boolean isSecure() { return this.session.isSecure(); } @Override - public URI getURI() { - return this.session.getRequestURI(); + public Principal getPrincipal() { + return this.session.getUserPrincipal(); + } + + @Override + public void setPrincipal(Principal principal) { + // ignore + } + + @Override + public String getRemoteHostName() { + return this.remoteHostName; + } + + @Override + public void setRemoteHostName(String name) { + this.remoteHostName = name; + } + + @Override + public String getRemoteAddress() { + return this.remoteAddress; + } + + @Override + public void setRemoteAddress(String address) { + this.remoteAddress = address; + } + + @Override + public boolean isOpen() { + return this.session.isOpen(); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java index 100ef64164..8a1333c0fa 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java @@ -28,12 +28,12 @@ import javax.websocket.ClientEndpointConfig.Configurator; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; import javax.websocket.HandshakeResponse; -import javax.websocket.Session; import javax.websocket.WebSocketContainer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpHeaders; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -67,15 +67,26 @@ public class StandardWebSocketClient implements WebSocketClient { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { - URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); - return doHandshake(webSocketHandler, null, uri); + UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode(); + return doHandshake(webSocketHandler, null, uriComponents); } @Override public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { - Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler); + return doHandshake(webSocketHandler, httpHeaders, UriComponentsBuilder.fromUri(uri).build()); + } + + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, + final HttpHeaders httpHeaders, UriComponents uriComponents) throws WebSocketConnectFailureException { + + URI uri = uriComponents.toUri(); + + StandardWebSocketSessionAdapter session = new StandardWebSocketSessionAdapter(); + session.setUri(uri); + session.setRemoteHostName(uriComponents.getHost()); + Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler, session); ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create(); if (httpHeaders != null) { @@ -109,8 +120,9 @@ public class StandardWebSocketClient implements WebSocketClient { } try { - Session session = this.webSocketContainer.connectToServer(endpoint, configBuidler.build(), uri); - return new StandardWebSocketSessionAdapter(session); + // TODO: do not block + this.webSocketContainer.connectToServer(endpoint, configBuidler.build(), uri); + return session; } catch (Exception e) { throw new WebSocketConnectFailureException("Failed to connect to " + uri, e); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java index ae1cc25cc9..0f96c94836 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java @@ -17,13 +17,12 @@ package org.springframework.websocket.client.jetty; import java.net.URI; -import java.util.concurrent.Future; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.eclipse.jetty.websocket.api.Session; import org.springframework.context.SmartLifecycle; import org.springframework.http.HttpHeaders; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.WebSocketSession; @@ -126,23 +125,34 @@ public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, String uriTemplate, Object... uriVariables) throws WebSocketConnectFailureException { - URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); - return doHandshake(webSocketHandler, null, uri); + UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode(); + return doHandshake(webSocketHandler, null, uriComponents); } @Override public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException { + return doHandshake(webSocketHandler, headers, UriComponentsBuilder.fromUri(uri).build()); + } + + public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, UriComponents uriComponents) + throws WebSocketConnectFailureException { + // TODO: populate headers - JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler); + URI uri = uriComponents.toUri(); + + JettyWebSocketSessionAdapter session = new JettyWebSocketSessionAdapter(); + session.setUri(uri); + session.setRemoteHostName(uriComponents.getHost()); + + JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler, session); try { - // block for now - Future future = this.client.connect(listener, uri); - Session session = future.get(); - return new JettyWebSocketSessionAdapter(session); + // TODO: do not block + this.client.connect(listener, uri).get(); + return session; } catch (Exception e) { throw new WebSocketConnectFailureException("Failed to connect to " + uri, e); diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java index 096ab251e8..324ac4e79f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java @@ -26,6 +26,8 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.adapter.StandardEndpointAdapter; +import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter; +import org.springframework.websocket.server.HandshakeFailureException; import org.springframework.websocket.server.RequestUpgradeStrategy; /** @@ -39,15 +41,20 @@ public abstract class AbstractEndpointUpgradeStrategy implements RequestUpgradeS protected final Log logger = LogFactory.getLog(getClass()); + private final ServerWebSocketSessionInitializer wsSessionInitializer = new ServerWebSocketSessionInitializer(); + @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, WebSocketHandler webSocketHandler) throws IOException { + String protocol, WebSocketHandler handler) throws IOException, HandshakeFailureException { - upgradeInternal(request, response, protocol, new StandardEndpointAdapter(webSocketHandler)); + StandardWebSocketSessionAdapter session = new StandardWebSocketSessionAdapter(); + this.wsSessionInitializer.initialize(request, response, session); + StandardEndpointAdapter endpoint = new StandardEndpointAdapter(handler, session); + upgradeInternal(request, response, protocol, endpoint); } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, Endpoint endpoint) throws IOException; + String selectedProtocol, Endpoint endpoint) throws IOException, HandshakeFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java index b536138e24..707e858033 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java @@ -34,6 +34,7 @@ import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.websocket.WebSocketHandler; import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter; +import org.springframework.websocket.adapter.JettyWebSocketSessionAdapter; import org.springframework.websocket.server.HandshakeFailureException; import org.springframework.websocket.server.RequestUpgradeStrategy; @@ -53,11 +54,13 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { // FIXME when to call factory.cleanup(); - private static final String HANDLER_PROVIDER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName() + private static final String WEBSOCKET_LISTENER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName() + ".HANDLER_PROVIDER"; private WebSocketServerFactory factory; + private final ServerWebSocketSessionInitializer wsSessionInitializer = new ServerWebSocketSessionInitializer(); + public JettyRequestUpgradeStrategy() { this.factory = new WebSocketServerFactory(); @@ -65,17 +68,14 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) { Assert.isInstanceOf(ServletWebSocketRequest.class, request); - ServletWebSocketRequest servletRequest = (ServletWebSocketRequest) request; - WebSocketHandler webSocketHandler = - (WebSocketHandler) servletRequest.getServletAttributes().get(HANDLER_PROVIDER_ATTR_NAME); - return new JettyWebSocketListenerAdapter(webSocketHandler); + return ((ServletWebSocketRequest) request).getServletAttributes().get(WEBSOCKET_LISTENER_ATTR_NAME); } }); try { this.factory.init(); } catch (Exception ex) { - throw new IllegalStateException(ex); + throw new IllegalStateException("Unable to initialize Jetty WebSocketServerFactory", ex); } } @@ -95,17 +95,18 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { Assert.isInstanceOf(ServletServerHttpResponse.class, response); HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); - upgrade(servletRequest, servletResponse, selectedProtocol, webSocketHandler); - } + if (!this.factory.isUpgradeRequest(servletRequest, servletResponse)) { + // should never happen + throw new HandshakeFailureException("Not a WebSocket request"); + } - private void upgrade(HttpServletRequest request, HttpServletResponse response, - String selectedProtocol, final WebSocketHandler webSocketHandler) throws IOException { + JettyWebSocketSessionAdapter session = new JettyWebSocketSessionAdapter(); + this.wsSessionInitializer.initialize(request, response, session); + JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler, session); - Assert.state(this.factory.isUpgradeRequest(request, response), "Expected websocket upgrade request"); + servletRequest.setAttribute(WEBSOCKET_LISTENER_ATTR_NAME, listener); - request.setAttribute(HANDLER_PROVIDER_ATTR_NAME, webSocketHandler); - - if (!this.factory.acceptWebSocket(request, response)) { + if (!this.factory.acceptWebSocket(servletRequest, servletResponse)) { // should never happen throw new HandshakeFailureException("WebSocket request not accepted by Jetty"); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java new file mode 100644 index 0000000000..412bd5841b --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java @@ -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.server.support; + +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.websocket.adapter.ConfigurableWebSocketSession; + + +/** + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class ServerWebSocketSessionInitializer { + + + public void initialize(ServerHttpRequest request, ServerHttpResponse response, ConfigurableWebSocketSession session) { + session.setUri(request.getURI()); + session.setRemoteHostName(request.getRemoteHostName()); + session.setRemoteAddress(request.getRemoteAddress()); + session.setPrincipal(request.getPrincipal()); + } + +} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java index 6dbad07312..7e4fa8bd8c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java @@ -41,7 +41,7 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { if (logger.isDebugEnabled()) { - logger.debug("Connection established, " + session + ", uri=" + session.getURI()); + logger.debug("Connection established, " + session + ", uri=" + session.getUri()); } super.afterConnectionEstablished(session); } From 4faf0d265f009697045e53976df9088e46b31d9e Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 2 May 2013 20:25:09 -0400 Subject: [PATCH 48/51] Rename classes --- build.gradle | 1 - .../websocket/WebSocketHandler.java | 2 +- ...ger.java => ConnectionManagerSupport.java} | 4 +- .../client/WebSocketConnectionManager.java | 2 +- .../AnnotatedEndpointConnectionManager.java | 40 +++++++++-- .../endpoint/EndpointConnectionManager.java | 41 +++++++++-- .../EndpointConnectionManagerSupport.java | 70 ------------------- .../server/DefaultHandshakeHandler.java | 8 +-- ...a => GlassFishRequestUpgradeStrategy.java} | 10 +-- ...ava => PerConnectionWebSocketHandler.java} | 12 ++-- 10 files changed, 90 insertions(+), 100 deletions(-) rename spring-websocket/src/main/java/org/springframework/websocket/client/{AbstractWebSocketConnectionManager.java => ConnectionManagerSupport.java} (96%) delete mode 100644 spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java rename spring-websocket/src/main/java/org/springframework/websocket/server/support/{GlassfishRequestUpgradeStrategy.java => GlassFishRequestUpgradeStrategy.java} (95%) rename spring-websocket/src/main/java/org/springframework/websocket/support/{PerConnectionWebSocketHandlerProxy.java => PerConnectionWebSocketHandler.java} (89%) diff --git a/build.gradle b/build.gradle index ea959552c8..7db372c12c 100644 --- a/build.gradle +++ b/build.gradle @@ -515,7 +515,6 @@ project("spring-websocket") { compile(project(":spring-core")) compile(project(":spring-context")) compile(project(":spring-web")) - optional(project(":spring-webmvc")) optional("org.apache.tomcat:tomcat-servlet-api:8.0-SNAPSHOT") // TODO: replace with "javax.servlet:javax.servlet-api" optional("org.apache.tomcat:tomcat-websocket-api:8.0-SNAPSHOT") // TODO: replace with "javax.websocket:javax.websocket-api" diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java index 21dc215097..6331c003c6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java @@ -21,7 +21,7 @@ package org.springframework.websocket; * *

Implementations of this interface are encouraged to handle exceptions locally where * it makes sense or alternatively let the exception bubble up in which case the exception - * is logged and the session closed with {@link CloseStatus#SERVER_ERROR SERVER_ERROR(101)} by default. + * is logged and the session closed with {@link CloseStatus#SERVER_ERROR SERVER_ERROR(1011)} by default. * The exception handling strategy is provided by * {@link org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator ExceptionWebSocketHandlerDecorator}, * which can be customized or replaced by decorating the {@link WebSocketHandler} with a diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/ConnectionManagerSupport.java similarity index 96% rename from spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/websocket/client/ConnectionManagerSupport.java index a67a9e7350..abf19ce491 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/AbstractWebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/ConnectionManagerSupport.java @@ -31,7 +31,7 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Rossen Stoyanchev * @since 4.0 */ -public abstract class AbstractWebSocketConnectionManager implements SmartLifecycle { +public abstract class ConnectionManagerSupport implements SmartLifecycle { protected final Log logger = LogFactory.getLog(getClass()); @@ -49,7 +49,7 @@ public abstract class AbstractWebSocketConnectionManager implements SmartLifecyc private final Object lifecycleMonitor = new Object(); - public AbstractWebSocketConnectionManager(String uriTemplate, Object... uriVariables) { + public ConnectionManagerSupport(String uriTemplate, Object... uriVariables) { this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVariables).encode().toUri(); } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java index 09c0d06cf2..07edf7e1e3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java @@ -31,7 +31,7 @@ import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; * @author Rossen Stoyanchev * @since 4.0 */ -public class WebSocketConnectionManager extends AbstractWebSocketConnectionManager { +public class WebSocketConnectionManager extends ConnectionManagerSupport { private final WebSocketClient client; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java index 2674744013..6e9267f507 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java @@ -16,23 +16,29 @@ package org.springframework.websocket.client.endpoint; +import javax.websocket.ContainerProvider; import javax.websocket.Session; +import javax.websocket.WebSocketContainer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.websocket.client.ConnectionManagerSupport; import org.springframework.websocket.support.BeanCreatingHandlerProvider; /** * @author Rossen Stoyanchev * @since 4.0 */ -public class AnnotatedEndpointConnectionManager extends EndpointConnectionManagerSupport - implements BeanFactoryAware { +public class AnnotatedEndpointConnectionManager extends ConnectionManagerSupport implements BeanFactoryAware { + + private final Object endpoint; private final BeanCreatingHandlerProvider endpointProvider; - private final Object endpoint; + private WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer(); + + private Session session; public AnnotatedEndpointConnectionManager(Object endpoint, String uriTemplate, Object... uriVariables) { @@ -48,6 +54,14 @@ public class AnnotatedEndpointConnectionManager extends EndpointConnectionManage } + public void setWebSocketContainer(WebSocketContainer webSocketContainer) { + this.webSocketContainer = webSocketContainer; + } + + public WebSocketContainer getWebSocketContainer() { + return this.webSocketContainer; + } + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (this.endpointProvider != null) { @@ -58,8 +72,24 @@ public class AnnotatedEndpointConnectionManager extends EndpointConnectionManage @Override protected void openConnection() throws Exception { Object endpoint = (this.endpoint != null) ? this.endpoint : this.endpointProvider.getHandler(); - Session session = getWebSocketContainer().connectToServer(endpoint, getUri()); - updateSession(session); + this.session = this.webSocketContainer.connectToServer(endpoint, getUri()); + } + + @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()); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java index 3c2dcdefa7..9e96b160f2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java @@ -21,29 +21,36 @@ import java.util.List; import javax.websocket.ClientEndpointConfig; import javax.websocket.ClientEndpointConfig.Configurator; +import javax.websocket.ContainerProvider; import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.Endpoint; import javax.websocket.Extension; import javax.websocket.Session; +import javax.websocket.WebSocketContainer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; +import org.springframework.websocket.client.ConnectionManagerSupport; import org.springframework.websocket.support.BeanCreatingHandlerProvider; /** * @author Rossen Stoyanchev * @since 4.0 */ -public class EndpointConnectionManager extends EndpointConnectionManagerSupport implements BeanFactoryAware { +public class EndpointConnectionManager extends ConnectionManagerSupport implements BeanFactoryAware { - private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); + private final Endpoint endpoint; private final BeanCreatingHandlerProvider endpointProvider; - private final Endpoint endpoint; + private final ClientEndpointConfig.Builder configBuilder = ClientEndpointConfig.Builder.create(); + + private WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer(); + + private Session session; public EndpointConnectionManager(Endpoint endpoint, String uriTemplate, Object... uriVariables) { @@ -81,6 +88,14 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport this.configBuilder.configurator(configurator); } + public void setWebSocketContainer(WebSocketContainer webSocketContainer) { + this.webSocketContainer = webSocketContainer; + } + + public WebSocketContainer getWebSocketContainer() { + return this.webSocketContainer; + } + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (this.endpointProvider != null) { @@ -92,8 +107,24 @@ public class EndpointConnectionManager extends EndpointConnectionManagerSupport protected void openConnection() throws Exception { Endpoint endpoint = (this.endpoint != null) ? this.endpoint : this.endpointProvider.getHandler(); ClientEndpointConfig endpointConfig = this.configBuilder.build(); - Session session = getWebSocketContainer().connectToServer(endpoint, endpointConfig, getUri()); - updateSession(session); + this.session = getWebSocketContainer().connectToServer(endpoint, endpointConfig, getUri()); + } + + @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()); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java b/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java deleted file mode 100644 index dfdee7c966..0000000000 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManagerSupport.java +++ /dev/null @@ -1,70 +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.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()); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java index 5e658feccc..f4d4b90d2a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java @@ -44,7 +44,7 @@ import org.springframework.websocket.WebSocketHandler; *

* A container-specific {@link RequestUpgradeStrategy} is required since standard Java * WebSocket currently does not provide a way to initiate a WebSocket handshake. - * Currently available are implementations for Tomcat and Glassfish. + * Currently available are implementations for Tomcat and GlassFish. * * @author Rossen Stoyanchev * @since 4.0 @@ -218,7 +218,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { private static final boolean tomcatWebSocketPresent = ClassUtils.isPresent( "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", DefaultHandshakeHandler.class.getClassLoader()); - private static final boolean glassfishWebSocketPresent = ClassUtils.isPresent( + private static final boolean glassFishWebSocketPresent = ClassUtils.isPresent( "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", DefaultHandshakeHandler.class.getClassLoader()); private static final boolean jettyWebSocketPresent = ClassUtils.isPresent( @@ -229,8 +229,8 @@ public class DefaultHandshakeHandler implements HandshakeHandler { if (tomcatWebSocketPresent) { className = "org.springframework.websocket.server.support.TomcatRequestUpgradeStrategy"; } - else if (glassfishWebSocketPresent) { - className = "org.springframework.websocket.server.support.GlassfishRequestUpgradeStrategy"; + else if (glassFishWebSocketPresent) { + className = "org.springframework.websocket.server.support.GlassFishRequestUpgradeStrategy"; } else if (jettyWebSocketPresent) { className = "org.springframework.websocket.server.support.JettyRequestUpgradeStrategy"; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassFishRequestUpgradeStrategy.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassFishRequestUpgradeStrategy.java index 358813af80..1d25d7e5de 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassfishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassFishRequestUpgradeStrategy.java @@ -52,13 +52,13 @@ import org.springframework.websocket.server.HandshakeFailureException; import org.springframework.websocket.server.endpoint.EndpointRegistration; /** - * Glassfish support for upgrading an {@link HttpServletRequest} during a WebSocket + * GlassFish support for upgrading an {@link HttpServletRequest} during a WebSocket * handshake. * * @author Rossen Stoyanchev * @since 4.0 */ -public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStrategy { +public class GlassFishRequestUpgradeStrategy extends AbstractEndpointUpgradeStrategy { private final static Random random = new Random(); @@ -86,7 +86,7 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra engine.register(tyrusEndpoint); } catch (DeploymentException ex) { - throw new HandshakeFailureException("Failed to deploy endpoint in Glassfish", ex); + throw new HandshakeFailureException("Failed to deploy endpoint in GlassFish", ex); } try { @@ -140,13 +140,13 @@ public class GlassfishRequestUpgradeStrategy extends AbstractEndpointUpgradeStra private Connection createConnection(TyrusHttpUpgradeHandler handler, HttpServletResponse response) { try { String name = "org.glassfish.tyrus.servlet.ConnectionImpl"; - Class clazz = ClassUtils.forName(name, GlassfishRequestUpgradeStrategy.class.getClassLoader()); + Class clazz = ClassUtils.forName(name, GlassFishRequestUpgradeStrategy.class.getClassLoader()); Constructor constructor = clazz.getDeclaredConstructor(TyrusHttpUpgradeHandler.class, HttpServletResponse.class); ReflectionUtils.makeAccessible(constructor); return (Connection) constructor.newInstance(handler, response); } catch (Exception ex) { - throw new IllegalStateException("Failed to instantiate Glassfish connection", ex); + throw new IllegalStateException("Failed to instantiate GlassFish connection", ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandler.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java rename to spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandler.java index 64ed5c4bec..53cd3dc08c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandlerProxy.java +++ b/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandler.java @@ -48,23 +48,23 @@ import org.springframework.websocket.WebSocketSession; * @author Rossen Stoyanchev * @since 4.0 */ -public class PerConnectionWebSocketHandlerProxy implements WebSocketHandler, BeanFactoryAware { +public class PerConnectionWebSocketHandler implements WebSocketHandler, BeanFactoryAware { - private Log logger = LogFactory.getLog(PerConnectionWebSocketHandlerProxy.class); + private static final Log logger = LogFactory.getLog(PerConnectionWebSocketHandler.class); private final BeanCreatingHandlerProvider provider; - private Map handlers = + private final Map handlers = new ConcurrentHashMap(); - private boolean streaming; + private final boolean streaming; - public PerConnectionWebSocketHandlerProxy(Class handlerType) { + public PerConnectionWebSocketHandler(Class handlerType) { this(handlerType, false); } - public PerConnectionWebSocketHandlerProxy(Class handlerType, boolean isStreaming) { + public PerConnectionWebSocketHandler(Class handlerType, boolean isStreaming) { this.provider = new BeanCreatingHandlerProvider(handlerType); this.streaming = isStreaming; } From 97d225ba75914ce69a3384909cb8acf5d8648435 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Thu, 2 May 2013 20:39:54 -0400 Subject: [PATCH 49/51] Refactor packages org.springframework.websocket -> org.springframework.web.socket org.springframework.sockjs -> org.springframework.web.socket.sockjs Flatten .sockjs and .sockjs.server --- .../springframework/sockjs/package-info.java | 21 ---------- .../socket}/BinaryMessage.java | 2 +- .../socket}/CloseStatus.java | 2 +- .../socket}/TextMessage.java | 2 +- .../socket}/WebSocketHandler.java | 4 +- .../socket}/WebSocketMessage.java | 2 +- .../socket}/WebSocketSession.java | 2 +- .../AbstractWebSocketSesssionAdapter.java | 12 +++--- .../BinaryWebSocketHandlerAdapter.java | 10 ++--- .../adapter/ConfigurableWebSocketSession.java | 4 +- .../JettyWebSocketListenerAdapter.java | 12 +++--- .../adapter/JettyWebSocketSessionAdapter.java | 10 ++--- .../adapter/StandardEndpointAdapter.java | 12 +++--- .../StandardWebSocketSessionAdapter.java | 10 ++--- .../adapter/TextWebSocketHandlerAdapter.java | 10 ++--- .../adapter/WebSocketHandlerAdapter.java | 14 +++---- .../socket}/adapter/package-info.java | 4 +- .../client/ConnectionManagerSupport.java | 2 +- .../socket}/client/WebSocketClient.java | 6 +-- .../WebSocketConnectFailureException.java | 2 +- .../client/WebSocketConnectionManager.java | 10 ++--- .../AnnotatedEndpointConnectionManager.java | 6 +-- .../endpoint/EndpointConnectionManager.java | 6 +-- .../endpoint/StandardWebSocketClient.java | 14 +++---- .../WebSocketContainerFactoryBean.java | 2 +- .../socket}/client/endpoint/package-info.java | 6 +-- .../client/jetty/JettyWebSocketClient.java | 14 +++---- .../socket}/client/package-info.java | 2 +- .../socket}/package-info.java | 2 +- .../server/DefaultHandshakeHandler.java | 4 +- .../server/HandshakeFailureException.java | 2 +- .../socket}/server/HandshakeHandler.java | 4 +- .../server/RequestUpgradeStrategy.java | 4 +- .../server/endpoint/EndpointExporter.java | 2 +- .../server/endpoint/EndpointRegistration.java | 4 +- .../ServletServerContainerFactoryBean.java | 6 +-- .../server/endpoint/SpringConfigurator.java | 2 +- .../socket}/server/endpoint/package-info.java | 8 ++-- .../socket}/server/package-info.java | 2 +- .../AbstractEndpointUpgradeStrategy.java | 12 +++--- .../GlassFishRequestUpgradeStrategy.java | 6 +-- .../support/JettyRequestUpgradeStrategy.java | 12 +++--- .../ServerWebSocketSessionInitializer.java | 4 +- .../support/TomcatRequestUpgradeStrategy.java | 6 +-- .../support/WebSocketHttpRequestHandler.java | 12 +++--- .../socket}/server/support/package-info.java | 2 +- .../sockjs}/AbstractServerSockJsSession.java | 11 +++-- .../socket/sockjs}/AbstractSockJsService.java | 4 +- .../socket}/sockjs/AbstractSockJsSession.java | 10 ++--- .../sockjs}/ConfigurableTransportHandler.java | 2 +- .../socket/sockjs}/SockJsConfiguration.java | 2 +- .../socket/sockjs}/SockJsFrame.java | 2 +- .../sockjs}/SockJsRuntimeException.java | 2 +- .../socket/sockjs}/SockJsService.java | 4 +- .../socket}/sockjs/SockJsSessionFactory.java | 4 +- .../sockjs}/TransportErrorException.java | 4 +- .../socket/sockjs}/TransportHandler.java | 5 +-- .../socket/sockjs}/TransportType.java | 2 +- .../socket/sockjs}/package-info.java | 4 +- .../sockjs}/support/DefaultSockJsService.java | 42 +++++++++---------- .../support/SockJsHttpRequestHandler.java | 10 ++--- .../socket/sockjs}/support/package-info.java | 4 +- ...AbstractHttpReceivingTransportHandler.java | 14 +++---- .../AbstractHttpSendingTransportHandler.java | 18 ++++---- .../AbstractHttpServerSockJsSession.java | 18 ++++---- .../EventSourceTransportHandler.java | 10 ++--- .../transport/HtmlFileTransportHandler.java | 12 +++--- .../JsonpPollingTransportHandler.java | 12 +++--- .../transport/JsonpTransportHandler.java | 8 ++-- .../transport/PollingServerSockJsSession.java | 8 ++-- .../transport/SockJsWebSocketHandler.java | 14 +++---- .../StreamingServerSockJsSession.java | 12 +++--- .../WebSocketServerSockJsSession.java | 16 +++---- .../transport/WebSocketTransportHandler.java | 20 ++++----- .../transport/XhrPollingTransportHandler.java | 10 ++--- .../XhrStreamingTransportHandler.java | 10 ++--- .../transport/XhrTransportHandler.java | 4 +- .../sockjs}/transport/package-info.java | 4 +- .../support/BeanCreatingHandlerProvider.java | 2 +- .../ExceptionWebSocketHandlerDecorator.java | 10 ++--- .../LoggingWebSocketHandlerDecorator.java | 10 ++--- .../PerConnectionWebSocketHandler.java | 10 ++--- .../support/WebSocketHandlerDecorator.java | 10 ++--- .../support/DefaultSockJsServiceTests.java | 7 ++-- 84 files changed, 313 insertions(+), 335 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/sockjs/package-info.java rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/BinaryMessage.java (99%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/CloseStatus.java (99%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/TextMessage.java (96%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/WebSocketHandler.java (94%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/WebSocketMessage.java (98%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/WebSocketSession.java (98%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/AbstractWebSocketSesssionAdapter.java (87%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/BinaryWebSocketHandlerAdapter.java (81%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/ConfigurableWebSocketSession.java (90%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/JettyWebSocketListenerAdapter.java (90%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/JettyWebSocketSessionAdapter.java (91%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/StandardEndpointAdapter.java (91%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/StandardWebSocketSessionAdapter.java (91%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/TextWebSocketHandlerAdapter.java (80%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/WebSocketHandlerAdapter.java (84%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/adapter/package-info.java (86%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/ConnectionManagerSupport.java (98%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/WebSocketClient.java (89%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/WebSocketConnectFailureException.java (95%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/WebSocketConnectionManager.java (90%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/endpoint/AnnotatedEndpointConnectionManager.java (93%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/endpoint/EndpointConnectionManager.java (95%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/endpoint/StandardWebSocketClient.java (90%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/endpoint/WebSocketContainerFactoryBean.java (97%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/endpoint/package-info.java (77%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/jetty/JettyWebSocketClient.java (89%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/client/package-info.java (93%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/package-info.java (94%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/DefaultHandshakeHandler.java (98%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/HandshakeFailureException.java (97%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/HandshakeHandler.java (93%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/RequestUpgradeStrategy.java (94%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/endpoint/EndpointExporter.java (98%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/endpoint/EndpointRegistration.java (97%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/endpoint/ServletServerContainerFactoryBean.java (95%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/endpoint/SpringConfigurator.java (97%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/endpoint/package-info.java (75%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/package-info.java (93%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/AbstractEndpointUpgradeStrategy.java (83%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/GlassFishRequestUpgradeStrategy.java (96%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/JettyRequestUpgradeStrategy.java (90%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/ServerWebSocketSessionInitializer.java (89%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/TomcatRequestUpgradeStrategy.java (93%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/WebSocketHttpRequestHandler.java (87%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/server/support/package-info.java (92%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/AbstractServerSockJsSession.java (93%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/AbstractSockJsService.java (99%) rename spring-websocket/src/main/java/org/springframework/{ => web/socket}/sockjs/AbstractSockJsSession.java (95%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/ConfigurableTransportHandler.java (94%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/SockJsConfiguration.java (97%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/SockJsFrame.java (98%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/SockJsRuntimeException.java (95%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/SockJsService.java (90%) rename spring-websocket/src/main/java/org/springframework/{ => web/socket}/sockjs/SockJsSessionFactory.java (91%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/TransportErrorException.java (94%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/TransportHandler.java (86%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/TransportType.java (98%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/package-info.java (86%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/support/DefaultSockJsService.java (85%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/support/SockJsHttpRequestHandler.java (91%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/support/package-info.java (83%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/AbstractHttpReceivingTransportHandler.java (88%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/AbstractHttpSendingTransportHandler.java (83%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/AbstractHttpServerSockJsSession.java (89%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/EventSourceTransportHandler.java (84%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/HtmlFileTransportHandler.java (90%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/JsonpPollingTransportHandler.java (87%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/JsonpTransportHandler.java (89%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/PollingServerSockJsSession.java (84%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/SockJsWebSocketHandler.java (87%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/StreamingServerSockJsSession.java (86%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/WebSocketServerSockJsSession.java (85%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/WebSocketTransportHandler.java (81%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/XhrPollingTransportHandler.java (82%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/XhrStreamingTransportHandler.java (85%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/XhrTransportHandler.java (91%) rename spring-websocket/src/main/java/org/springframework/{sockjs/server => web/socket/sockjs}/transport/package-info.java (85%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/support/BeanCreatingHandlerProvider.java (98%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/support/ExceptionWebSocketHandlerDecorator.java (89%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/support/LoggingWebSocketHandlerDecorator.java (88%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/support/PerConnectionWebSocketHandler.java (94%) rename spring-websocket/src/main/java/org/springframework/{websocket => web/socket}/support/WebSocketHandlerDecorator.java (87%) rename spring-websocket/src/test/java/org/springframework/{sockjs/server => web/socket/sockjs}/support/DefaultSockJsServiceTests.java (86%) diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java b/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java deleted file mode 100644 index f7f8da613c..0000000000 --- a/spring-websocket/src/main/java/org/springframework/sockjs/package-info.java +++ /dev/null @@ -1,21 +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. - */ - -/** - * Common abstractions and Spring configuration support for the SockJS protocol. - */ -package org.springframework.sockjs; - diff --git a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java b/spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java similarity index 99% rename from spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java rename to spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java index 90d3efd691..cf6b35594c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/BinaryMessage.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/BinaryMessage.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.web.socket; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java b/spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java similarity index 99% rename from spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java rename to spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java index 8774fe46f9..9e63bdf8e6 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/CloseStatus.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/CloseStatus.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.web.socket; import org.eclipse.jetty.websocket.api.StatusCode; import org.springframework.util.Assert; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java b/spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java similarity index 96% rename from spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java rename to spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java index df78a6ef63..0bcc9f9b7c 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/TextMessage.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/TextMessage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.web.socket; import java.io.Reader; import java.io.StringReader; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java index 6331c003c6..ffdcdb2cf3 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.web.socket; /** * A handler for WebSocket messages and lifecycle events. @@ -23,7 +23,7 @@ package org.springframework.websocket; * it makes sense or alternatively let the exception bubble up in which case the exception * is logged and the session closed with {@link CloseStatus#SERVER_ERROR SERVER_ERROR(1011)} by default. * The exception handling strategy is provided by - * {@link org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator ExceptionWebSocketHandlerDecorator}, + * {@link org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator ExceptionWebSocketHandlerDecorator}, * which can be customized or replaced by decorating the {@link WebSocketHandler} with a * different decorator. * diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java rename to spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java index a5a06824b0..329c9d2092 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketMessage.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketMessage.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.web.socket; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java index 19b4cc56dd..c3e119b1f4 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/WebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket; +package org.springframework.web.socket; import java.io.IOException; import java.net.InetSocketAddress; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java similarity index 87% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java index 4b44badd96..606c37ac7a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/AbstractWebSocketSesssionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/AbstractWebSocketSesssionAdapter.java @@ -13,18 +13,18 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.io.IOException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java similarity index 81% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java index 7e467593e9..3f0bef0012 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/BinaryWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/BinaryWebSocketHandlerAdapter.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.io.IOException; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java index 63f724e558..f894640a60 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/ConfigurableWebSocketSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/ConfigurableWebSocketSession.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.net.URI; import java.security.Principal; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java index 4720d25784..e33359ff04 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketListenerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketListenerAdapter.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.springframework.util.Assert; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; /** * Adapts Spring's {@link WebSocketHandler} to Jetty's {@link WebSocketListener}. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java index d7eeb411ed..8a3a4e3403 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/JettyWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/JettyWebSocketSessionAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.io.IOException; import java.net.InetSocketAddress; @@ -24,10 +24,10 @@ import java.security.Principal; import org.eclipse.jetty.websocket.api.Session; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java index f7fd00b720..7d27878015 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardEndpointAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardEndpointAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.nio.ByteBuffer; @@ -26,11 +26,11 @@ import javax.websocket.MessageHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java index ad98adb90d..8c5ccaff90 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/StandardWebSocketSessionAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/StandardWebSocketSessionAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.io.IOException; import java.net.URI; @@ -24,10 +24,10 @@ import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCodes; import org.springframework.util.Assert; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; /** * A standard Java implementation of {@link WebSocketSession} that delegates to diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java similarity index 80% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java index 6fe21f7725..f207885f38 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/TextWebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/TextWebSocketHandlerAdapter.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; import java.io.IOException; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java similarity index 84% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java index fd88b052d6..7b97ca5876 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/WebSocketHandlerAdapter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/WebSocketHandlerAdapter.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; -import org.springframework.websocket.BinaryMessage; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/package-info.java similarity index 86% rename from spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/adapter/package-info.java index 59f29a08a3..c3e015ceed 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/adapter/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/adapter/package-info.java @@ -17,7 +17,7 @@ /** * Classes adapting Spring's WebSocket API classes to and from various WebSocket * implementations. Also contains convenient base classes for - * {@link org.springframework.websocket.WebSocketHandler} implementations. + * {@link org.springframework.web.socket.WebSocketHandler} implementations. */ -package org.springframework.websocket.adapter; +package org.springframework.web.socket.adapter; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/ConnectionManagerSupport.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/client/ConnectionManagerSupport.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java index abf19ce491..6cfe503d61 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/ConnectionManagerSupport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/ConnectionManagerSupport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client; +package org.springframework.web.socket.client; import java.net.URI; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java index 6348f34dc9..9bef4eefb2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketClient.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.websocket.client; +package org.springframework.web.socket.client; import java.net.URI; import org.springframework.http.HttpHeaders; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; /** * Contract for programmatically starting a WebSocket handshake request. For most cases it diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java index 0ed7c32e8e..184a3e195a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectFailureException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client; +package org.springframework.web.socket.client; import org.springframework.core.NestedRuntimeException; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java index 07edf7e1e3..1ee44acf41 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/WebSocketConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/WebSocketConnectionManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client; +package org.springframework.web.socket.client; import java.util.ArrayList; import java.util.List; @@ -22,10 +22,10 @@ import java.util.List; import org.springframework.context.SmartLifecycle; import org.springframework.http.HttpHeaders; import org.springframework.util.CollectionUtils; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; -import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/AnnotatedEndpointConnectionManager.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/AnnotatedEndpointConnectionManager.java index 6e9267f507..36e713edca 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/AnnotatedEndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/AnnotatedEndpointConnectionManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client.endpoint; +package org.springframework.web.socket.client.endpoint; import javax.websocket.ContainerProvider; import javax.websocket.Session; @@ -23,8 +23,8 @@ import javax.websocket.WebSocketContainer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.websocket.client.ConnectionManagerSupport; -import org.springframework.websocket.support.BeanCreatingHandlerProvider; +import org.springframework.web.socket.client.ConnectionManagerSupport; +import org.springframework.web.socket.support.BeanCreatingHandlerProvider; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java index 9e96b160f2..92fc73b683 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/EndpointConnectionManager.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/EndpointConnectionManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client.endpoint; +package org.springframework.web.socket.client.endpoint; import java.util.Arrays; import java.util.List; @@ -33,8 +33,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; -import org.springframework.websocket.client.ConnectionManagerSupport; -import org.springframework.websocket.support.BeanCreatingHandlerProvider; +import org.springframework.web.socket.client.ConnectionManagerSupport; +import org.springframework.web.socket.support.BeanCreatingHandlerProvider; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java index 8a1333c0fa..0734f7c085 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client.endpoint; +package org.springframework.web.socket.client.endpoint; import java.net.URI; import java.util.Arrays; @@ -33,14 +33,14 @@ import javax.websocket.WebSocketContainer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpHeaders; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.adapter.StandardEndpointAdapter; +import org.springframework.web.socket.adapter.StandardWebSocketSessionAdapter; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.client.WebSocketConnectFailureException; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.adapter.StandardEndpointAdapter; -import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter; -import org.springframework.websocket.client.WebSocketClient; -import org.springframework.websocket.client.WebSocketConnectFailureException; /** * A standard Java {@link WebSocketClient}. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/WebSocketContainerFactoryBean.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/WebSocketContainerFactoryBean.java index 944987eb2e..10dd2c71df 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/WebSocketContainerFactoryBean.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/WebSocketContainerFactoryBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client.endpoint; +package org.springframework.web.socket.client.endpoint; import javax.websocket.ContainerProvider; import javax.websocket.WebSocketContainer; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/package-info.java similarity index 77% rename from spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/package-info.java index af6c74b7d2..c0904051a0 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/package-info.java @@ -16,9 +16,9 @@ /** * 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} + * {@link org.springframework.web.socket.client.endpoint.EndpointConnectionManager} and + * {@link org.springframework.web.socket.client.endpoint.AnnotatedEndpointConnectionManager} * for connecting to server endpoints using type-based or annotated endpoints respectively. */ -package org.springframework.websocket.client.endpoint; +package org.springframework.web.socket.client.endpoint; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java index 0f96c94836..6cc19bf561 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/jetty/JettyWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.client.jetty; +package org.springframework.web.socket.client.jetty; import java.net.URI; @@ -22,14 +22,14 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.SmartLifecycle; import org.springframework.http.HttpHeaders; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.adapter.JettyWebSocketListenerAdapter; +import org.springframework.web.socket.adapter.JettyWebSocketSessionAdapter; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.client.WebSocketConnectFailureException; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter; -import org.springframework.websocket.adapter.JettyWebSocketSessionAdapter; -import org.springframework.websocket.client.WebSocketClient; -import org.springframework.websocket.client.WebSocketConnectFailureException; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/package-info.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/client/package-info.java index e7860b5cea..3304705b56 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/client/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/package-info.java @@ -17,5 +17,5 @@ /** * Server-side abstractions for WebSocket applications. */ -package org.springframework.websocket.client; +package org.springframework.web.socket.client; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/package-info.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/websocket/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/package-info.java index c1f27cbcb7..e5f39989c2 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/package-info.java @@ -17,5 +17,5 @@ /** * Common abstractions and Spring configuration support for WebSocket applications. */ -package org.springframework.websocket; +package org.springframework.web.socket; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java index f4d4b90d2a..8390c3079e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server; +package org.springframework.web.socket.server; import java.io.IOException; import java.nio.charset.Charset; @@ -37,7 +37,7 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * TODO diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java index 0c17392d39..9284737818 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server; +package org.springframework.web.socket.server; import org.springframework.core.NestedRuntimeException; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java index 4a2df4928c..867812b160 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.websocket.server; +package org.springframework.web.socket.server; import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * Contract for processing a WebSocket handshake request. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java index f717ac81e3..1421f51984 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.websocket.server; +package org.springframework.web.socket.server; import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * A strategy for performing container-specific steps to upgrade an HTTP request during a diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/EndpointExporter.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/EndpointExporter.java index aa9d3829ee..0e1ce1029e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointExporter.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/EndpointExporter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.web.socket.server.endpoint; import java.lang.reflect.Method; import java.util.ArrayList; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/EndpointRegistration.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/EndpointRegistration.java index 628243166e..40ec854e65 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/EndpointRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/EndpointRegistration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.web.socket.server.endpoint; import java.util.ArrayList; import java.util.HashMap; @@ -33,7 +33,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; -import org.springframework.websocket.support.BeanCreatingHandlerProvider; +import org.springframework.web.socket.support.BeanCreatingHandlerProvider; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java index 1d7c64c0e7..7ea6cefe7f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/ServletServerContainerFactoryBean.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/ServletServerContainerFactoryBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.web.socket.server.endpoint; import javax.servlet.ServletContext; import javax.websocket.WebSocketContainer; @@ -22,10 +22,10 @@ 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; +import org.springframework.web.socket.server.DefaultHandshakeHandler; +import org.springframework.web.socket.sockjs.SockJsService; /** * A FactoryBean for {@link javax.websocket.server.ServerContainer}. Since diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java index e553f247cf..dd6efb56ec 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/SpringConfigurator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/SpringConfigurator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.web.socket.server.endpoint; import java.util.Map; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java similarity index 75% rename from spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java index e52ce874a1..f73eb69625 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java @@ -16,13 +16,13 @@ /** * Server classes for use with standard Java WebSocket endpoints including - * {@link org.springframework.websocket.server.endpoint.EndpointRegistration} and - * {@link org.springframework.websocket.server.endpoint.EndpointExporter} for + * {@link org.springframework.web.socket.server.endpoint.EndpointRegistration} and + * {@link org.springframework.web.socket.server.endpoint.EndpointExporter} for * registering type-based endpoints, - * {@link org.springframework.websocket.server.endpoint.SpringConfigurator} for + * {@link org.springframework.web.socket.server.endpoint.SpringConfigurator} for * instantiating annotated endpoints through Spring, and * {@link org.springframework.websocket.server.support.EndpointHandshakeHandler} * for integrating endpoints into HTTP request processing. */ -package org.springframework.websocket.server.endpoint; +package org.springframework.web.socket.server.endpoint; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/package-info.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/package-info.java index e70c9d1436..b1560a6364 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/package-info.java @@ -17,5 +17,5 @@ /** * Server-side abstractions for WebSocket applications. */ -package org.springframework.websocket.server; +package org.springframework.web.socket.server; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractEndpointUpgradeStrategy.java similarity index 83% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractEndpointUpgradeStrategy.java index 324ac4e79f..bce86dd46d 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/AbstractEndpointUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractEndpointUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; import java.io.IOException; @@ -24,11 +24,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.adapter.StandardEndpointAdapter; -import org.springframework.websocket.adapter.StandardWebSocketSessionAdapter; -import org.springframework.websocket.server.HandshakeFailureException; -import org.springframework.websocket.server.RequestUpgradeStrategy; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.adapter.StandardEndpointAdapter; +import org.springframework.web.socket.adapter.StandardWebSocketSessionAdapter; +import org.springframework.web.socket.server.HandshakeFailureException; +import org.springframework.web.socket.server.RequestUpgradeStrategy; /** * A {@link RequestUpgradeStrategy} that supports WebSocket handlers of type diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassFishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java similarity index 96% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassFishRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java index 1d25d7e5de..a327e0b6bb 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/GlassFishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; import java.io.IOException; import java.lang.reflect.Constructor; @@ -48,8 +48,8 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; -import org.springframework.websocket.server.HandshakeFailureException; -import org.springframework.websocket.server.endpoint.EndpointRegistration; +import org.springframework.web.socket.server.HandshakeFailureException; +import org.springframework.web.socket.server.endpoint.EndpointRegistration; /** * GlassFish support for upgrading an {@link HttpServletRequest} during a WebSocket diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java index 707e858033..0a428f3c5e 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; import java.io.IOException; @@ -32,11 +32,11 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.adapter.JettyWebSocketListenerAdapter; -import org.springframework.websocket.adapter.JettyWebSocketSessionAdapter; -import org.springframework.websocket.server.HandshakeFailureException; -import org.springframework.websocket.server.RequestUpgradeStrategy; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.adapter.JettyWebSocketListenerAdapter; +import org.springframework.web.socket.adapter.JettyWebSocketSessionAdapter; +import org.springframework.web.socket.server.HandshakeFailureException; +import org.springframework.web.socket.server.RequestUpgradeStrategy; /** * {@link RequestUpgradeStrategy} for use with Jetty. Based on Jetty's internal diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java index 412bd5841b..60e1f70784 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/ServerWebSocketSessionInitializer.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/ServerWebSocketSessionInitializer.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.adapter.ConfigurableWebSocketSession; +import org.springframework.web.socket.adapter.ConfigurableWebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java index c5a262b3a8..1236c36c55 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; import java.io.IOException; import java.lang.reflect.Method; @@ -32,8 +32,8 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; -import org.springframework.websocket.server.HandshakeFailureException; -import org.springframework.websocket.server.endpoint.EndpointRegistration; +import org.springframework.web.socket.server.HandshakeFailureException; +import org.springframework.web.socket.server.endpoint.EndpointRegistration; /** * Tomcat support for upgrading an {@link HttpServletRequest} during a WebSocket handshake. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java similarity index 87% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java index 0b8b8c2d8b..6c0406171f 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/WebSocketHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/WebSocketHttpRequestHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; import java.io.IOException; @@ -28,11 +28,11 @@ import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.server.DefaultHandshakeHandler; -import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; -import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.DefaultHandshakeHandler; +import org.springframework.web.socket.server.HandshakeHandler; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator; /** * An {@link HttpRequestHandler} that wraps the invocation of a {@link HandshakeHandler}. diff --git a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/package-info.java similarity index 92% rename from spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/server/support/package-info.java index 8b29c9295c..41c16bb7b7 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/server/support/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/package-info.java @@ -18,5 +18,5 @@ * Server-side support classes including container-specific strategies for upgrading a * request. */ -package org.springframework.websocket.server.support; +package org.springframework.web.socket.server.support; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java similarity index 93% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java index 554d813a55..725d3c3ac6 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import java.io.EOFException; import java.io.IOException; @@ -22,12 +22,11 @@ import java.net.SocketException; import java.util.Date; import java.util.concurrent.ScheduledFuture; -import org.springframework.sockjs.AbstractSockJsSession; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; /** * Provides partial implementations of {@link SockJsSession} methods to send messages, diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java similarity index 99% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java index 15107e5a05..31f15781ed 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import java.io.IOException; import java.nio.charset.Charset; @@ -36,7 +36,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.DigestUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * Provides support for SockJS configuration options and serves the static SockJS URLs. diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java index 671f4d993b..c7009b9c96 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs; +package org.springframework.web.socket.sockjs; import java.io.IOException; import java.net.URI; @@ -23,10 +23,10 @@ import java.security.Principal; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.adapter.ConfigurableWebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.adapter.ConfigurableWebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/ConfigurableTransportHandler.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/ConfigurableTransportHandler.java index 44d55ff0c5..5e10c7bf01 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/ConfigurableTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/ConfigurableTransportHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java similarity index 97% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java index 61de30afa7..3fbf767130 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsConfiguration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import org.springframework.scheduling.TaskScheduler; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java index 5552757580..436e31f79b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import java.nio.charset.Charset; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsRuntimeException.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsRuntimeException.java similarity index 95% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsRuntimeException.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsRuntimeException.java index e9026012bb..3803bb9634 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsRuntimeException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsRuntimeException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import org.springframework.core.NestedRuntimeException; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java index 91c8a3d52e..915ab840fb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java index f565808dd7..5f78084550 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/SockJsSessionFactory.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsSessionFactory.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.springframework.sockjs; +package org.springframework.web.socket.sockjs; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * A factory for creating a SockJS session. diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java index 90eb5e8170..9b35daaa4d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportErrorException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportErrorException.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import org.springframework.core.NestedRuntimeException; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java similarity index 86% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java index e01af545be..82e1a03bb8 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.AbstractSockJsSession; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java index f3c9e5c6e2..05f89bac38 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; import java.util.Arrays; import java.util.List; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/package-info.java similarity index 86% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/package-info.java index 167717047a..d523c864b3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/package-info.java @@ -15,7 +15,7 @@ */ /** - * Server-side SockJS abstractions and base classes. + * Common abstractions for the SockJS protocol. */ -package org.springframework.sockjs.server; +package org.springframework.web.socket.sockjs; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java similarity index 85% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java index 53edca9c5d..c0fec3e6bb 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.support; +package org.springframework.web.socket.sockjs.support; import java.io.IOException; import java.util.Arrays; @@ -34,27 +34,27 @@ import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.scheduling.TaskScheduler; -import org.springframework.sockjs.AbstractSockJsSession; -import org.springframework.sockjs.SockJsSessionFactory; -import org.springframework.sockjs.server.AbstractSockJsService; -import org.springframework.sockjs.server.ConfigurableTransportHandler; -import org.springframework.sockjs.server.SockJsService; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.sockjs.server.TransportHandler; -import org.springframework.sockjs.server.TransportType; -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.CollectionUtils; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.server.DefaultHandshakeHandler; -import org.springframework.websocket.server.HandshakeHandler; -import org.springframework.websocket.server.support.ServerWebSocketSessionInitializer; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.DefaultHandshakeHandler; +import org.springframework.web.socket.server.HandshakeHandler; +import org.springframework.web.socket.server.support.ServerWebSocketSessionInitializer; +import org.springframework.web.socket.sockjs.AbstractSockJsService; +import org.springframework.web.socket.sockjs.AbstractSockJsSession; +import org.springframework.web.socket.sockjs.ConfigurableTransportHandler; +import org.springframework.web.socket.sockjs.SockJsService; +import org.springframework.web.socket.sockjs.SockJsSessionFactory; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.TransportHandler; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.transport.EventSourceTransportHandler; +import org.springframework.web.socket.sockjs.transport.HtmlFileTransportHandler; +import org.springframework.web.socket.sockjs.transport.JsonpPollingTransportHandler; +import org.springframework.web.socket.sockjs.transport.JsonpTransportHandler; +import org.springframework.web.socket.sockjs.transport.WebSocketTransportHandler; +import org.springframework.web.socket.sockjs.transport.XhrPollingTransportHandler; +import org.springframework.web.socket.sockjs.transport.XhrStreamingTransportHandler; +import org.springframework.web.socket.sockjs.transport.XhrTransportHandler; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java index 241e550a38..336df01ca0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.support; +package org.springframework.web.socket.sockjs.support; import java.io.IOException; @@ -26,14 +26,14 @@ import org.springframework.http.server.AsyncServletServerHttpRequest; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.sockjs.server.SockJsService; import org.springframework.util.Assert; import org.springframework.web.HttpRequestHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.SockJsService; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator; import org.springframework.web.util.NestedServletException; import org.springframework.web.util.UrlPathHelper; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; -import org.springframework.websocket.support.LoggingWebSocketHandlerDecorator; /** * @author Rossen Stoyanchev diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java similarity index 83% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java index 1d3b9d2052..b5a32e8776 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/support/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java @@ -16,9 +16,9 @@ /** * Server-side SockJS classes including a - * {@link org.springframework.sockjs.server.support.DefaultSockJsService} implementation + * {@link org.springframework.web.socket.sockjs.support.DefaultSockJsService} implementation * as well as a Spring MVC HandlerMapping mapping SockJS services to incoming requests. * */ -package org.springframework.sockjs.server.support; +package org.springframework.web.socket.sockjs.support; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java similarity index 88% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java index 078c354eee..313794ea1c 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import java.nio.charset.Charset; @@ -26,12 +26,12 @@ 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.AbstractSockJsSession; -import org.springframework.sockjs.server.SockJsRuntimeException; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.sockjs.server.TransportHandler; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.AbstractSockJsSession; +import org.springframework.web.socket.sockjs.SockJsRuntimeException; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.TransportHandler; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java similarity index 83% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java index 681f3c66ce..8c76b8c14e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpSendingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; @@ -23,14 +23,14 @@ import org.apache.commons.logging.LogFactory; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.AbstractSockJsSession; -import org.springframework.sockjs.SockJsSessionFactory; -import org.springframework.sockjs.server.ConfigurableTransportHandler; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.AbstractSockJsSession; +import org.springframework.web.socket.sockjs.ConfigurableTransportHandler; +import org.springframework.web.socket.sockjs.SockJsConfiguration; +import org.springframework.web.socket.sockjs.SockJsFrame; +import org.springframework.web.socket.sockjs.SockJsSessionFactory; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; /** * TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java index 856296ceb6..c04e4c54ee 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; @@ -23,15 +23,15 @@ 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.server.AbstractServerSockJsSession; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportErrorException; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.support.ExceptionWebSocketHandlerDecorator; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.AbstractServerSockJsSession; +import org.springframework.web.socket.sockjs.SockJsConfiguration; +import org.springframework.web.socket.sockjs.SockJsFrame; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; +import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; /** * An abstract base class for use with HTTP-based transports. diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java similarity index 84% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java index bae88528b8..f469628c05 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/EventSourceTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import java.nio.charset.Charset; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; -import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; /** * TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java similarity index 90% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java index 7070a88fea..acf2c877fc 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/HtmlFileTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import java.nio.charset.Charset; @@ -23,14 +23,14 @@ 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.server.SockJsFrame.DefaultFrameFormat; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; import org.springframework.web.util.JavaScriptUtils; -import org.springframework.websocket.WebSocketHandler; /** * TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java similarity index 87% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java index fde554f0d1..e155c3a5ac 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.nio.charset.Charset; @@ -22,14 +22,14 @@ 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.server.SockJsFrame; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.SockJsFrame; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; import org.springframework.web.util.JavaScriptUtils; -import org.springframework.websocket.WebSocketHandler; /** * TODO diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java index b0d74454ef..6cedeea2c0 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/JsonpTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; @@ -22,9 +22,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.AbstractSockJsSession; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.sockjs.server.TransportType; +import org.springframework.web.socket.sockjs.AbstractSockJsSession; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.TransportType; public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingServerSockJsSession.java similarity index 84% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingServerSockJsSession.java index 7a70a5791f..75e6945089 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/PollingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingServerSockJsSession.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.SockJsConfiguration; +import org.springframework.web.socket.sockjs.SockJsFrame; public class PollingServerSockJsSession extends AbstractHttpServerSockJsSession { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java similarity index 87% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java index a5f41dd5c8..147ae65d3d 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/SockJsWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/SockJsWebSocketHandler.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.sockjs.server.SockJsConfiguration; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; -import org.springframework.websocket.adapter.TextWebSocketHandlerAdapter; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter; +import org.springframework.web.socket.sockjs.SockJsConfiguration; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java similarity index 86% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java index bc5be9cab8..f01bb94c99 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.SockJsConfiguration; +import org.springframework.web.socket.sockjs.SockJsFrame; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSession { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java similarity index 85% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java index 5e45d6a269..fb91266a2b 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java @@ -14,18 +14,18 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; -import org.springframework.sockjs.server.AbstractServerSockJsSession; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.SockJsFrame; import org.springframework.util.StringUtils; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.TextMessage; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.sockjs.AbstractServerSockJsSession; +import org.springframework.web.socket.sockjs.SockJsConfiguration; +import org.springframework.web.socket.sockjs.SockJsFrame; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java similarity index 81% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java index de077f2e17..5db64bddaf 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; -import org.springframework.sockjs.AbstractSockJsSession; -import org.springframework.sockjs.SockJsSessionFactory; -import org.springframework.sockjs.server.ConfigurableTransportHandler; -import org.springframework.sockjs.server.SockJsConfiguration; -import org.springframework.sockjs.server.TransportErrorException; -import org.springframework.sockjs.server.TransportHandler; -import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.server.HandshakeHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeHandler; +import org.springframework.web.socket.sockjs.AbstractSockJsSession; +import org.springframework.web.socket.sockjs.ConfigurableTransportHandler; +import org.springframework.web.socket.sockjs.SockJsConfiguration; +import org.springframework.web.socket.sockjs.SockJsSessionFactory; +import org.springframework.web.socket.sockjs.TransportErrorException; +import org.springframework.web.socket.sockjs.TransportHandler; +import org.springframework.web.socket.sockjs.TransportType; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java similarity index 82% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java index b25818c67f..93adce579e 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrPollingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.nio.charset.Charset; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; -import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java similarity index 85% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java index 84228029ab..f99f27bdf3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrStreamingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import java.nio.charset.Charset; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; -import org.springframework.sockjs.server.SockJsFrame.DefaultFrameFormat; -import org.springframework.sockjs.server.SockJsFrame.FrameFormat; -import org.springframework.sockjs.server.TransportType; import org.springframework.util.Assert; -import org.springframework.websocket.WebSocketHandler; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat; +import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat; /** diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java similarity index 91% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java index e38417ac5a..9d752c4496 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/XhrTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; import java.io.IOException; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; -import org.springframework.sockjs.server.TransportType; +import org.springframework.web.socket.sockjs.TransportType; public class XhrTransportHandler extends AbstractHttpReceivingTransportHandler { diff --git a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java similarity index 85% rename from spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java index c35beb3138..976e512ad3 100644 --- a/spring-websocket/src/main/java/org/springframework/sockjs/server/transport/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java @@ -16,11 +16,11 @@ /** * Server-side support for SockJS transports including - * {@link org.springframework.sockjs.server.TransportHandler} implementations + * {@link org.springframework.web.socket.sockjs.TransportHandler} implementations * for processing incoming requests and their * {@link org.springframework.sockjs.SockJsSession} counterparts for * sending messages over the various transports. * */ -package org.springframework.sockjs.server.transport; +package org.springframework.web.socket.sockjs.transport; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java similarity index 98% rename from spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java rename to spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java index 4ed2092447..9f53215b37 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/BeanCreatingHandlerProvider.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/BeanCreatingHandlerProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.web.socket.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java similarity index 89% rename from spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java rename to spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java index 0e82d03249..bb9f84409a 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/ExceptionWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/ExceptionWebSocketHandlerDecorator.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.web.socket.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java similarity index 88% rename from spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java rename to spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java index 7e4fa8bd8c..be5433e2a8 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/LoggingWebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/LoggingWebSocketHandlerDecorator.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.web.socket.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/PerConnectionWebSocketHandler.java similarity index 94% rename from spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandler.java rename to spring-websocket/src/main/java/org/springframework/web/socket/support/PerConnectionWebSocketHandler.java index 53cd3dc08c..8727f1afaf 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/PerConnectionWebSocketHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/PerConnectionWebSocketHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.web.socket.support; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -25,10 +25,10 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; /** * A {@link WebSocketHandler} that initializes and destroys a {@link WebSocketHandler} diff --git a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java b/spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java similarity index 87% rename from spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java rename to spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java index 5fb9d6d7d3..96ab730434 100644 --- a/spring-websocket/src/main/java/org/springframework/websocket/support/WebSocketHandlerDecorator.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/support/WebSocketHandlerDecorator.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package org.springframework.websocket.support; +package org.springframework.web.socket.support; import org.springframework.util.Assert; -import org.springframework.websocket.CloseStatus; -import org.springframework.websocket.WebSocketHandler; -import org.springframework.websocket.WebSocketMessage; -import org.springframework.websocket.WebSocketSession; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; /** diff --git a/spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java similarity index 86% rename from spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java rename to spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java index 77ee2edf15..a135e51a40 100644 --- a/spring-websocket/src/test/java/org/springframework/sockjs/server/support/DefaultSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java @@ -14,14 +14,15 @@ * limitations under the License. */ -package org.springframework.sockjs.server.support; +package org.springframework.web.socket.sockjs.support; import java.util.Map; import org.junit.Test; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.sockjs.server.TransportHandler; -import org.springframework.sockjs.server.TransportType; +import org.springframework.web.socket.sockjs.TransportHandler; +import org.springframework.web.socket.sockjs.TransportType; +import org.springframework.web.socket.sockjs.support.DefaultSockJsService; import static org.junit.Assert.*; From 7845ebc428e9705a3a4404fff47e434effd5465f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 5 May 2013 20:51:37 -0400 Subject: [PATCH 50/51] Add SockJS path detection --- .../http/server/AsyncServerHttpRequest.java | 1 + .../server/AsyncServletServerHttpRequest.java | 6 +- .../endpoint/StandardWebSocketClient.java | 10 +- .../client/jetty/JettyWebSocketClient.java | 10 +- .../server/DefaultHandshakeHandler.java | 8 +- .../socket/server/endpoint/package-info.java | 4 +- .../sockjs/AbstractServerSockJsSession.java | 7 +- .../socket/sockjs/AbstractSockJsService.java | 133 ++++++++++++++-- .../socket/sockjs/AbstractSockJsSession.java | 4 +- .../web/socket/sockjs/SockJsFrame.java | 50 +++--- .../web/socket/sockjs/SockJsService.java | 5 +- .../web/socket/sockjs/TransportType.java | 20 ++- .../sockjs/support/DefaultSockJsService.java | 2 +- .../support/SockJsHttpRequestHandler.java | 39 +---- ...AbstractHttpReceivingTransportHandler.java | 13 +- .../AbstractHttpServerSockJsSession.java | 5 +- .../StreamingServerSockJsSession.java | 6 +- .../WebSocketServerSockJsSession.java | 2 +- .../web/socket/AbstractHttpRequestTests.java | 61 ++++++++ .../sockjs/AbstractSockJsServiceTests.java | 145 ++++++++++++++++++ .../web/socket/sockjs/StubSockJsConfig.java | 59 +++++++ .../web/socket/sockjs/StubTaskScheduler.java | 87 +++++++++++ .../web/socket/sockjs/TransportTypeTests.java | 42 +++++ .../support/DefaultSockJsServiceTests.java | 38 ++++- spring-websocket/src/test/resources/log4j.xml | 30 ++++ 25 files changed, 654 insertions(+), 133 deletions(-) create mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java create mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java create mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java create mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java create mode 100644 spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java create mode 100644 spring-websocket/src/test/resources/log4j.xml diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java index 0253ac66e1..bbf973d5d8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java @@ -16,6 +16,7 @@ package org.springframework.http.server; + /** * TODO.. */ diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java index 1e53fc7abf..996a7d3fdc 100644 --- a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java @@ -100,11 +100,6 @@ public class AsyncServletServerHttpRequest extends ServletServerHttpRequest } } - public void dispatch() { - Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); - this.asyncContext.dispatch(); - } - public void completeAsync() { Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext"); if (isAsyncStarted() && !isAsyncCompleted()) { @@ -112,6 +107,7 @@ public class AsyncServletServerHttpRequest extends ServletServerHttpRequest } } + // --------------------------------------------------------------------- // Implementation of AsyncListener methods // --------------------------------------------------------------------- diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java index 0734f7c085..21678e12ef 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java @@ -75,17 +75,9 @@ public class StandardWebSocketClient implements WebSocketClient { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException { - return doHandshake(webSocketHandler, httpHeaders, UriComponentsBuilder.fromUri(uri).build()); - } - - public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, - final HttpHeaders httpHeaders, UriComponents uriComponents) throws WebSocketConnectFailureException { - - URI uri = uriComponents.toUri(); - StandardWebSocketSessionAdapter session = new StandardWebSocketSessionAdapter(); session.setUri(uri); - session.setRemoteHostName(uriComponents.getHost()); + session.setRemoteHostName(uri.getHost()); Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler, session); ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create(); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java index 6cc19bf561..d5bb04ddfd 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java @@ -133,19 +133,11 @@ public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle { public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri) throws WebSocketConnectFailureException { - return doHandshake(webSocketHandler, headers, UriComponentsBuilder.fromUri(uri).build()); - } - - public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, UriComponents uriComponents) - throws WebSocketConnectFailureException { - // TODO: populate headers - URI uri = uriComponents.toUri(); - JettyWebSocketSessionAdapter session = new JettyWebSocketSessionAdapter(); session.setUri(uri); - session.setRemoteHostName(uriComponents.getHost()); + session.setRemoteHostName(uri.getHost()); JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler, session); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java index 8390c3079e..f3948b795e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java @@ -149,7 +149,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler { protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { logger.debug("Invalid Upgrade header " + request.getHeaders().getUpgrade()); response.setStatusCode(HttpStatus.BAD_REQUEST); - response.getBody().write("Can \"Upgrade\" only to \"websocket\".".getBytes("UTF-8")); + response.getBody().write("Can \"Upgrade\" only to \"WebSocket\".".getBytes("UTF-8")); } protected void handleInvalidConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException { @@ -227,13 +227,13 @@ public class DefaultHandshakeHandler implements HandshakeHandler { private RequestUpgradeStrategy create() { String className; if (tomcatWebSocketPresent) { - className = "org.springframework.websocket.server.support.TomcatRequestUpgradeStrategy"; + className = "org.springframework.web.socket.server.support.TomcatRequestUpgradeStrategy"; } else if (glassFishWebSocketPresent) { - className = "org.springframework.websocket.server.support.GlassFishRequestUpgradeStrategy"; + className = "org.springframework.web.socket.server.support.GlassFishRequestUpgradeStrategy"; } else if (jettyWebSocketPresent) { - className = "org.springframework.websocket.server.support.JettyRequestUpgradeStrategy"; + className = "org.springframework.web.socket.server.support.JettyRequestUpgradeStrategy"; } else { throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName()); diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java index f73eb69625..5b5a29efbe 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java @@ -20,9 +20,7 @@ * {@link org.springframework.web.socket.server.endpoint.EndpointExporter} for * registering type-based endpoints, * {@link org.springframework.web.socket.server.endpoint.SpringConfigurator} for - * instantiating annotated endpoints through Spring, and - * {@link org.springframework.websocket.server.support.EndpointHandshakeHandler} - * for integrating endpoints into HTTP request processing. + * instantiating annotated endpoints through Spring. */ package org.springframework.web.socket.server.endpoint; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java index 725d3c3ac6..710e5e7d33 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java @@ -83,7 +83,6 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession disconnect(status); } - // TODO: close status/reason protected abstract void disconnect(CloseStatus status) throws IOException; /** @@ -104,12 +103,14 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession else { logger.warn("Terminating connection due to failure to send message: " + ex.getMessage()); } - close(); + disconnect(CloseStatus.SERVER_ERROR); + close(CloseStatus.SERVER_ERROR); throw ex; } catch (Throwable ex) { logger.warn("Terminating connection due to failure to send message: " + ex.getMessage()); - close(); + disconnect(CloseStatus.SERVER_ERROR); + close(CloseStatus.SERVER_ERROR); throw new SockJsRuntimeException("Failed to write " + frame, ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java index 31f15781ed..0abf1cc523 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java @@ -17,11 +17,16 @@ package org.springframework.web.socket.sockjs; import java.io.IOException; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Random; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -39,7 +44,18 @@ import org.springframework.util.StringUtils; import org.springframework.web.socket.WebSocketHandler; /** - * Provides support for SockJS configuration options and serves the static SockJS URLs. + * An abstract class for {@link SockJsService} implementations. Provides configuration + * support, SockJS path resolution, and processing for static SockJS requests (e.g. + * "/info", "/iframe.html", etc). Sub-classes are responsible for handling transport + * requests. + * + *

+ * It is expected that this service is mapped correctly to one or more prefixes such as + * "/echo" including all sub-URLs (e.g. "/echo/**"). A SockJS service itself is generally + * unaware of request mapping details but nevertheless must be able to extract the SockJS + * path, which is the portion of the request path following the prefix. In most cases, + * this class can auto-detect the SockJS path but you can also explicitly configure the + * list of valid prefixes with {@link #setValidSockJsPrefixes(String...)}. * * @author Rossen Stoyanchev * @since 4.0 @@ -51,7 +67,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf private static final int ONE_YEAR = 365 * 24 * 60 * 60; - private String name = "SockJS Service " + ObjectUtils.getIdentityHexString(this); + private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this); private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js"; @@ -67,6 +83,9 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf private final TaskScheduler taskScheduler; + private final List sockJsPrefixes = new ArrayList(); + + private final Set sockJsPathCache = new CopyOnWriteArraySet(); public AbstractSockJsService(TaskScheduler scheduler) { @@ -85,6 +104,38 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf return this.name; } + /** + * Use this property to configure one or more prefixes that this SockJS service is + * allowed to serve. The prefix (e.g. "/echo") is needed to extract the SockJS + * specific portion of the URL (e.g. "${prefix}/info", "${prefix}/iframe.html", etc). + *

+ * This property is not strictly required. In most cases, the SockJS path can be + * auto-detected since the initial request from the SockJS client is of the form + * "{prefix}/info". Assuming the SockJS service is mapped correctly (e.g. using + * Ant-style pattern "/echo/**") this should work fine. This property can be used + * to configure explicitly the prefixes this service is allowed to service. + * + * @param prefixes the prefixes to use; prefixes do not need to include the portions + * of the path that represent Servlet container context or Servlet path. + */ + public void setValidSockJsPrefixes(String... prefixes) { + + this.sockJsPrefixes.clear(); + for (String prefix : prefixes) { + if (prefix.endsWith("/") && (prefix.length() > 1)) { + prefix = prefix.substring(0, prefix.length() - 1); + } + this.sockJsPrefixes.add(prefix); + } + + // sort with longest prefix at the top + Collections.sort(this.sockJsPrefixes, Collections.reverseOrder(new Comparator() { + public int compare(String o1, String o2) { + return new Integer(o1.length()).compareTo(new Integer(o2.length())); + } + })); + } + /** * Transports which don't support cross-domain communication natively (e.g. * "eventsource", "htmlfile") rely on serving a simple page (using the @@ -198,10 +249,18 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf * * @throws Exception */ - public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException { + public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) + throws IOException, TransportErrorException { - logger.debug(request.getMethod() + " [" + sockJsPath + "]"); + String sockJsPath = getSockJsPath(request); + if (sockJsPath == null) { + logger.warn("Could not determine SockJS path for URL \"" + request.getURI().getPath() + + ". Consider setting validSockJsPrefixes."); + response.setStatusCode(HttpStatus.NOT_FOUND); + return; + } + + logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]"); try { request.getHeaders(); @@ -225,13 +284,13 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf return; } else if (sockJsPath.equals("/websocket")) { - handleRawWebSocketRequest(request, response, webSocketHandler); + handleRawWebSocketRequest(request, response, handler); return; } String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/"); if (pathSegments.length != 3) { - logger.debug("Expected /{server}/{session}/{transport} but got " + sockJsPath); + logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\""); response.setStatusCode(HttpStatus.NOT_FOUND); return; } @@ -245,13 +304,62 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf return; } - handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), webSocketHandler); + handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), handler); } finally { response.flush(); } } + /** + * Return the SockJS path or null if the path could not be determined. + */ + private String getSockJsPath(ServerHttpRequest request) { + + String path = request.getURI().getPath(); + + // SockJS prefix hints? + if (!this.sockJsPrefixes.isEmpty()) { + for (String prefix : this.sockJsPrefixes) { + int index = path.indexOf(prefix); + if (index != -1) { + this.sockJsPathCache.add(path.substring(0, index + prefix.length())); + return path.substring(index + prefix.length()); + } + } + } + + // SockJS info request? + if (path.endsWith("/info")) { + this.sockJsPathCache.add(path.substring(0, path.length() - 6)); + return "/info"; + } + + // Have we seen this prefix before (following the initial /info request)? + String match = null; + for (String sockJsPath : this.sockJsPathCache) { + if (path.startsWith(sockJsPath)) { + if ((match == null) || (match.length() < sockJsPath.length())) { + match = sockJsPath; + } + } + } + if (match != null) { + return path.substring(match.length()); + } + + // SockJS greeting? + String pathNoSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; + String lastSegment = pathNoSlash.substring(pathNoSlash.lastIndexOf('/') + 1); + + if ((TransportType.fromValue(lastSegment) == null) && !lastSegment.startsWith("iframe")) { + this.sockJsPathCache.add(path); + return ""; + } + + return null; + } + protected abstract void handleRawWebSocketRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException; @@ -263,18 +371,18 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf protected boolean validateRequest(String serverId, String sessionId, String transport) { if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) { - logger.debug("Empty server, session, or transport value"); + logger.warn("Empty server, session, or transport value"); return false; } // Server and session id's must not contain "." if (serverId.contains(".") || sessionId.contains(".")) { - logger.debug("Server or session contain a \".\""); + logger.warn("Server or session contain a \".\""); return false; } if (!isWebSocketEnabled() && transport.equals(TransportType.WEBSOCKET.value())) { - logger.debug("Websocket transport is disabled"); + logger.warn("Websocket transport is disabled"); return false; } @@ -346,7 +454,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf response.setStatusCode(HttpStatus.NO_CONTENT); - addCorsHeaders(request, response, HttpMethod.GET, HttpMethod.OPTIONS); + addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET); addCacheHeaders(response); } else { @@ -404,4 +512,5 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf } }; + } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java index c7009b9c96..eb461db072 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java @@ -217,7 +217,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess *

Performs cleanup and notifies the {@link SockJsHandler}. */ public final void close() throws IOException { - close(CloseStatus.NORMAL); + close(new CloseStatus(3000, "Go away!")); } /** @@ -225,7 +225,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess *

Performs cleanup and notifies the {@link SockJsHandler}. */ public final void close(CloseStatus status) throws IOException { - if (!isClosed()) { + if (isOpen()) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this + ", " + status); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java index 436e31f79b..17233f7e0e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java @@ -78,6 +78,30 @@ public class SockJsFrame { return this.content.getBytes(Charset.forName("UTF-8")); } + public static String escapeCharacters(char[] chars) { + StringBuilder result = new StringBuilder(); + for (char ch : chars) { + if (isSockJsEscapeCharacter(ch)) { + result.append('\\').append('u'); + String hex = Integer.toHexString(ch).toLowerCase(); + for (int i = 0; i < (4 - hex.length()); i++) { + result.append('0'); + } + result.append(hex); + } + else { + result.append(ch); + } + } + return result.toString(); + } + + private static boolean isSockJsEscapeCharacter(char ch) { + return (ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F') + || (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F') + || (ch >= '\uFFF0' && ch <= '\uFFFF') || (ch >= '\uD800' && ch <= '\uDFFF'); + } + public String toString() { String result = this.content; if (result.length() > 80) { @@ -101,7 +125,7 @@ public class SockJsFrame { sb.append('"'); // TODO: dependency on Jackson char[] quotedChars = JsonStringEncoder.getInstance().quoteAsString(messages[i]); - sb.append(escapeSockJsCharacters(quotedChars)); + sb.append(escapeCharacters(quotedChars)); sb.append('"'); if (i < messages.length - 1) { sb.append(','); @@ -110,30 +134,6 @@ public class SockJsFrame { sb.append(']'); return sb.toString(); } - - private static String escapeSockJsCharacters(char[] chars) { - StringBuilder result = new StringBuilder(); - for (char ch : chars) { - if (isSockJsEscapeCharacter(ch)) { - result.append('\\').append('u'); - String hex = Integer.toHexString(ch).toLowerCase(); - for (int i = 0; i < (4 - hex.length()); i++) { - result.append('0'); - } - result.append(hex); - } - else { - result.append(ch); - } - } - return result.toString(); - } - - private static boolean isSockJsEscapeCharacter(char ch) { - return (ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F') - || (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F') - || (ch >= '\uFFF0' && ch <= '\uFFFF') || (ch >= '\uD800' && ch <= '\uDFFF'); - } } public interface FrameFormat { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java index 915ab840fb..45e34d4c69 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java @@ -23,13 +23,14 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; /** + * * @author Rossen Stoyanchev * @since 4.0 */ public interface SockJsService { - void handleRequest(ServerHttpRequest request, ServerHttpResponse response, - String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException; + void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler) + throws IOException, TransportErrorException; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java index 05f89bac38..1c2c5814d7 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java @@ -17,7 +17,9 @@ package org.springframework.web.socket.sockjs; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.springframework.http.HttpMethod; @@ -50,6 +52,14 @@ public enum TransportType { private final List headerHints; + private static final Map transportTypes = new HashMap(); + + static { + for (TransportType type : values()) { + transportTypes.put(type.value, type); + } + } + private TransportType(String value, HttpMethod httpMethod, String... headerHints) { this.value = value; @@ -57,6 +67,7 @@ public enum TransportType { this.headerHints = Arrays.asList(headerHints); } + public String value() { return this.value; } @@ -80,13 +91,8 @@ public enum TransportType { return this.headerHints.contains("jsessionid"); } - public static TransportType fromValue(String transportValue) { - for (TransportType type : values()) { - if (type.value().equals(transportValue)) { - return type; - } - } - throw new IllegalArgumentException("No matching constant for [" + transportValue + "]"); + public static TransportType fromValue(String value) { + return transportTypes.get(value); } @Override diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java index c0fec3e6bb..6f8e497678 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java @@ -177,7 +177,7 @@ public class DefaultSockJsService extends AbstractSockJsService { if (!supportedMethod.equals(request.getMethod())) { if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.supportsCors()) { response.setStatusCode(HttpStatus.NO_CONTENT); - addCorsHeaders(request, response, supportedMethod, HttpMethod.OPTIONS); + addCorsHeaders(request, response, HttpMethod.OPTIONS, supportedMethod); addCacheHeaders(response); } else { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java index 336df01ca0..8a335a76d2 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java @@ -32,8 +32,6 @@ import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsService; import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator; import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator; -import org.springframework.web.util.NestedServletException; -import org.springframework.web.util.UrlPathHelper; /** * @author Rossen Stoyanchev @@ -41,29 +39,17 @@ import org.springframework.web.util.UrlPathHelper; */ public class SockJsHttpRequestHandler implements HttpRequestHandler { - private final String prefix; - private final SockJsService sockJsService; private final WebSocketHandler webSocketHandler; - private final UrlPathHelper urlPathHelper = new UrlPathHelper(); - /** * Class constructor with {@link SockJsHandler} instance ... - * - * @param prefix the path prefix for the SockJS service. All requests with a path - * that begins with the specified prefix will be handled by this service. In a - * Servlet container this is the path within the current servlet mapping. */ - public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler webSocketHandler) { - - Assert.hasText(prefix, "prefix is required"); + public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler webSocketHandler) { Assert.notNull(sockJsService, "sockJsService is required"); Assert.notNull(webSocketHandler, "webSocketHandler is required"); - - this.prefix = prefix; this.sockJsService = sockJsService; this.webSocketHandler = decorateWebSocketHandler(webSocketHandler); } @@ -79,35 +65,14 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { return new LoggingWebSocketHandlerDecorator(handler); } - public String getPrefix() { - return this.prefix; - } - - public String getPattern() { - return this.prefix + "/**"; - } - @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String lookupPath = this.urlPathHelper.getLookupPathForRequest(request); - - Assert.isTrue(lookupPath.startsWith(this.prefix), - "Request path does not match the prefix of the SockJsService " + this.prefix); - - String sockJsPath = lookupPath.substring(prefix.length()); - ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response); ServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - try { - this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, this.webSocketHandler); - } - catch (Exception ex) { - // TODO - throw new NestedServletException("SockJS service failure", ex); - } + this.sockJsService.handleRequest(httpRequest, httpResponse, this.webSocketHandler); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java index 313794ea1c..237343c9ac 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java @@ -28,6 +28,7 @@ import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.AbstractSockJsSession; +import org.springframework.web.socket.sockjs.SockJsFrame; import org.springframework.web.socket.sockjs.SockJsRuntimeException; import org.springframework.web.socket.sockjs.TransportErrorException; import org.springframework.web.socket.sockjs.TransportHandler; @@ -61,6 +62,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport if (session == null) { response.setStatusCode(HttpStatus.NOT_FOUND); + logger.warn("Session not found"); return; } @@ -75,22 +77,28 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport messages = readMessages(request); } catch (JsonMappingException ex) { + logger.error("Failed to read message: ", ex); sendInternalServerError(response, "Payload expected.", session.getId()); return; } catch (IOException ex) { + logger.error("Failed to read message: ", ex); sendInternalServerError(response, "Broken JSON encoding.", session.getId()); return; } catch (Throwable t) { + logger.error("Failed to read message: ", t); sendInternalServerError(response, "Failed to process messages", session.getId()); return; } if (logger.isTraceEnabled()) { - logger.trace("Received messages: " + Arrays.asList(messages)); + logger.trace("Received message(s): " + Arrays.asList(messages)); } + response.setStatusCode(getResponseStatus()); + response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); + try { session.delegateMessages(messages); } @@ -98,9 +106,6 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport ExceptionWebSocketHandlerDecorator.tryCloseWithError(session, t, logger); throw new SockJsRuntimeException("Unhandled WebSocketHandler error in " + this, t); } - - response.setStatusCode(getResponseStatus()); - response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8"))); } protected void sendInternalServerError(ServerHttpResponse response, String error, diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java index c04e4c54ee..be38656c67 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java @@ -157,12 +157,13 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock protected synchronized void resetRequest() { updateLastActiveTime(); - if (isActive()) { + if (isActive() && this.asyncRequest.isAsyncStarted()) { try { + logger.debug("Completing async request"); this.asyncRequest.completeAsync(); } catch (Throwable ex) { - logger.warn("Failed to complete async request: " + ex.getMessage()); + logger.error("Failed to complete async request: " + ex.getMessage()); } } this.asyncRequest = null; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java index f01bb94c99..fe37568a61 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java @@ -41,7 +41,11 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio FrameFormat frameFormat) throws TransportErrorException { super.setInitialRequest(request, response, frameFormat); - super.setLongPollingRequest(request, response, frameFormat); + + // the WebSocketHandler delegate may have closed the session + if (!isClosed()) { + super.setLongPollingRequest(request, response, frameFormat); + } } protected void flushCache() throws IOException { diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java index fb91266a2b..48bd272d7e 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java @@ -62,7 +62,7 @@ public class WebSocketServerSockJsSession extends AbstractServerSockJsSession { @Override public boolean isActive() { - return this.webSocketSession.isOpen(); + return ((this.webSocketSession != null) && this.webSocketSession.isOpen()); } public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java new file mode 100644 index 0000000000..7ef451307e --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.socket; + +import org.junit.Before; +import org.springframework.http.server.AsyncServletServerHttpRequest; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.mock.web.test.MockHttpServletResponse; + + +/** + * @author Rossen Stoyanchev + */ +public class AbstractHttpRequestTests { + + protected ServerHttpRequest request; + + protected ServerHttpResponse response; + + protected MockHttpServletRequest servletRequest; + + protected MockHttpServletResponse servletResponse; + + + @Before + public void setUp() { + this.servletRequest = new MockHttpServletRequest(); + this.servletResponse = new MockHttpServletResponse(); + this.request = new AsyncServletServerHttpRequest(this.servletRequest, this.servletResponse); + this.response = new ServletServerHttpResponse(this.servletResponse); + } + + + protected void setRequest(String method, String requestUri) { + this.servletRequest.setMethod(method); + this.servletRequest.setRequestURI(requestUri); + } + + protected void resetResponse() { + this.servletResponse = new MockHttpServletResponse(); + this.response = new ServletServerHttpResponse(this.servletResponse); + } + +} diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java new file mode 100644 index 0000000000..fcc5325127 --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.socket.sockjs; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.web.socket.AbstractHttpRequestTests; +import org.springframework.web.socket.WebSocketHandler; + +import static org.junit.Assert.*; + +/** + * @author Rossen Stoyanchev + */ +public class AbstractSockJsServiceTests extends AbstractHttpRequestTests { + + private TestSockJsService service; + + private WebSocketHandler handler; + + + @Before + public void setUp() { + super.setUp(); + this.service = new TestSockJsService(new ThreadPoolTaskScheduler()); + } + + @Test + public void getSockJsPath() throws Exception { + + handleRequest("/echo", HttpStatus.OK); + assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString()); + + handleRequest("/echo/info", HttpStatus.OK); + assertTrue(this.servletResponse.getContentAsString().startsWith("{\"entropy\":")); + + handleRequest("/echo/", HttpStatus.OK); + assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString()); + + handleRequest("/echo/iframe.html", HttpStatus.OK); + assertTrue(this.servletResponse.getContentAsString().startsWith("\n")); + + handleRequest("/echo/websocket", HttpStatus.OK); + assertNull(this.service.sessionId); + assertSame(this.handler, this.service.handler); + + handleRequest("/echo/server1/session2/xhr", HttpStatus.OK); + assertEquals("session2", this.service.sessionId); + assertEquals(TransportType.XHR, this.service.transportType); + assertSame(this.handler, this.service.handler); + + handleRequest("/echo/other", HttpStatus.NOT_FOUND); + handleRequest("/echo//", HttpStatus.NOT_FOUND); + handleRequest("/echo///", HttpStatus.NOT_FOUND); + } + + + @Test + public void getSockJsPathGreetingRequest() throws Exception { + handleRequest("/echo", HttpStatus.OK); + assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString()); + } + + @Test + public void getSockJsPathInfoRequest() throws Exception { + handleRequest("/echo/info", HttpStatus.OK); + assertTrue(this.servletResponse.getContentAsString().startsWith("{\"entropy\":")); + } + + @Test + public void getSockJsPathWithConfiguredPrefix() throws Exception { + this.service.setValidSockJsPrefixes("/echo"); + handleRequest("/echo/s1/s2/xhr", HttpStatus.OK); + } + + @Test + public void getInfoOptions() throws Exception { + setRequest("OPTIONS", "/echo/info"); + this.service.handleRequest(this.request, this.response, this.handler); + + assertEquals(204, servletResponse.getStatus()); + } + + + private void handleRequest(String uri, HttpStatus httpStatus) throws IOException { + resetResponse(); + setRequest("GET", uri); + this.service.handleRequest(this.request, this.response, this.handler); + + assertEquals(httpStatus.value(), this.servletResponse.getStatus()); + } + + private static class TestSockJsService extends AbstractSockJsService { + + private String sessionId; + + private TransportType transportType; + + private WebSocketHandler handler; + + public TestSockJsService(TaskScheduler scheduler) { + super(scheduler); + } + + @Override + protected void handleRawWebSocketRequest(ServerHttpRequest request, + ServerHttpResponse response, WebSocketHandler handler) throws IOException { + + this.handler = handler; + } + + @Override + protected void handleTransportRequest(ServerHttpRequest request, + ServerHttpResponse response, String sessionId, + TransportType transportType, WebSocketHandler handler) + throws IOException, TransportErrorException { + + this.sessionId = sessionId; + this.transportType = transportType; + this.handler = handler; + } + } + +} diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java new file mode 100644 index 0000000000..0f2a4c4f05 --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.socket.sockjs; + +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + + +/** + * @author Rossen Stoyanchev + */ +public class StubSockJsConfig implements SockJsConfiguration { + + private int streamBytesLimit = 128 * 1024; + + private long heartbeatTime = 25 * 1000; + + private TaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + + + public int getStreamBytesLimit() { + return streamBytesLimit; + } + + public void setStreamBytesLimit(int streamBytesLimit) { + this.streamBytesLimit = streamBytesLimit; + } + + public long getHeartbeatTime() { + return heartbeatTime; + } + + public void setHeartbeatTime(long heartbeatTime) { + this.heartbeatTime = heartbeatTime; + } + + public TaskScheduler getTaskScheduler() { + return taskScheduler; + } + + public void setTaskScheduler(TaskScheduler taskScheduler) { + this.taskScheduler = taskScheduler; + } + +} diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java new file mode 100644 index 0000000000..57849d1ba1 --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.socket.sockjs; + +import java.util.Date; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.FutureTask; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.Trigger; + + +/** + * @author Rossen Stoyanchev + */ +public class StubTaskScheduler implements TaskScheduler { + + @Override + public ScheduledFuture schedule(Runnable task, Trigger trigger) { + return new StubScheduledFuture(); + } + + @Override + public ScheduledFuture schedule(Runnable task, Date startTime) { + return new StubScheduledFuture(); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) { + return new StubScheduledFuture(); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) { + return new StubScheduledFuture(); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { + return new StubScheduledFuture(); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay) { + return new StubScheduledFuture(); + } + + + private static class StubScheduledFuture extends FutureTask implements ScheduledFuture { + + @SuppressWarnings("unchecked") + public StubScheduledFuture() { + super(new Callable() { + public Object call() throws Exception { + return null; + } + }); + } + + @Override + public long getDelay(TimeUnit unit) { + return 0; + } + + @Override + public int compareTo(Delayed o) { + return 0; + } + } +} diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java new file mode 100644 index 0000000000..9f4b34c17f --- /dev/null +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java @@ -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.web.socket.sockjs; + +import org.junit.Test; + +import static org.junit.Assert.*; + + +/** + * @author Rossen Stoyanchev + */ +public class TransportTypeTests { + + + @Test + public void testFromValue() { + assertEquals(TransportType.WEBSOCKET, TransportType.fromValue("websocket")); + assertEquals(TransportType.XHR, TransportType.fromValue("xhr")); + assertEquals(TransportType.XHR_SEND, TransportType.fromValue("xhr_send")); + assertEquals(TransportType.JSONP, TransportType.fromValue("jsonp")); + assertEquals(TransportType.JSONP_SEND, TransportType.fromValue("jsonp_send")); + assertEquals(TransportType.XHR_STREAMING, TransportType.fromValue("xhr_streaming")); + assertEquals(TransportType.EVENT_SOURCE, TransportType.fromValue("eventsource")); + assertEquals(TransportType.HTML_FILE, TransportType.fromValue("htmlfile")); + } + +} diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java index a135e51a40..a8630d5319 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java @@ -18,11 +18,13 @@ package org.springframework.web.socket.sockjs.support; import java.util.Map; +import org.junit.Before; import org.junit.Test; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.web.socket.AbstractHttpRequestTests; +import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter; +import org.springframework.web.socket.sockjs.StubTaskScheduler; import org.springframework.web.socket.sockjs.TransportHandler; import org.springframework.web.socket.sockjs.TransportType; -import org.springframework.web.socket.sockjs.support.DefaultSockJsService; import static org.junit.Assert.*; @@ -32,14 +34,22 @@ import static org.junit.Assert.*; * * @author Rossen Stoyanchev */ -public class DefaultSockJsServiceTests { +public class DefaultSockJsServiceTests extends AbstractHttpRequestTests { + private DefaultSockJsService service; + + + @Before + public void setUp() { + super.setUp(); + this.service = new DefaultSockJsService(new StubTaskScheduler()); + this.service.setValidSockJsPrefixes("/echo"); + } @Test - public void testDefaultTransportHandlers() { + public void defaultTransportHandlers() { - DefaultSockJsService sockJsService = new DefaultSockJsService(new ThreadPoolTaskScheduler()); - Map handlers = sockJsService.getTransportHandlers(); + Map handlers = service.getTransportHandlers(); assertEquals(8, handlers.size()); assertNotNull(handlers.get(TransportType.WEBSOCKET)); @@ -52,5 +62,21 @@ public class DefaultSockJsServiceTests { assertNotNull(handlers.get(TransportType.EVENT_SOURCE)); } + @Test + public void xhrSend() throws Exception { + + setRequest("POST", "/echo/000/c5839f69/xhr"); + this.service.handleRequest(this.request, this.response, new TextWebSocketHandlerAdapter()); + + resetResponse(); + setRequest("POST", "/echo/000/c5839f69/xhr_send"); + this.servletRequest.setContent("[\"x\"]".getBytes("UTF-8")); + + this.service.handleRequest(this.request, this.response, new TextWebSocketHandlerAdapter()); + + assertEquals(204, this.servletResponse.getStatus()); + assertEquals("text/plain;charset=UTF-8", this.servletResponse.getContentType()); + } + } diff --git a/spring-websocket/src/test/resources/log4j.xml b/spring-websocket/src/test/resources/log4j.xml new file mode 100644 index 0000000000..8fa59bf2f3 --- /dev/null +++ b/spring-websocket/src/test/resources/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d89b18613f26094eee45d664cc2a8e5fc9fcba16 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 6 May 2013 14:33:00 -0400 Subject: [PATCH 51/51] Polish (minor) --- .../web/socket/sockjs/SockJsFrame.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java index 17233f7e0e..c1a52ef37d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java @@ -78,24 +78,29 @@ public class SockJsFrame { return this.content.getBytes(Charset.forName("UTF-8")); } - public static String escapeCharacters(char[] chars) { + /** + * See "JSON Unicode Encoding" section of SockJS protocol. + */ + public static String escapeCharacters(char[] characters) { StringBuilder result = new StringBuilder(); - for (char ch : chars) { - if (isSockJsEscapeCharacter(ch)) { + for (char c : characters) { + if (isSockJsEscapeCharacter(c)) { result.append('\\').append('u'); - String hex = Integer.toHexString(ch).toLowerCase(); + String hex = Integer.toHexString(c).toLowerCase(); for (int i = 0; i < (4 - hex.length()); i++) { result.append('0'); } result.append(hex); } else { - result.append(ch); + result.append(c); } } return result.toString(); } + // See `escapable_by_server` var in SockJS protocol (under "JSON Unicode Encoding") + private static boolean isSockJsEscapeCharacter(char ch) { return (ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F') || (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F')