GUACAMOLE-504: Merge changes adding support for overriding HTTP/WebSocket status codes via custom exceptions.

This commit is contained in:
Michael Jumper
2018-02-14 15:38:00 -08:00
7 changed files with 189 additions and 51 deletions

View File

@@ -68,5 +68,29 @@ public class GuacamoleException extends Exception {
public GuacamoleStatus getStatus() { public GuacamoleStatus getStatus() {
return GuacamoleStatus.SERVER_ERROR; return GuacamoleStatus.SERVER_ERROR;
} }
/**
* Returns the most applicable HTTP status code that can be associated
* with this exception.
*
* @return
* An integer representing the most applicable HTTP status code
* associated with this exception.
*/
public int getHttpStatusCode() {
return getStatus().getHttpStatusCode();
}
/**
* Returns the most applicable WebSocket status code that can be
* associated with this exception.
*
* @return
* An integer representing the most applicable WebSocket status
* code associated with this exception.
*/
public int getWebSocketCode() {
return getStatus().getWebSocketCode();
}
} }

View File

@@ -149,26 +149,29 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
* @param response * @param response
* The HTTP response to use to send the error. * The HTTP response to use to send the error.
* *
* @param guacStatus * @param guacamoleStatusCode
* The status to send * The GuacamoleStatus code to send.
*
* @param guacamoleHttpCode
* The numeric HTTP code to send.
* *
* @param message * @param message
* A human-readable message that can be presented to the user. * The human-readable error message to send.
* *
* @throws ServletException * @throws ServletException
* If an error prevents sending of the error code. * If an error prevents sending of the error code.
*/ */
protected void sendError(HttpServletResponse response, protected void sendError(HttpServletResponse response, int guacamoleStatusCode,
GuacamoleStatus guacStatus, String message) int guacamoleHttpCode, String message)
throws ServletException { throws ServletException {
try { try {
// If response not committed, send error code and message // If response not committed, send error code and message
if (!response.isCommitted()) { if (!response.isCommitted()) {
response.addHeader("Guacamole-Status-Code", Integer.toString(guacStatus.getGuacamoleStatusCode())); response.addHeader("Guacamole-Status-Code", Integer.toString(guacamoleStatusCode));
response.addHeader("Guacamole-Error-Message", message); response.addHeader("Guacamole-Error-Message", message);
response.sendError(guacStatus.getHttpStatusCode()); response.sendError(guacamoleHttpCode);
} }
} }
@@ -237,14 +240,14 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// If read operation, call doRead() with tunnel UUID, ignoring any // If read operation, call doRead() with tunnel UUID, ignoring any
// characters following the tunnel UUID. // characters following the tunnel UUID.
else if(query.startsWith(READ_PREFIX)) else if (query.startsWith(READ_PREFIX))
doRead(request, response, query.substring( doRead(request, response, query.substring(
READ_PREFIX_LENGTH, READ_PREFIX_LENGTH,
READ_PREFIX_LENGTH + UUID_LENGTH)); READ_PREFIX_LENGTH + UUID_LENGTH));
// If write operation, call doWrite() with tunnel UUID, ignoring any // If write operation, call doWrite() with tunnel UUID, ignoring any
// characters following the tunnel UUID. // characters following the tunnel UUID.
else if(query.startsWith(WRITE_PREFIX)) else if (query.startsWith(WRITE_PREFIX))
doWrite(request, response, query.substring( doWrite(request, response, query.substring(
WRITE_PREFIX_LENGTH, WRITE_PREFIX_LENGTH,
WRITE_PREFIX_LENGTH + UUID_LENGTH)); WRITE_PREFIX_LENGTH + UUID_LENGTH));
@@ -258,12 +261,14 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// HTTP response, logging each error appropriately. // HTTP response, logging each error appropriately.
catch (GuacamoleClientException e) { catch (GuacamoleClientException e) {
logger.warn("HTTP tunnel request rejected: {}", e.getMessage()); logger.warn("HTTP tunnel request rejected: {}", e.getMessage());
sendError(response, e.getStatus(), e.getMessage()); sendError(response, e.getStatus().getGuacamoleStatusCode(),
e.getStatus().getHttpStatusCode(), e.getMessage());
} }
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("HTTP tunnel request failed: {}", e.getMessage()); logger.error("HTTP tunnel request failed: {}", e.getMessage());
logger.debug("Internal error in HTTP tunnel.", e); logger.debug("Internal error in HTTP tunnel.", e);
sendError(response, e.getStatus(), "Internal server error."); sendError(response, e.getStatus().getGuacamoleStatusCode(),
e.getStatus().getHttpStatusCode(), "Internal server error.");
} }
} }

View File

