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 80dcc5aa855..fe9d1d9f120 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 @@ -132,50 +132,55 @@ public class DefaultHandshakeHandler implements HandshakeHandler { @Override public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler webSocketHandler, Map attributes) throws IOException, HandshakeFailureException { + WebSocketHandler wsHandler, Map attributes) throws HandshakeFailureException { if (logger.isDebugEnabled()) { logger.debug("Initiating handshake for " + request.getURI() + ", headers=" + request.getHeaders()); } - 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; + try { + 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 (!"WebSocket".equalsIgnoreCase(request.getHeaders().getUpgrade())) { + handleInvalidUpgradeHeader(request, response); + return false; + } + if (!request.getHeaders().getConnection().contains("Upgrade") && + !request.getHeaders().getConnection().contains("upgrade")) { + handleInvalidConnectHeader(request, response); + return false; + } + if (!isWebSocketVersionSupported(request)) { + handleWebSocketVersionNotSupported(request, response); + return false; + } + if (!isValidOrigin(request)) { + response.setStatusCode(HttpStatus.FORBIDDEN); + return false; + } + String wsKey = request.getHeaders().getSecWebSocketKey(); + if (wsKey == null) { + logger.debug("Missing \"Sec-WebSocket-Key\" header"); + response.setStatusCode(HttpStatus.BAD_REQUEST); + return false; + } } - if (!"WebSocket".equalsIgnoreCase(request.getHeaders().getUpgrade())) { - handleInvalidUpgradeHeader(request, response); - return false; - } - if (!request.getHeaders().getConnection().contains("Upgrade") && - !request.getHeaders().getConnection().contains("upgrade")) { - handleInvalidConnectHeader(request, response); - return false; - } - if (!isWebSocketVersionSupported(request)) { - handleWebSocketVersionNotSupported(request, response); - return false; - } - if (!isValidOrigin(request)) { - response.setStatusCode(HttpStatus.FORBIDDEN); - return false; - } - String wsKey = request.getHeaders().getSecWebSocketKey(); - if (wsKey == null) { - logger.debug("Missing \"Sec-WebSocket-Key\" header"); - response.setStatusCode(HttpStatus.BAD_REQUEST); - return false; + catch (IOException ex) { + throw new HandshakeFailureException( + "Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex); } - String selectedProtocol = selectProtocol(request.getHeaders().getSecWebSocketProtocol()); - // TODO: select extensions + String subProtocol = selectProtocol(request.getHeaders().getSecWebSocketProtocol()); if (logger.isDebugEnabled()) { - logger.debug("Upgrading request"); + logger.debug("Upgrading request, sub-protocol=" + subProtocol); } - this.requestUpgradeStrategy.upgrade(request, response, selectedProtocol, webSocketHandler, attributes); + this.requestUpgradeStrategy.upgrade(request, response, subProtocol, wsHandler, attributes); return true; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java index 121e3b454ff..defd8776c4b 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeFailureException.java @@ -34,12 +34,19 @@ import org.springframework.core.NestedRuntimeException; @SuppressWarnings("serial") public class HandshakeFailureException extends NestedRuntimeException { - public HandshakeFailureException(String msg, Throwable cause) { - super(msg, cause); + + /** + * Constructor with message and root cause. + */ + public HandshakeFailureException(String message, Throwable cause) { + super(message, cause); } - public HandshakeFailureException(String msg) { - super(msg); + /** + * Constructor without a message. + */ + public HandshakeFailureException(String message) { + super(message); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java index a463961fc3b..f2576f6559d 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/HandshakeHandler.java @@ -16,7 +16,6 @@ package org.springframework.web.socket.server; -import java.io.IOException; import java.util.Map; import org.springframework.http.server.ServerHttpRequest; @@ -36,6 +35,7 @@ import org.springframework.web.socket.support.PerConnectionWebSocketHandler; */ public interface HandshakeHandler { + /** * Initiate the handshake. * @@ -51,14 +51,11 @@ public interface HandshakeHandler { * response status, headers, and body will have been updated to reflect the * result of the negotiation * - * @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. + * opposed to a failure to successfully negotiate the handshake. */ boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, - Map attributes) throws IOException, HandshakeFailureException; + Map attributes) throws HandshakeFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java index 9a8e360d35b..b7814264455 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/RequestUpgradeStrategy.java @@ -16,7 +16,6 @@ package org.springframework.web.socket.server; -import java.io.IOException; import java.util.Map; import org.springframework.http.server.ServerHttpRequest; @@ -53,6 +52,6 @@ public interface RequestUpgradeStrategy { * handshake request. */ void upgrade(ServerHttpRequest request, ServerHttpResponse response, String acceptedProtocol, - WebSocketHandler wsHandler, Map attributes) throws IOException, HandshakeFailureException; + WebSocketHandler wsHandler, Map attributes) throws HandshakeFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractStandardUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractStandardUpgradeStrategy.java index 127e6511690..925540c3fcb 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractStandardUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractStandardUpgradeStrategy.java @@ -16,7 +16,6 @@ package org.springframework.web.socket.server.support; -import java.io.IOException; import java.net.InetSocketAddress; import java.util.Map; @@ -34,7 +33,8 @@ import org.springframework.web.socket.server.HandshakeFailureException; import org.springframework.web.socket.server.RequestUpgradeStrategy; /** - * A {@link RequestUpgradeStrategy} for containers that support standard Java WebSocket. + * A base class for {@link RequestUpgradeStrategy} implementations that build on the + * standard WebSocket API for Java. * * @author Rossen Stoyanchev * @since 4.0 @@ -46,20 +46,20 @@ public abstract class AbstractStandardUpgradeStrategy implements RequestUpgradeS @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, String acceptedProtocol, - WebSocketHandler wsHandler, Map attributes) - throws IOException, HandshakeFailureException { + WebSocketHandler wsHandler, Map attributes) throws HandshakeFailureException { HttpHeaders headers = request.getHeaders(); + InetSocketAddress localAddr = request.getLocalAddress(); InetSocketAddress remoteAddr = request.getRemoteAddress(); - StandardWebSocketSession wsSession = new StandardWebSocketSession(headers, attributes, localAddr, remoteAddr); - StandardWebSocketHandlerAdapter endpoint = new StandardWebSocketHandlerAdapter(wsHandler, wsSession); + StandardWebSocketSession session = new StandardWebSocketSession(headers, attributes, localAddr, remoteAddr); + StandardWebSocketHandlerAdapter endpoint = new StandardWebSocketHandlerAdapter(wsHandler, session); upgradeInternal(request, response, acceptedProtocol, endpoint); } protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, Endpoint endpoint) throws IOException, HandshakeFailureException; + String selectedProtocol, Endpoint endpoint) throws HandshakeFailureException; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java index ccb055f1e58..99ba61474ce 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/GlassFishRequestUpgradeStrategy.java @@ -70,7 +70,7 @@ public class GlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStra @Override public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - String selectedProtocol, Endpoint endpoint) throws IOException, HandshakeFailureException { + String selectedProtocol, Endpoint endpoint) throws HandshakeFailureException { Assert.isTrue(request instanceof ServletServerHttpRequest); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -85,12 +85,16 @@ public class GlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStra webSocketEngine.register(webSocketApplication); } catch (DeploymentException ex) { - throw new HandshakeFailureException("Failed to deploy endpoint in GlassFish", ex); + throw new HandshakeFailureException("Failed to configure endpoint in GlassFish", ex); } try { performUpgrade(servletRequest, servletResponse, request.getHeaders(), webSocketApplication); } + catch (IOException ex) { + throw new HandshakeFailureException( + "Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex); + } finally { webSocketEngine.unregister(webSocketApplication); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java index 184033ca0a3..1efd0706642 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/JettyRequestUpgradeStrategy.java @@ -44,30 +44,26 @@ import org.springframework.web.socket.server.RequestUpgradeStrategy; * {@code org.eclipse.jetty.websocket.server.WebSocketHandler} class. * * @author Phillip Webb + * @author Rossen Stoyanchev * @since 4.0 */ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { - // 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 WEBSOCKET_LISTENER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName() - + ".HANDLER_PROVIDER"; + private static final String WS_HANDLER_ATTR_NAME = JettyRequestUpgradeStrategy.class.getName() + ".WS_LISTENER"; private WebSocketServerFactory factory; + /** + * Default constructor. + */ public JettyRequestUpgradeStrategy() { this.factory = new WebSocketServerFactory(); this.factory.setCreator(new WebSocketCreator() { @Override public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) { Assert.isInstanceOf(ServletUpgradeRequest.class, request); - return ((ServletUpgradeRequest) request).getServletAttributes().get(WEBSOCKET_LISTENER_ATTR_NAME); + return ((ServletUpgradeRequest) request).getServletAttributes().get(WS_HANDLER_ATTR_NAME); } }); try { @@ -86,7 +82,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { @Override public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - String protocol, WebSocketHandler wsHandler, Map attrs) throws IOException { + String protocol, WebSocketHandler wsHandler, Map attrs) throws HandshakeFailureException { Assert.isInstanceOf(ServletServerHttpRequest.class, request); HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); @@ -94,19 +90,21 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy { Assert.isInstanceOf(ServletServerHttpResponse.class, response); HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse(); - if (!this.factory.isUpgradeRequest(servletRequest, servletResponse)) { - // should never happen - throw new HandshakeFailureException("Not a WebSocket request"); + Assert.isTrue(this.factory.isUpgradeRequest(servletRequest, servletResponse), "Not a WebSocket handshake"); + + JettyWebSocketSession session = new JettyWebSocketSession(request.getPrincipal(), attrs); + JettyWebSocketHandlerAdapter handlerAdapter = new JettyWebSocketHandlerAdapter(wsHandler, session); + + try { + servletRequest.setAttribute(WS_HANDLER_ATTR_NAME, handlerAdapter); + this.factory.acceptWebSocket(servletRequest, servletResponse); } - - JettyWebSocketSession wsSession = new JettyWebSocketSession(request.getPrincipal(), attrs); - JettyWebSocketHandlerAdapter wsListener = new JettyWebSocketHandlerAdapter(wsHandler, wsSession); - - servletRequest.setAttribute(WEBSOCKET_LISTENER_ATTR_NAME, wsListener); - - if (!this.factory.acceptWebSocket(servletRequest, servletResponse)) { - // should not happen - throw new HandshakeFailureException("WebSocket request not accepted by Jetty"); + catch (IOException ex) { + throw new HandshakeFailureException( + "Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex); + } + finally { + servletRequest.removeAttribute(WS_HANDLER_ATTR_NAME); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java index eccc85c7c1a..854d22a59ff 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/TomcatRequestUpgradeStrategy.java @@ -16,11 +16,13 @@ package org.springframework.web.socket.server.support; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Map; import javax.servlet.ServletContext; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Endpoint; @@ -67,8 +69,13 @@ public class TomcatRequestUpgradeStrategy extends AbstractStandardUpgradeStrateg try { getContainer(servletRequest).doUpgrade(servletRequest, servletResponse, endpointConfig, pathParams); } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to upgrade HttpServletRequest", ex); + catch (ServletException ex) { + throw new HandshakeFailureException( + "Servlet request failed to upgrade to WebSocket, uri=" + request.getURI(), ex); + } + catch (IOException ex) { + throw new HandshakeFailureException( + "Response update failed during upgrade to WebSocket, uri=" + request.getURI(), ex); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsException.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsException.java index ccd2f325989..27b0a0ccdb2 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsException.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsException.java @@ -30,6 +30,10 @@ public class SockJsException extends NestedRuntimeException { private final String sessionId; + public SockJsException(String message, Throwable cause) { + this(message, null, cause); + } + public SockJsException(String message, String sessionId, Throwable cause) { super(message, cause); this.sessionId = sessionId; diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java index d2416fb07db..e2d5073631a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsHttpRequestHandler.java @@ -74,10 +74,15 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler { public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException { - ServerHttpRequest serverRequest = new ServletServerHttpRequest(servletRequest); - ServerHttpResponse serverResponse = new ServletServerHttpResponse(servletResponse); + ServerHttpRequest request = new ServletServerHttpRequest(servletRequest); + ServerHttpResponse response = new ServletServerHttpResponse(servletResponse); - this.sockJsService.handleRequest(serverRequest, serverResponse, this.webSocketHandler); + try { + this.sockJsService.handleRequest(request, response, this.webSocketHandler); + } + catch (Throwable t) { + throw new SockJsException("Uncaught failure in SockJS request, uri=" + request.getURI(), t); + } } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java index 788cb93bd6f..68958c75020 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/handler/WebSocketTransportHandler.java @@ -16,7 +16,6 @@ package org.springframework.web.socket.sockjs.transport.handler; -import java.io.IOException; import java.util.Collections; import java.util.Map; @@ -26,6 +25,7 @@ import org.springframework.util.Assert; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.server.HandshakeFailureException; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsTransportFailureException; @@ -35,7 +35,7 @@ import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSes import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession; /** - * A WebSocket {@link TransportHandler}. Uses {@link SockJsWebSocketHandler} and + * WebSocket-based {@link TransportHandler}. Uses {@link SockJsWebSocketHandler} and * {@link WebSocketServerSockJsSession} to add SockJS processing. * *

Also implements {@link HandshakeHandler} to support raw WebSocket communication at @@ -87,7 +87,7 @@ public class WebSocketTransportHandler extends TransportHandlerSupport @Override public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, - WebSocketHandler handler, Map attributes) throws IOException { + WebSocketHandler handler, Map attributes) throws HandshakeFailureException { return this.handshakeHandler.doHandshake(request, response, handler, attributes); }