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
+ * 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.