@@ -66,17 +66,24 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint {
private GuacamoleTunnel tunnel; private GuacamoleTunnel tunnel;
/** /**
* Sends the given status on the given WebSocket connection and closes the * Sends the numeric Guacaomle Status Code and Web Socket
* connection. * code and closes the connection.
* *
* @param session The outbound WebSocket connection to close. * @param session
* @param guac_status The status to send. * The outbound WebSocket connection to close.
*
* @param guacamoleStatusCode
* The numeric Guacamole status to send.
*
* @param webSocketCode
* The numeric WebSocket status to send.
*/ */
private void closeConnection(Session session, GuacamoleStatus guac_status) { private void closeConnection(Session session, int guacamoleStatusCode,
int webSocketCode) {
try { try {
CloseCode code = CloseReason.CloseCodes.getCloseCode(guac_status.getWebSocketCode()); CloseCode code = CloseReason.CloseCodes.getCloseCode(webSocketCode);
String message = Integer.toString(guac_status.getGuacamoleStatusCode()); String message = Integer.toString(guacamoleStatusCode);
session.close(new CloseReason(code, message)); session.close(new CloseReason(code, message));
} }
catch (IOException e) { catch (IOException e) {
@@ -85,6 +92,21 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint {
} }
/**
* Sends the given Guacaomle Status and closes the given
* connection.
*
* @param session
* The outbound WebSocket connection to close.
*
* @param guacStatus
* The status to use for the connection.
*/
private void closeConnection(Session session, GuacamoleStatus guacStatus) {
closeConnection(session, guacStatus.getGuacamoleStatusCode(),
guacStatus.getWebSocketCode());
}
/** /**
* Returns a new tunnel for the given session. How this tunnel is created * Returns a new tunnel for the given session. How this tunnel is created
* or retrieved is implementation-dependent. * or retrieved is implementation-dependent.
@@ -117,7 +139,8 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint {
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage()); logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e); logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(session, e.getStatus()); closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
return; return;
} }
@@ -181,7 +204,8 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint {
catch (GuacamoleClientException e) { catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage()); logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e); logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(session, e.getStatus()); closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
catch (GuacamoleConnectionClosedException e) { catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e); logger.debug("Connection to guacd closed.", e);
@@ -190,7 +214,8 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint {
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage()); logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e); logger.debug("Internal error during connection to guacd.", e);
closeConnection(session, e.getStatus()); closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
} }

View File

@@ -173,7 +173,7 @@ public class RESTExceptionWrapper implements MethodInterceptor {
// Translate GuacamoleException subclasses to HTTP error codes // Translate GuacamoleException subclasses to HTTP error codes
catch (GuacamoleException e) { catch (GuacamoleException e) {
throw new APIException( throw new APIException(
Response.Status.fromStatusCode(e.getStatus().getHttpStatusCode()), Response.Status.fromStatusCode(e.getHttpStatusCode()),
e e
); );
} }

View File

@@ -53,17 +53,42 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
private static final int BUFFER_SIZE = 8192; private static final int BUFFER_SIZE = 8192;
/** /**
* Sends the given status on the given WebSocket connection and closes the * Sends the given numeric Guacamole and WebSocket status
* on the given WebSocket connection and closes the
* connection. * connection.
* *
* @param connection The WebSocket connection to close. * @param connection
* @param guac_status The status to send. * The WebSocket connection to close.
*
* @param guacamoleStatusCode
* The numeric Guacamole Status code to send.
*
* @param webSocketCode
* The numeric WebSocket status code to send.
*/ */
public static void closeConnection(Connection connection, private static void closeConnection(Connection connection,
GuacamoleStatus guac_status) { int guacamoleStatusCode, int webSocketCode) {
connection.close(guac_status.getWebSocketCode(), connection.close(webSocketCode,
Integer.toString(guac_status.getGuacamoleStatusCode())); Integer.toString(guacamoleStatusCode));
}
/**
* Sends the given status on the given WebSocket connection
* and closes the connection.
*
* @param connection
* The WebSocket connection to close.
*
* @param guacStatus
* The status to send.
*/
private static void closeConnection(Connection connection,
GuacamoleStatus guacStatus) {
closeConnection(connection, guacStatus.getGuacamoleStatusCode(),
guacStatus.getWebSocketCode());
} }
@@ -114,7 +139,8 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage()); logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e); logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(connection, e.getStatus()); closeConnection(connection, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
return; return;
} }
@@ -168,7 +194,8 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
catch (GuacamoleClientException e) { catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage()); logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e); logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(connection, e.getStatus()); closeConnection(connection, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
catch (GuacamoleConnectionClosedException e) { catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e); logger.debug("Connection to guacd closed.", e);
@@ -177,7 +204,8 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage()); logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e); logger.debug("Internal error during connection to guacd.", e);
closeConnection(connection, e.getStatus()); closeConnection(connection, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
} }

View File

