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 4a6261f4ec2..e56d3060d1b 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 71b5e8408d4..3511d2a86f0 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 00000000000..35324cfd6a4 --- /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 00000000000..e69de29bb2d diff --git a/spring-websocket/src/test/java/.gitignore b/spring-websocket/src/test/java/.gitignore new file mode 100644 index 00000000000..e69de29bb2d 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 e56d3060d1b..3bb431d2ac4 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 5d480d5e4b8..e972709572a 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 35324cfd6a4..c7ccb126505 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 00000000000..b4bfb6c1e4f --- /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 00000000000..a1f4154dc3f --- /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 00000000000..fc08356b072 --- /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 00000000000..22ee961ea67 --- /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 00000000000..e4f8d64501a --- /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 00000000000..0e1c99eabce --- /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 00000000000..696b0bf7ce0 --- /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 00000000000..e40aa72c3ba --- /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 c7ccb126505..a6104614eeb 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 b4bfb6c1e4f..7a71ab595b5 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 a1f4154dc3f..032abc6b46f 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 00000000000..bcf288f9cfd --- /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 00000000000..5dabaf0f6b9 --- /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 fc08356b072..adaf3b69e8a 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 22ee961ea67..22d0386771e 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 e4f8d64501a..32f04f74be1 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 0e1c99eabce..76674870225 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 32f04f74be1..4366ec026b3 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 76674870225..608be8f932a 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 00000000000..1e5962b911c --- /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 696b0bf7ce0..0f8de388554 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 e40aa72c3ba..9e2f9f1962d 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 adaf3b69e8a..bb821144846 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 22d0386771e..8179253e6be 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 bcf288f9cfd..2c7ca73808e 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 5dabaf0f6b9..a185f67ad3e 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 00000000000..a5f66584f8c --- /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 bb821144846..5a601bfe88b 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 00000000000..aae44c148cb --- /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 8179253e6be..bbc4d6b814a 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 00000000000..745001b2060 --- /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 2c7ca73808e..2e36774f796 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 a185f67ad3e..00000000000 --- 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 00000000000..b25c1a47c36 --- /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 a5f66584f8c..3ede1f92bca 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 3bb431d2ac4..e8986cd15ad 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 5de14e9954c..d73a1ea0ef5 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 43fa1b3e7ee..8cda7862a54 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 00000000000..a60c73c87ff --- /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 00000000000..7dc13536c93 --- /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 00000000000..82a09ba26e3 --- /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 80f7ca292d5..05824e67abd 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 47422a0065d..90849670786 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 cd6166575b4..33c123cff95 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 bb878444201..794ca6ac2ad 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 f075b202bd5..382b3fa20ab 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 4aecd01dcd3..c9c8ef99556 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 516eb92ab25..5ddcfead7b1 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 00000000000..0253ac66e1a --- /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 00000000000..37d424e017d --- /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 ce8fcd3ad26..669835afc15 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 5dd0dbd4249..38d6e3df422 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 985085e51e9..b0901324575 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 18412ce33cc..0ca8a29aca6 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 cb08fa91a11..3287a7d93f9 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 2df6331b8e3..0358bf42bff 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 00000000000..00b4935c56d --- /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 00000000000..08b93d1296a --- /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 00000000000..77a61128e3c --- /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 00000000000..f35e1e8fd53 --- /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 00000000000..130e1f2f447 --- /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 00000000000..89941b9840b --- /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 00000000000..d56303fa0e6 --- /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 00000000000..c8242874a68 --- /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 00000000000..131354fbde8 --- /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 00000000000..ea2610ab0d9 --- /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 00000000000..3efe70404dc --- /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 00000000000..7ff8b8f553a --- /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 00000000000..0c4d232e9e6 --- /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 00000000000..95bb75ff086 --- /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 00000000000..c00c49222ad --- /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 00000000000..a6c50e49779 --- /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 00000000000..08ad9b4e761 --- /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 00000000000..90ef3c29a62 --- /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 00000000000..ad4939c9003 --- /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 00000000000..10816de521c --- /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 00000000000..2e05a419275 --- /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 00000000000..cc1e98ac224 --- /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 00000000000..c696c6cf127 --- /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 00000000000..694bb944536 --- /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 00000000000..f7b6b13a077 --- /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 00000000000..d327568331f --- /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 00000000000..9abf049b167 --- /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 00000000000..73013776084 --- /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 00000000000..d141cb2c7e8 --- /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 00000000000..f5fe4b13ff4 --- /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 7a71ab595b5..5877a5b4522 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 032abc6b46f..261aff04515 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 a6104614eeb..947a565bb73 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 608be8f932a..2722cb09eda 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 4366ec026b3..ce3dd1c5313 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 bbc4d6b814a..17ee09328bc 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 5a601bfe88b..9c287e7071f 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 17ee09328bc..385f8ece2b9 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 00000000000..d412fd9e299 --- /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 00000000000..1cb0d35102e --- /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 00000000000..ccc4c3f4a0a --- /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 00000000000..472a9df942e --- /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 9c287e7071f..0596a27a81b 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 385f8ece2b9..12367774fb6 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 2e36774f796..d62b2f44c6a 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 d412fd9e299..6ed65012e67 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 1cb0d35102e..a95b9ab53f3 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 12367774fb6..843cb075986 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 e8986cd15ad..1708b1587c5 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 89941b9840b..67e193f314b 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 d56303fa0e6..fb7c5edcf53 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 95bb75ff086..b2719948620 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 ea2610ab0d9..183e964305b 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 c00c49222ad..d6dcb875627 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 a6c50e49779..6f41298d60c 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 00000000000..c40565ecacf --- /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 00000000000..57d3baf3330 --- /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 90ef3c29a62..9a59a79e674 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 10816de521c..e7740d6d490 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 cc1e98ac224..adc983e0b41 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 c696c6cf127..bff94ad72e8 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 d327568331f..2eb846683fc 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 9abf049b167..40ced92ad5e 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 947a565bb73..d930dde9d0f 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 ce3dd1c5313..302a0333923 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 2722cb09eda..da3d7a768aa 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 aae44c148cb..abb66ff4cc5 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 843cb075986..9dfc509042e 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 38d6e3df422..5f4f90e9a86 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 f35e1e8fd53..6e20a0b1317 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 67e193f314b..b4624914bb9 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 fb7c5edcf53..412704b3355 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 c8242874a68..a122a769e37 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 b2719948620..00000000000 --- 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 183e964305b..00000000000 --- 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 3efe70404dc..93ada27d1ea 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 7ff8b8f553a..f89c2ea81ba 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 130e1f2f447..5373d3edc8e 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 d6dcb875627..69f325da2c3 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 6f41298d60c..fbbeab48a44 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 08ad9b4e761..0597e8621f5 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 9a59a79e674..dffa4389c02 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 ad4939c9003..0c0d60ae66f 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 00000000000..9f13cd10b19 --- /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 e7740d6d490..89b076d93fd 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 00000000000..0221c33a770 --- /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 00000000000..b6762111f3a --- /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 2e05a419275..0725e6284cb 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 adc983e0b41..188c59d9ab4 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 bff94ad72e8..28fe888714e 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 694bb944536..e839b77f4ee 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 f7b6b13a077..3dd0e8d517d 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 00000000000..db5da4c3628 --- /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 2eb846683fc..accff89ee6e 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 00000000000..e617ab794ff --- /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 40ced92ad5e..00000000000 --- 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 73013776084..78733099e00 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 d141cb2c7e8..6a697474979 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 f5fe4b13ff4..e38417ac5ac 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 a95b9ab53f3..8e5d3f754af 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 0f8de388554..d3807d0146e 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 9e2f9f1962d..684bc362536 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 abb66ff4cc5..8eeb33026dc 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 9dfc509042e..8e373c02618 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 b25c1a47c36..4e1f8d633b3 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 745001b2060..9a1b43dc123 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 302a0333923..d3384ed6b07 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 da3d7a768aa..017d0181eaf 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 1e5962b911c..b30d21c7abc 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 1708b1587c5..57b4a485098 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 d3807d0146e..eaba42d78bb 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 8eeb33026dc..9758aab2d48 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 9a1b43dc123..bcd8a70dd14 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 00000000000..9690141b262 --- /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 4e1f8d633b3..3e4dc96b617 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 00000000000..5b2f27cafe6 --- /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 00000000000..8748c1b5822 --- /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 c40565ecacf..656324fe5b4 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 00000000000..b2030dead7f --- /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 b6762111f3a..8d41943c9a9 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 00000000000..b31cef1145e --- /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 00000000000..28e7e9eb868 --- /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 d3384ed6b07..302a0333923 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 017d0181eaf..b70076b3d8f 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 00000000000..99723de31db --- /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 00000000000..8bf3b32aa55 --- /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 eaba42d78bb..7ff08b8097b 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 684bc362536..a8da4981ac8 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 0596a27a81b..76194c4ecd2 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 8e373c02618..9dfc509042e 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 bcd8a70dd14..20472434293 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 d62b2f44c6a..b5b216fc8b5 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 9758aab2d48..a63033361f6 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 9690141b262..52750dc4d13 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 3e4dc96b617..a44468bdab8 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 00000000000..8adf2395cbe --- /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 3ede1f92bca..ce327f239ee 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 00000000000..8a44c0bc885 --- /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 57d3baf3330..26f5d59ae1c 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 00000000000..c76d943d9f4 --- /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 b30d21c7abc..00000000000 --- 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 37d424e017d..1e53fc7abf8 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 8ce306271ff..02541fab927 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 b0901324575..3a2ba0445d8 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 77a61128e3c..0dab8e17f52 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 b4624914bb9..feb7599b807 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 412704b3355..bff4ccace3a 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 00000000000..9f89c4a1207 --- /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 131354fbde8..ded997e9455 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 69f325da2c3..8a6ca6a5646 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 dffa4389c02..8b43a6aff6a 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 0c0d60ae66f..5f657d94709 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 0725e6284cb..235b6f5de13 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 188c59d9ab4..35e48b238e4 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 3dd0e8d517d..34302f610f2 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 db5da4c3628..c19818e03df 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 accff89ee6e..94cf47cd9de 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 e617ab794ff..82e94f67884 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 6a697474979..0f49b75de34 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 d930dde9d0f..2abe644a5a4 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 302a0333923..dc4126b168f 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 9dfc509042e..3c33a31017d 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 a63033361f6..089a9f8efdb 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 52750dc4d13..6a23883fb5c 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 20472434293..2dd09023b72 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 a44468bdab8..b07cd2e0dff 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 00000000000..f78c2ccf4ba --- /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 bff4ccace3a..2a81ca6f8c5 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 93ada27d1ea..8f4087227c4 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 5373d3edc8e..04936f7d562 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 8a6ca6a5646..4cd8af9f1b8 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 0597e8621f5..589e9770bd1 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 8b43a6aff6a..f8dbd593661 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 0221c33a770..ba0a224bf65 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 e839b77f4ee..029e5c4ebb1 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 f78c2ccf4ba..b67dcefb2c2 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 6e20a0b1317..8c9747dc0a6 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 feb7599b807..caa436512b2 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 2a81ca6f8c5..e6823c0d4c2 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 f89c2ea81ba..8ad26ef6fae 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 a122a769e37..4bf1db79e82 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 2dd09023b72..dfb1e002d46 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 8f4087227c4..ea6208b8369 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 0c4d232e9e6..00000000000 --- 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 4cd8af9f1b8..af0789aa022 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 fbbeab48a44..00000000000 --- 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 00000000000..977f9eb8b9d --- /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 656324fe5b4..00000000000 --- 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 589e9770bd1..300adbd9816 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 f8dbd593661..aff6cea280d 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 5f657d94709..e03fb4a4c97 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 9f13cd10b19..2f723092fee 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 89b076d93fd..5aa8eb11be2 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 ba0a224bf65..00000000000 --- 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 8d41943c9a9..00000000000 --- 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 235b6f5de13..ae8efbc6670 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 35e48b238e4..d6568fa7972 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 28fe888714e..a00c5de6b74 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 029e5c4ebb1..95e90dccc01 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 34302f610f2..d48fa31a2c2 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 c19818e03df..f90fbdb13e8 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 94cf47cd9de..71bdf2d9dd4 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 82e94f67884..9ec2a9e386a 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 00000000000..3fc0a7f9f3d --- /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 78733099e00..7655e42948d 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 0f49b75de34..cf407442849 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 00000000000..f8d2d90bd25 --- /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 8e5d3f754af..9a24e04af55 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 ccc4c3f4a0a..a1005e3507f 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 472a9df942e..9ca8653bbfc 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 7ff08b8097b..cf4e4046f6d 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 a8da4981ac8..8222e7258ed 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 00000000000..d0cf29fa178 --- /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 3c33a31017d..d9e36a61fb6 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 6ed65012e67..1129cf6e66a 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 089a9f8efdb..00000000000 --- 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 8adf2395cbe..00000000000 --- 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 ce327f239ee..34a043af9c6 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 00000000000..1e7f1a27944 --- /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 6a23883fb5c..1a7619af456 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 b07cd2e0dff..ac74c9e5f11 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 26f5d59ae1c..6e641eaba01 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 c76d943d9f4..ac054317dd9 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 e6823c0d4c2..16740af5562 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 af0789aa022..b0f311ec64f 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 ac74c9e5f11..5b2f3a25755 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 0dab8e17f52..e7cd75bf4b1 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 8c9747dc0a6..0dac36118db 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 caa436512b2..43473eb9972 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 16740af5562..399bf5d5d66 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 dfb1e002d46..23c79be7d21 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 b0f311ec64f..60f8143f706 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 977f9eb8b9d..22d058e0eb9 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 e03fb4a4c97..47efdea55b8 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 2f723092fee..00000000000 --- 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 f90fbdb13e8..2da57cbf354 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 9ec2a9e386a..1c266860e07 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 2abe644a5a4..a9af7d7d7b9 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 dc4126b168f..546f16c96e4 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 b70076b3d8f..7680f24ef3a 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 43473eb9972..54be439ddec 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 aff6cea280d..d917ef035b6 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 47efdea55b8..b71b4287a9d 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 5aa8eb11be2..f8dc637c166 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 d6568fa7972..eb752cd302c 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 a00c5de6b74..23c9f07efbe 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 d48fa31a2c2..baac23d5874 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 2da57cbf354..c23863f9012 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 71bdf2d9dd4..e0293a73724 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 1c266860e07..560335693cd 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 7655e42948d..e424bac91a0 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 2db7ae75f27..712344802e2 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 00000000000..3ccfdb599cb --- /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 3ccfdb599cb..5eb04d08cb0 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 5eb04d08cb0..cd9a7077710 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 00b4935c56d..e8c20e573f9 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 08b93d1296a..3523f6c0dc0 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 e7cd75bf4b1..e1e46155cd8 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 b67dcefb2c2..4e4950181d8 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 0dac36118db..3cf7e61fc12 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 54be439ddec..04fc33409d5 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 399bf5d5d66..6d847137275 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 d917ef035b6..726fa76c876 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 b71b4287a9d..0c9d624f564 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 c23863f9012..e616455a2c1 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 560335693cd..12a5bcd282d 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 3fc0a7f9f3d..32702d00d9f 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 00000000000..979ec4090f9 --- /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 5877a5b4522..783e9b66d18 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 261aff04515..af03f63b1ca 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 a9af7d7d7b9..d1d3c27a350 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 546f16c96e4..64f4d4d7fdc 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 7680f24ef3a..73840558ac2 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 1e7f1a27944..29b2372f91a 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 1a7619af456..e462e65dd74 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 5b2f3a25755..d90156b97f0 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 f8d2d90bd25..135c674d8bd 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 00000000000..762cc2fe8ba --- /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 9a24e04af55..7839c7c2a3b 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 00000000000..ae82de0dc08 --- /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 00000000000..553423cb92e --- /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 00000000000..cb16e5be449 --- /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 a1005e3507f..75433591ca4 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 9ca8653bbfc..b7006ab2fc5 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 00000000000..ba4fdbcdfbb --- /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 00000000000..522bfcdf1e0 --- /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 00000000000..a6d9d53ebf6 --- /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 00000000000..777e2ae7da6 --- /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 28e7e9eb868..817c86c58d4 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 73840558ac2..7593adbfd03 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 76194c4ecd2..1d092418464 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 b5b216fc8b5..00000000000 --- 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 00000000000..427ca9fcbdf --- /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 00000000000..ee6211341be --- /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 3cf7e61fc12..1f3e82541ef 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 e8c20e573f9..00000000000 --- 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 e1e46155cd8..00000000000 --- 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 4e4950181d8..c87ff5a7b9c 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 04fc33409d5..51004660a6b 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 6d847137275..24c3dacb066 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 8ad26ef6fae..799ba50d891 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 23c79be7d21..604ddcbca18 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 ea6208b8369..8c662127d66 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 60f8143f706..aa35115fe6d 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 22d058e0eb9..b5e2b49cafd 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 300adbd9816..8b7e3604a97 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 726fa76c876..978663598c3 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 0c9d624f564..e87006958b4 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 f8dc637c166..0adfd867b1e 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 23c9f07efbe..fabd525393e 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 95e90dccc01..8d39e2d125a 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 baac23d5874..c9814f09f21 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 e616455a2c1..4e4dc0987e9 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 e0293a73724..77cacca7808 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 12a5bcd282d..00000000000 --- 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 32702d00d9f..836e7331f54 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 e424bac91a0..57b5fd4e944 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 00000000000..5f136bc31ee --- /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 3523f6c0dc0..50940e34daf 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 00000000000..7752baf6cc7 --- /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 762cc2fe8ba..b254ae7f806 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 00000000000..ea073eaf3d4 --- /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 783e9b66d18..b420e83940e 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 af03f63b1ca..4914f3767a3 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 00000000000..19b79eda74c --- /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 d1d3c27a350..2c9d4355f52 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 64f4d4d7fdc..0da50203eef 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 7593adbfd03..82d75c098db 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 cf4e4046f6d..c7a3fdcb875 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 8222e7258ed..2af525c6063 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 d0cf29fa178..81c238c4230 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 29b2372f91a..b16f3d746d6 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 6e641eaba01..9418afc6893 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 1f3e82541ef..29791860a8b 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 c87ff5a7b9c..4f6051c1f88 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 51004660a6b..0c179b9dd0c 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 24c3dacb066..0319ff41bc7 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 604ddcbca18..eeef0913d19 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 8c662127d66..1d0eedb7664 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 aa35115fe6d..f54b540eb13 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 b5e2b49cafd..4b16cacb0f1 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 8b7e3604a97..d3928730a48 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 978663598c3..e2218715b01 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 e87006958b4..72aa97e7c11 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 0adfd867b1e..ed626a29615 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 fabd525393e..3f5854d30bb 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 c9814f09f21..86851ab9d59 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 4e4dc0987e9..0e504569572 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 77cacca7808..094fd115e98 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 836e7331f54..361c8b3c14c 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 57b5fd4e944..06a6aaf37d8 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 135c674d8bd..3c8dc2c455d 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 ae82de0dc08..579fcb1d8aa 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 cb16e5be449..29b465e477c 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 75433591ca4..9bb7fe1d2ad 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 b7006ab2fc5..1307f55d9e7 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 522bfcdf1e0..1a15d4bd958 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 82d75c098db..bb1048c7916 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 c7a3fdcb875..3ed1ccb4414 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 2af525c6063..61f0e42b332 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 81c238c4230..6d2827baaa5 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 d9e36a61fb6..c6aadd3028d 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 ee6211341be..00000000000 --- 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 b16f3d746d6..f7884838d32 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 9418afc6893..096ee1ca0b0 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 00000000000..1c6567dc9ac --- /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 00000000000..c880a64802d --- /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 bb1048c7916..be728fdcc1f 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 57b4a485098..72eebf80591 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 3ed1ccb4414..6ae721819ce 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 00000000000..a00ee912ef7 --- /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 5f136bc31ee..dbb12bcee22 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 b254ae7f806..61f3916547c 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 19b79eda74c..6ed55410ad0 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 0e504569572..776df0e30b3 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 6d2827baaa5..fadf15c99e0 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 29791860a8b..d8565455c17 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 776df0e30b3..eef056aab12 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 be728fdcc1f..7f7bd50a45d 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 0c179b9dd0c..a39e5d67897 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 0319ff41bc7..e44c44cec0d 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 4bf1db79e82..1fa79db8835 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 f54b540eb13..5a4baadf3fa 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 29b465e477c..05eddadd77e 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 00000000000..77ee2edf155 --- /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 d8565455c17..24c5e84cbb6 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 5a4baadf3fa..3d389353e95 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 eef056aab12..07b6cf34577 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 979ec4090f9..b0f751392de 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 b420e83940e..a4972eeb466 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 579fcb1d8aa..169170da8d0 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 1a15d4bd958..97655d29e99 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 7f7bd50a45d..1b151164865 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 a00ee912ef7..6be0568cb8b 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 096ee1ca0b0..343b065f553 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 24c5e84cbb6..ee8157e5b64 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 a39e5d67897..13ff66c27af 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 e44c44cec0d..755c8ee4b6d 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 eeef0913d19..8df23d21342 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 00000000000..90eb5e81702 --- /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 1d0eedb7664..6364a1c83ff 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 3d389353e95..925ba0305a9 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 d3928730a48..99a59a723fd 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 e2218715b01..e2aa777fb54 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 72aa97e7c11..7e6742a1e1c 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 ed626a29615..00000000000 --- 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 ae8efbc6670..700fd34706a 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 eb752cd302c..ccd029d0c15 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 3f5854d30bb..24c754e89dd 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 8d39e2d125a..5cbd6288e29 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 86851ab9d59..743c3694875 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 07b6cf34577..691571a72a8 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 094fd115e98..7e4a3c83fb6 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 361c8b3c14c..8f615c913dc 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 cf407442849..9e6c7457a48 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 50940e34daf..bac882087fd 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 ea073eaf3d4..fe79622b4b2 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 a4972eeb466..da4b841d2cd 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 4914f3767a3..4d9e5259d96 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 2c9d4355f52..483c6f72e81 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 1b151164865..ce9f0736ba9 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 6ae721819ce..79463681ea7 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 61f0e42b332..1989f61ad08 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 fadf15c99e0..359cf9dc474 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 f7884838d32..ef53d8e237b 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 e462e65dd74..a5ca56c5b50 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 6be0568cb8b..c313f9cdc08 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 ee8157e5b64..e5843337596 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 691571a72a8..95cb4a05227 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 fe79622b4b2..acfeeab6d2e 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 bac882087fd..69941bfa14a 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 da4b841d2cd..b835c383af8 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 4d9e5259d96..95cd36af563 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 ce9f0736ba9..4d02cabd6b0 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 c313f9cdc08..22c186637a1 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 e5843337596..9f3248b8e08 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 4f6051c1f88..d9be6247914 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 5b2f27cafe6..f7f8da613c7 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 13ff66c27af..7f2e7bbeea5 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 755c8ee4b6d..8e8999a0496 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 799ba50d891..44d55ff0c5c 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 9f89c4a1207..63f82da2575 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 1fa79db8835..4cec5cff6f2 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 ded997e9455..55527575801 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 8df23d21342..46d357cc6a3 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 6364a1c83ff..beac1aa1a3a 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 04936f7d562..a71775534c5 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 8748c1b5822..167717047ac 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 925ba0305a9..a0d61f79e2e 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 4b16cacb0f1..cb583622d20 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 b2030dead7f..1d3b9d2052f 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 99a59a723fd..0f1fd0d2465 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 e2aa777fb54..ac53c77f58c 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 7e6742a1e1c..0492fee3156 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 700fd34706a..8fb5a0313c0 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 ccd029d0c15..3075aade2be 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 24c754e89dd..cc036453546 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 5cbd6288e29..b0d74454ef2 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 7e4a3c83fb6..506985ba734 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 b31cef1145e..c35beb31389 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 dbb12bcee22..8bc0e420076 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 b0f751392de..dafcb988e7b 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 3c8dc2c455d..5158a669a36 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 7752baf6cc7..30524be5fa4 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 61f3916547c..df78a6ef63d 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 b835c383af8..a16ab765bf5 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 95cd36af563..b42f7000f09 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 6ed55410ad0..61d4f11b7c7 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 483c6f72e81..e1ef76b61d5 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 7839c7c2a3b..ab696d9c8f4 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 169170da8d0..18260dfd9b6 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 553423cb92e..a6f904b1ef1 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 05eddadd77e..5b84998cafd 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 9bb7fe1d2ad..ce03fb798a3 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 1307f55d9e7..44162dacc57 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 ba4fdbcdfbb..dfdee7c9666 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 97655d29e99..25519dd2ea9 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 a6d9d53ebf6..944987eb2e7 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 777e2ae7da6..af6c74b7d2e 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 817c86c58d4..e7860b5cea7 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 0da50203eef..e59ee859422 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 4d02cabd6b0..528ac34ff1e 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 99723de31db..64cfefd85e6 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 8bf3b32aa55..c1f27cbcb7c 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 79463681ea7..c37ffef466d 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 1989f61ad08..227b6fefd4b 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 359cf9dc474..9af17b4fff1 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 1d092418464..aa9d3829ee9 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 c6aadd3028d..61546fc2528 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 427ca9fcbdf..1d7c64c0e7a 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 1129cf6e66a..e553f247cf2 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 34a043af9c6..e52ce874a1a 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 8a44c0bc885..e70c9d1436a 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 ef53d8e237b..ec114934b57 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 22c186637a1..2713266ff45 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 d90156b97f0..0dcced9a125 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 343b065f553..c5e622726de 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 ac054317dd9..8b29c9295c0 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 1c6567dc9ac..858b440d2d8 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 c880a64802d..aa9598dc2e5 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 9f3248b8e08..9b1e07c99f9 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 a16ab765bf5..5984d49ae0c 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 00000000000..2ec1ec6f569 --- /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 00000000000..ef23f92427d --- /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 00000000000..3ce78ab01f9 --- /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 e59ee859422..30bc063baf2 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 00000000000..45abc18a524 --- /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 64cfefd85e6..695869a5ffc 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 25519dd2ea9..ca0e103efbb 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 528ac34ff1e..00000000000 --- 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 61546fc2528..ebde513034a 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 ec114934b57..e27ca8b66e1 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 2713266ff45..95e54b34e42 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 9b1e07c99f9..d105a16d6af 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 d9be6247914..2f8f66c3bbc 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 7f2e7bbeea5..3ba7ae422c8 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 8e8999a0496..dddd2c73208 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 46d357cc6a3..0e2a91cbf46 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 beac1aa1a3a..80d95fbd912 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 a0d61f79e2e..28181ef124b 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 cb583622d20..a90de740144 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 0f1fd0d2465..6da6af510ea 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 ac53c77f58c..f7bfcfeeeb5 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 0492fee3156..0a59625bcda 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 8fb5a0313c0..ec59fa8a260 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 3075aade2be..e2fda86dbcb 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 cc036453546..8fed9533c62 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 743c3694875..89aa292c14c 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 95cb4a05227..b8da4c33f0d 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 506985ba734..a5352e7f1ca 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 8f615c913dc..6d1b83955cd 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 06a6aaf37d8..7c7709e0c24 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 9e6c7457a48..ef31f696284 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 8bc0e420076..90d3efd6910 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 acfeeab6d2e..00000000000 --- 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 69941bfa14a..00000000000 --- 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 5984d49ae0c..3b0b9476f93 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 61d4f11b7c7..a5a06824b0d 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 00000000000..122f2264c53 --- /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 00000000000..d136dc9012a --- /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 2ec1ec6f569..ccfe4b4048a 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 ef23f92427d..c48f1b7db3f 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 3ce78ab01f9..a0f06d0c205 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 30bc063baf2..811ebd93c59 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 b42f7000f09..6c54f653e80 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 00000000000..8a4c64f630c --- /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 45abc18a524..7e9dce31b0a 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 695869a5ffc..59f29a08a3e 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 18260dfd9b6..d77b252a789 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 5b84998cafd..d633545cbea 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 ca0e103efbb..0b9340807c5 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 c37ffef466d..46b043fd57b 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 227b6fefd4b..532dd65501c 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 9af17b4fff1..773d5863f7d 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 e27ca8b66e1..cd34bfdfc72 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 95e54b34e42..7448e66ac73 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 c5e622726de..6d8edc0c28c 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 d105a16d6af..c7dd7a4875f 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 2f8f66c3bbc..c9bc94519c2 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 3ba7ae422c8..f9e57a94870 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 dddd2c73208..6e0504847bc 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 4cec5cff6f2..61de30afa7a 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 0e2a91cbf46..580d2afec6e 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 80d95fbd912..af05a34357f 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 28181ef124b..f070c1b94db 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 a90de740144..ebf38e4cd74 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 6da6af510ea..832ba537ba6 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 f7bfcfeeeb5..917b71b6553 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 0a59625bcda..d5ab1a09e9f 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 ec59fa8a260..07fcaa8123b 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 e2fda86dbcb..88dc77a1f1e 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 8fed9533c62..d098148b09b 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 89aa292c14c..b8dffe7e334 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 b8da4c33f0d..5a79779be2e 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 a5352e7f1ca..8e147c30911 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 6d1b83955cd..12554ff676f 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 7c7709e0c24..b1640a1eb0e 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 ef31f696284..642b719dff3 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 5158a669a36..00000000000 --- 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 ccfe4b4048a..3bf095785ea 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 a0f06d0c205..cb9047a79ff 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 7e9dce31b0a..555f119013a 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 d633545cbea..bb3fb63f1ee 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 ce03fb798a3..26747440138 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 44162dacc57..3c2dcdefa7b 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 0b9340807c5..cadd72cb7c9 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 46b043fd57b..99958818837 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 532dd65501c..368568d6b07 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 773d5863f7d..c99835165e9 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 ebde513034a..628243166eb 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 cd34bfdfc72..71e2d5fbaf7 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 7448e66ac73..7704196f6fa 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 6d8edc0c28c..eb82dd6b7c7 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 858b440d2d8..c4c5b1b7da0 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 00000000000..01e259fb9ba --- /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 aa9598dc2e5..00000000000 --- 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 c7dd7a4875f..8c8a28406bd 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 c9bc94519c2..977cc9f0a31 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 f9e57a94870..fdd9f153305 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 6e0504847bc..16fdfc4214a 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 580d2afec6e..91c8a3d52e3 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 af05a34357f..e01af545be0 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 f070c1b94db..92dddbed595 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 ebf38e4cd74..3c90f9db6dc 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 832ba537ba6..0f8d492eb1b 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 917b71b6553..f0c92e72f8e 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 d5ab1a09e9f..055102b681e 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 07fcaa8123b..bae88528b86 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 88dc77a1f1e..7070a88fea0 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 d098148b09b..fde554f0d14 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 b8dffe7e334..7a70a5791f9 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 5a79779be2e..13a0b9e12e4 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 8e147c30911..bc5be9cab8f 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 12554ff676f..81d82c48330 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 b1640a1eb0e..b25818c67f7 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 642b719dff3..84228029abb 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 3b0b9476f93..1ea26cd3fe6 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 d136dc9012a..7e467593e9d 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 3bf095785ea..d8aa4f2d137 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 cb9047a79ff..c076eebe423 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 6c54f653e80..6fe21f7725c 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 8a4c64f630c..a9b464f015f 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 e1d3dc6eb0c..5aaa0380f9a 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 bb3fb63f1ee..0e5090e584e 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 cadd72cb7c9..ffb043c340b 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 99958818837..e557a0cc120 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 368568d6b07..fbdf2ca1a60 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 c99835165e9..70a7653af49 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 71e2d5fbaf7..096ab251e88 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 7704196f6fa..9819a639919 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 eb82dd6b7c7..5d3b815810e 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 01e259fb9ba..ee4091178f4 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 d67bce9c073..f2090b5d6cf 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 c4c5b1b7da0..4ed2092447e 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 8c8a28406bd..3912dcb268b 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 3c90f9db6dc..241e550a38f 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 1ea26cd3fe6..4220facf782 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 d8aa4f2d137..163ffa057fd 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 c076eebe423..a36249078e9 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 a9b464f015f..850cb2cacb2 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 f2090b5d6cf..00000000000 --- 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 0e5090e584e..ea49fc0c6b5 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 5d3b815810e..6bb1bfa171f 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 00000000000..8d45636d198 --- /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 00000000000..8cb0e0e8c21 --- /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 ee4091178f4..7d39bd0caf7 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 00000000000..cff8b7202d1 --- /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 cd9a7077710..27c1e641b6a 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 72eebf80591..ea959552c8e 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 ab696d9c8f4..a67a9e7350e 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 5aaa0380f9a..6348f34dc95 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 ea49fc0c6b5..09c0d06cf2a 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 00000000000..210e0dfc131 --- /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 9819a639919..ef5674c6bd0 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 7d39bd0caf7..350c23bc052 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 cff8b7202d1..a94fbb8b518 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 dafcb988e7b..8774fe46f95 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 ffb043c340b..100ef641649 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 210e0dfc131..ae1cc25cc93 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 e557a0cc120..b369aca0850 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 3912dcb268b..30514835ad1 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 fdd9f153305..554d813a551 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 63f82da2575..e9026012bb0 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 0f8d492eb1b..078c354eee9 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 055102b681e..856296ceb64 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 13a0b9e12e4..e1978fe226b 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 30524be5fa4..00000000000 --- 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 4220facf782..21dc2150973 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 163ffa057fd..04458b35601 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 a36249078e9..4f6f731281e 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 850cb2cacb2..fd88b052d67 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 a6f904b1ef1..0ed7c32e8e6 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 b369aca0850..5e658feccc6 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 00000000000..0c17392d39c --- /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 fbdf2ca1a60..4a2df4928c0 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 70a7653af49..f717ac81e3a 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 a5ca56c5b50..358813af800 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 ef5674c6bd0..b536138e240 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 0dcced9a125..c5a262b3a8d 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 6bb1bfa171f..0b8b8c2d8b5 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 8d45636d198..0e82d032494 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 8cb0e0e8c21..6dbad07312f 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 350c23bc052..64ed5c4bec9 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 a94fbb8b518..5fb9d6d7d3a 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 669835afc15..c765fd34926 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 5f4f90e9a86..2236df64cd1 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 30514835ad1..671f4d993bd 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 977cc9f0a31..f565808dd7c 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 16fdfc4214a..15107e5a058 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 a71775534c5..f3c9e5c6e20 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 92dddbed595..53edca9c5dc 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 f0c92e72f8e..681f3c66ce6 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 e1978fe226b..a5f41dd5c86 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 00000000000..5e45d6a269c --- /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 81d82c48330..de077f2e176 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 e1ef76b61d5..19b4cc56dd7 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 122f2264c53..4b44badd96d 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 00000000000..63f724e558d --- /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 04458b35601..4720d257844 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 c48f1b7db3f..d7eeb411ed7 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 4f6f731281e..f7fd00b720a 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 811ebd93c59..ad98adb90db 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 100ef641649..8a1333c0fa3 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 ae1cc25cc93..0f96c948361 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 096ab251e88..324ac4e79f7 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 b536138e240..707e8580332 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 00000000000..412bd5841bb --- /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 6dbad07312f..7e4fa8bd8c3 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 ea959552c8e..7db372c12cb 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 21dc2150973..6331c003c6f 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 a67a9e7350e..abf19ce4917 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 09c0d06cf2a..07edf7e1e3d 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 26747440138..6e9267f507b 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 3c2dcdefa7b..9e96b160f2b 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 dfdee7c9666..00000000000 --- 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 5e658feccc6..f4d4b90d2a8 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 358813af800..1d25d7e5de8 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 64ed5c4bec9..53cd3dc08c7 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 f7f8da613c7..00000000000 --- 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 90d3efd6910..cf6b35594ce 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 8774fe46f95..9e63bdf8e69 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 df78a6ef63d..0bcc9f9b7c8 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 6331c003c6f..ffdcdb2cf37 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 a5a06824b0d..329c9d20921 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 19b4cc56dd7..c3e119b1f42 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 4b44badd96d..606c37ac7a2 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 7e467593e9d..3f0bef00128 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 63f724e558d..f894640a607 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 4720d257844..e33359ff04a 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 d7eeb411ed7..8a3a4e34030 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 f7fd00b720a..7d27878015f 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 ad98adb90db..8c5ccaff905 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 6fe21f7725c..f207885f380 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 fd88b052d67..7b97ca58763 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 59f29a08a3e..c3e015ceedd 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 abf19ce4917..6cfe503d617 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 6348f34dc95..9bef4eefb2e 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 0ed7c32e8e6..184a3e195a2 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 07edf7e1e3d..1ee44acf41b 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 6e9267f507b..36e713edca6 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 9e96b160f2b..92fc73b683b 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 8a1333c0fa3..0734f7c0857 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 944987eb2e7..10dd2c71df0 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 af6c74b7d2e..c0904051a07 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 0f96c948361..6cc19bf561d 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 e7860b5cea7..3304705b56c 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 c1f27cbcb7c..e5f39989c2c 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 f4d4b90d2a8..8390c3079e5 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 0c17392d39c..9284737818b 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 4a2df4928c0..867812b1606 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 f717ac81e3a..1421f519844 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 aa9d3829ee9..0e1ce1029e6 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 628243166eb..40ec854e651 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 1d7c64c0e7a..7ea6cefe7f6 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 e553f247cf2..dd6efb56ec7 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 e52ce874a1a..f73eb69625d 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 e70c9d1436a..b1560a6364b 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 324ac4e79f7..bce86dd46d0 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 1d25d7e5de8..a327e0b6bb5 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 707e8580332..0a428f3c5e2 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 412bd5841bb..60e1f707842 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 c5a262b3a8d..1236c36c554 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 0b8b8c2d8b5..6c0406171f1 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 8b29c9295c0..41c16bb7b7d 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 554d813a551..725d3c3ac68 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 15107e5a058..31f15781ed8 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 671f4d993bd..c7009b9c963 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 44d55ff0c5c..5e10c7bf018 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 61de30afa7a..3fbf767130d 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 55527575801..436e31f79be 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 e9026012bb0..3803bb96343 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 91c8a3d52e3..915ab840fbf 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 f565808dd7c..5f78084550f 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 90eb5e81702..9b35daaa4d3 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 e01af545be0..82e1a03bb82 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 f3c9e5c6e20..05f89bac38b 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 167717047ac..d523c864b3b 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 53edca9c5dc..c0fec3e6bb4 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 241e550a38f..336df01ca02 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 1d3b9d2052f..b5a32e87769 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 078c354eee9..313794ea1cb 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 681f3c66ce6..8c76b8c14ea 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 856296ceb64..c04e4c54ee9 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 bae88528b86..f469628c057 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 7070a88fea0..acf2c877fc7 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 fde554f0d14..e155c3a5aca 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 b0d74454ef2..6cedeea2c00 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 7a70a5791f9..75e6945089e 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 a5f41dd5c86..147ae65d3db 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 bc5be9cab8f..f01bb94c99a 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 5e45d6a269c..fb91266a2b4 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 de077f2e176..5db64bddaf4 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 b25818c67f7..93adce579e8 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 84228029abb..f99f27bdf34 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 e38417ac5ac..9d752c4496a 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 c35beb31389..976e512ad35 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 4ed2092447e..9f53215b37d 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 0e82d032494..bb9f84409aa 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 7e4fa8bd8c3..be5433e2a80 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 53cd3dc08c7..8727f1afaf0 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 5fb9d6d7d3a..96ab7304344 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 77ee2edf155..a135e51a404 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 0253ac66e1a..bbf973d5d85 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 1e53fc7abf8..996a7d3fdcc 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 0734f7c0857..21678e12ef4 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 6cc19bf561d..d5bb04ddfd3 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 8390c3079e5..f3948b795e7 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 f73eb69625d..5b5a29efbe8 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 725d3c3ac68..710e5e7d337 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 31f15781ed8..0abf1cc5230 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 c7009b9c963..eb461db0727 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 436e31f79be..17233f7e0e9 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 915ab840fbf..45e34d4c699 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 05f89bac38b..1c2c5814d77 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 c0fec3e6bb4..6f8e497678a 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 336df01ca02..8a335a76d2f 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 313794ea1cb..237343c9acb 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 c04e4c54ee9..be38656c676 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 f01bb94c99a..fe37568a61b 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 fb91266a2b4..48bd272d7e0 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 00000000000..7ef451307e2 --- /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 00000000000..fcc53251275 --- /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 00000000000..0f2a4c4f05b --- /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 00000000000..57849d1ba1c --- /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 00000000000..9f4b34c17f7 --- /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 a135e51a404..a8630d5319b 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 00000000000..8fa59bf2f34 --- /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 17233f7e0e9..c1a52ef37dc 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')