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.
This commit is contained in:
Rossen Stoyanchev 2013-05-01 15:57:55 -04:00
parent 9ca4672300
commit 166ca7a5a3
24 changed files with 277 additions and 121 deletions

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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")));

View File

@ -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 {

View File

@ -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.
*
* <p>
* 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");

View File

@ -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 {
}

View File

@ -17,7 +17,15 @@
package org.springframework.websocket;
/**
* A handler for WebSocket sessions.
* A handler for WebSocket messages and lifecycle events.
*
* <p> 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 <T> 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.

View File

@ -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);
}
}
}

View File

@ -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<String>() {
@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);
}
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.websocket.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.
*
* <p>
* 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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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");
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}