@@ -57,18 +57,25 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
private GuacamoleTunnel tunnel; private GuacamoleTunnel tunnel;
/** /**
* Sends the given status on the given WebSocket connection and closes the * Sends the given numeric Guacamole and WebSocket status
* codes on the given WebSocket connection and closes the
* connection. * connection.
* *
* @param session The outbound WebSocket connection to close. * @param session
* @param guac_status The status to send. * The outbound WebSocket connection to close.
*
* @param guacamoleStatusCode
* The numeric Guacamole status code to send.
*
* @param webSocketCode
* The numeric WebSocket status code to send.
*/ */
private void closeConnection(Session session, GuacamoleStatus guac_status) { private void closeConnection(Session session, int guacamoleStatusCode,
int webSocketCode) {
try { try {
int code = guac_status.getWebSocketCode(); String message = Integer.toString(guacamoleStatusCode);
String message = Integer.toString(guac_status.getGuacamoleStatusCode()); session.close(new CloseStatus(webSocketCode, message));
session.close(new CloseStatus(code, message));
} }
catch (IOException e) { catch (IOException e) {
logger.debug("Unable to close WebSocket connection.", e); logger.debug("Unable to close WebSocket connection.", e);
@@ -76,6 +83,24 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
} }
/**
* Sends the given status on the given WebSocket connection
* and closes the connection.
*
* @param session
* The outbound WebSocket connection to close.
*
* @param guacStatus
* The status to send.
*/
private void closeConnection(Session session,
GuacamoleStatus guacStatus) {
closeConnection(session, guacStatus.getGuacamoleStatusCode(),
guacStatus.getWebSocketCode());
}
/** /**
* Returns a new tunnel for the given session. How this tunnel is created * Returns a new tunnel for the given session. How this tunnel is created
* or retrieved is implementation-dependent. * or retrieved is implementation-dependent.
@@ -105,7 +130,7 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage()); logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e); logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(session, e.getStatus()); closeConnection(session, e.getStatus().getGuacamoleStatusCode(), e.getWebSocketCode());
return; return;
} }
@@ -159,7 +184,8 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
catch (GuacamoleClientException e) { catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage()); logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e); logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(session, e.getStatus()); closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
catch (GuacamoleConnectionClosedException e) { catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e); logger.debug("Connection to guacd closed.", e);
@@ -168,7 +194,8 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage()); logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e); logger.debug("Internal error during connection to guacd.", e);
closeConnection(session, e.getStatus()); closeConnection(session, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
} }

View File

@@ -58,17 +58,25 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class); private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
/** /**
* Sends the given status on the given WebSocket connection and closes the * Sends the given Guacamole and WebSocket numeric status
* on the given WebSocket connection and closes the
* connection. * connection.
* *
* @param outbound The outbound WebSocket connection to close. * @param outbound
* @param guac_status The status to send. * The outbound WebSocket connection to close.
*
* @param guacamoleStatusCode
* The status to send.
*
* @param webSocketCode
* The numeric WebSocket status code to send.
*/ */
public void closeConnection(WsOutbound outbound, GuacamoleStatus guac_status) { private void closeConnection(WsOutbound outbound, int guacamoleStatusCode,
int webSocketCode) {
try { try {
byte[] message = Integer.toString(guac_status.getGuacamoleStatusCode()).getBytes("UTF-8"); byte[] message = Integer.toString(guacamoleStatusCode).getBytes("UTF-8");
outbound.close(guac_status.getWebSocketCode(), ByteBuffer.wrap(message)); outbound.close(webSocketCode, ByteBuffer.wrap(message));
} }
catch (IOException e) { catch (IOException e) {
logger.debug("Unable to close WebSocket tunnel.", e); logger.debug("Unable to close WebSocket tunnel.", e);
@@ -76,6 +84,24 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
} }
/**
* Sends the given status on the given WebSocket connection
* and closes the connection.
*
* @param outbound
* The outbound WebSocket connection to close.
*
* @param guacStatus
* The status to send.
*/
private void closeConnection(WsOutbound outbound,
GuacamoleStatus guacStatus) {
closeConnection(outbound, guacStatus.getGuacamoleStatusCode(),
guacStatus.getWebSocketCode());
}
@Override @Override
protected String selectSubProtocol(List<String> subProtocols) { protected String selectSubProtocol(List<String> subProtocols) {
@@ -142,7 +168,8 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage()); logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e); logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(outbound, e.getStatus()); closeConnection(outbound, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
return; return;
} }
@@ -196,7 +223,8 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
catch (GuacamoleClientException e) { catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage()); logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e); logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(outbound, e.getStatus()); closeConnection(outbound, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
catch (GuacamoleConnectionClosedException e) { catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e); logger.debug("Connection to guacd closed.", e);
@@ -205,7 +233,8 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
catch (GuacamoleException e) { catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage()); logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e); logger.debug("Internal error during connection to guacd.", e);
closeConnection(outbound, e.getStatus()); closeConnection(outbound, e.getStatus().getGuacamoleStatusCode(),
e.getWebSocketCode());
} }
} }