From b83c83c324f004d8313f9e598057703af60580fa Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 26 Oct 2014 15:14:35 -0700 Subject: [PATCH] GUAC-906: Implement GuacamoleConnectionClosedException. Throw when read/write fails due to closure. --- .../GuacamoleConnectionClosedException.java | 72 +++++++++++++++++++ .../guacamole/io/ReaderGuacamoleReader.java | 5 ++ .../guacamole/io/WriterGuacamoleWriter.java | 10 +++ .../servlet/GuacamoleHTTPTunnelServlet.java | 31 +++++--- .../GuacamoleWebSocketTunnelEndpoint.java | 8 +++ .../GuacamoleWebSocketTunnelServlet.java | 8 +++ .../GuacamoleWebSocketTunnelListener.java | 8 +++ .../GuacamoleWebSocketTunnelServlet.java | 8 +++ 8 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 guacamole-common/src/main/java/org/glyptodon/guacamole/GuacamoleConnectionClosedException.java diff --git a/guacamole-common/src/main/java/org/glyptodon/guacamole/GuacamoleConnectionClosedException.java b/guacamole-common/src/main/java/org/glyptodon/guacamole/GuacamoleConnectionClosedException.java new file mode 100644 index 000000000..a88728299 --- /dev/null +++ b/guacamole-common/src/main/java/org/glyptodon/guacamole/GuacamoleConnectionClosedException.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole; + +import org.glyptodon.guacamole.protocol.GuacamoleStatus; + + +/** + * An exception which is thrown when an operation cannot be performed because + * its corresponding connection is closed. + * + * @author Michael Jumper + */ +public class GuacamoleConnectionClosedException extends GuacamoleServerException { + + /** + * Creates a new GuacamoleConnectionClosedException with the given message + * and cause. + * + * @param message A human readable description of the exception that + * occurred. + * @param cause The cause of this exception. + */ + public GuacamoleConnectionClosedException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Creates a new GuacamoleConnectionClosedException with the given message. + * + * @param message A human readable description of the exception that + * occurred. + */ + public GuacamoleConnectionClosedException(String message) { + super(message); + } + + /** + * Creates a new GuacamoleConnectionClosedException with the given cause. + * + * @param cause The cause of this exception. + */ + public GuacamoleConnectionClosedException(Throwable cause) { + super(cause); + } + + @Override + public GuacamoleStatus getStatus() { + return GuacamoleStatus.SERVER_ERROR; + } + +} diff --git a/guacamole-common/src/main/java/org/glyptodon/guacamole/io/ReaderGuacamoleReader.java b/guacamole-common/src/main/java/org/glyptodon/guacamole/io/ReaderGuacamoleReader.java index 9cfc3d265..756d93902 100644 --- a/guacamole-common/src/main/java/org/glyptodon/guacamole/io/ReaderGuacamoleReader.java +++ b/guacamole-common/src/main/java/org/glyptodon/guacamole/io/ReaderGuacamoleReader.java @@ -25,9 +25,11 @@ package org.glyptodon.guacamole.io; import java.io.IOException; import java.io.Reader; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.Deque; import java.util.LinkedList; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleServerException; import org.glyptodon.guacamole.GuacamoleUpstreamTimeoutException; @@ -182,6 +184,9 @@ public class ReaderGuacamoleReader implements GuacamoleReader { catch (SocketTimeoutException e) { throw new GuacamoleUpstreamTimeoutException("Connection to guacd timed out.", e); } + catch (SocketException e) { + throw new GuacamoleConnectionClosedException("Connection to guacd is closed.", e); + } catch (IOException e) { throw new GuacamoleServerException(e); } diff --git a/guacamole-common/src/main/java/org/glyptodon/guacamole/io/WriterGuacamoleWriter.java b/guacamole-common/src/main/java/org/glyptodon/guacamole/io/WriterGuacamoleWriter.java index 5f0068ee7..1301d68ab 100644 --- a/guacamole-common/src/main/java/org/glyptodon/guacamole/io/WriterGuacamoleWriter.java +++ b/guacamole-common/src/main/java/org/glyptodon/guacamole/io/WriterGuacamoleWriter.java @@ -25,8 +25,12 @@ package org.glyptodon.guacamole.io; import java.io.IOException; import java.io.Writer; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleServerException; +import org.glyptodon.guacamole.GuacamoleUpstreamTimeoutException; import org.glyptodon.guacamole.protocol.GuacamoleInstruction; /** @@ -58,6 +62,12 @@ public class WriterGuacamoleWriter implements GuacamoleWriter { output.write(chunk, off, len); output.flush(); } + catch (SocketTimeoutException e) { + throw new GuacamoleUpstreamTimeoutException("Connection to guacd timed out.", e); + } + catch (SocketException e) { + throw new GuacamoleConnectionClosedException("Connection to guacd is closed.", e); + } catch (IOException e) { throw new GuacamoleServerException(e); } diff --git a/guacamole-common/src/main/java/org/glyptodon/guacamole/servlet/GuacamoleHTTPTunnelServlet.java b/guacamole-common/src/main/java/org/glyptodon/guacamole/servlet/GuacamoleHTTPTunnelServlet.java index 1ad950d9e..7539793d2 100644 --- a/guacamole-common/src/main/java/org/glyptodon/guacamole/servlet/GuacamoleHTTPTunnelServlet.java +++ b/guacamole-common/src/main/java/org/glyptodon/guacamole/servlet/GuacamoleHTTPTunnelServlet.java @@ -35,9 +35,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleResourceNotFoundException; -import org.glyptodon.guacamole.GuacamoleSecurityException; import org.glyptodon.guacamole.GuacamoleServerException; import org.glyptodon.guacamole.io.GuacamoleReader; import org.glyptodon.guacamole.io.GuacamoleWriter; @@ -287,7 +287,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { // data yet. char[] message = reader.read(); if (message == null) - throw new GuacamoleResourceNotFoundException("Tunnel reached end of stream."); + throw new GuacamoleConnectionClosedException("Tunnel reached end of stream."); // For all messages, until another stream is ready (we send at least one message) do { @@ -317,20 +317,28 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { response.flushBuffer(); } + // Send end-of-stream marker if connection is closed + catch (GuacamoleConnectionClosedException e) { + out.write("0.;"); + out.flush(); + response.flushBuffer(); + } + + catch (GuacamoleException e) { + + // Detach and close + session.detachTunnel(tunnel); + tunnel.close(); + + throw e; + } + // Always close output stream finally { out.close(); } } - catch (GuacamoleException e) { - - // Detach and close - session.detachTunnel(tunnel); - tunnel.close(); - - throw e; - } catch (IOException e) { // Log typically frequent I/O error if desired @@ -411,6 +419,9 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet { } } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + } catch (IOException e) { // Detach and close diff --git a/guacamole-common/src/main/java/org/glyptodon/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java b/guacamole-common/src/main/java/org/glyptodon/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java index 1459cc600..e176c96f9 100644 --- a/guacamole-common/src/main/java/org/glyptodon/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java +++ b/guacamole-common/src/main/java/org/glyptodon/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java @@ -38,6 +38,7 @@ import org.glyptodon.guacamole.io.GuacamoleReader; import org.glyptodon.guacamole.io.GuacamoleWriter; import org.glyptodon.guacamole.net.GuacamoleTunnel; import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.protocol.GuacamoleStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -178,6 +179,10 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { logger.warn("Client request rejected: {}", e.getMessage()); closeConnection(session, e.getStatus()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + closeConnection(session, GuacamoleStatus.SUCCESS); + } catch (GuacamoleException e) { logger.error("Internal server error.", e); closeConnection(session, e.getStatus()); @@ -205,6 +210,9 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { // Write received message writer.write(message.toCharArray()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + } catch (GuacamoleException e) { logger.debug("Tunnel write failed.", e); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java index 293542619..f22ba199e 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocket.Connection; import org.eclipse.jetty.websocket.WebSocketServlet; import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.protocol.GuacamoleStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,6 +94,9 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { try { writer.write(string.toCharArray()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + } catch (GuacamoleException e) { logger.debug("Tunnel write failed.", e); } @@ -148,6 +152,10 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { logger.warn("Client request rejected: {}", e.getMessage()); closeConnection(connection, e.getStatus()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + closeConnection(connection, GuacamoleStatus.SUCCESS); + } catch (GuacamoleException e) { logger.error("Internal server error.", e); closeConnection(connection, e.getStatus()); diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java index fee776442..896fcfbfb 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.io.GuacamoleReader; import org.glyptodon.guacamole.io.GuacamoleWriter; @@ -164,6 +165,10 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe logger.warn("Client request rejected: {}", e.getMessage()); closeConnection(session, e.getStatus()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + closeConnection(session, GuacamoleStatus.SUCCESS); + } catch (GuacamoleException e) { logger.error("Internal server error.", e); closeConnection(session, e.getStatus()); @@ -191,6 +196,9 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe // Write received message writer.write(message.toCharArray()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + } catch (GuacamoleException e) { logger.debug("Tunnel write failed.", e); } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java index 01da60936..3b542b2ed 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java @@ -37,6 +37,7 @@ import org.apache.catalina.websocket.StreamInbound; import org.apache.catalina.websocket.WebSocketServlet; import org.apache.catalina.websocket.WsOutbound; import org.glyptodon.guacamole.GuacamoleClientException; +import org.glyptodon.guacamole.GuacamoleConnectionClosedException; import org.glyptodon.guacamole.protocol.GuacamoleStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -120,6 +121,9 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { writer.write(buffer, 0, num_read); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + } catch (GuacamoleException e) { logger.debug("Tunnel write failed.", e); } @@ -180,6 +184,10 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { logger.warn("Client request rejected: {}", e.getMessage()); closeConnection(outbound, e.getStatus()); } + catch (GuacamoleConnectionClosedException e) { + logger.debug("Connection closed.", e); + closeConnection(outbound, GuacamoleStatus.SUCCESS); + } catch (GuacamoleException e) { logger.error("Internal server error.", e); closeConnection(outbound, e.getStatus());