mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-567: Add support for WebSocket-specific ping messages to the legacy WebSocket tunnel implementations.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
@@ -30,6 +31,8 @@ import org.eclipse.jetty.websocket.WebSocket.Connection;
|
||||
import org.eclipse.jetty.websocket.WebSocketServlet;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.protocol.FilteredGuacamoleWriter;
|
||||
import org.apache.guacamole.protocol.GuacamoleFilter;
|
||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
@@ -52,6 +55,15 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* The opcode of the instruction used to indicate a connection stability
|
||||
* test ping request or response. Note that this instruction is
|
||||
* encapsulated within an internal tunnel instruction (with the opcode
|
||||
* being the empty string), thus this will actually be the value of the
|
||||
* first element of the received instruction.
|
||||
*/
|
||||
private static final String PING_OPCODE = "ping";
|
||||
|
||||
/**
|
||||
* Sends the given numeric Guacamole and WebSocket status
|
||||
* on the given WebSocket connection and closes the
|
||||
@@ -106,6 +118,58 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
*/
|
||||
private GuacamoleTunnel tunnel = null;
|
||||
|
||||
/**
|
||||
* The active WebSocket connection. This value will always be
|
||||
* non-null if tunnel is non-null.
|
||||
*/
|
||||
private Connection connection = null;
|
||||
|
||||
/**
|
||||
* Sends a Guacamole instruction along the outbound WebSocket
|
||||
* connection to the connected Guacamole client. If an instruction
|
||||
* is already in the process of being sent by another thread, this
|
||||
* function will block until in-progress instructions are complete.
|
||||
*
|
||||
* @param instruction
|
||||
* The instruction to send.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs preventing the given instruction from
|
||||
* being sent.
|
||||
*/
|
||||
private void sendInstruction(String instruction)
|
||||
throws IOException {
|
||||
|
||||
// NOTE: Synchronization on the non-final remote field here is
|
||||
// intentional. The outbound websocket connection is only
|
||||
// sensitive to simultaneous attempts to send messages with
|
||||
// respect to itself. If the connection changes, then
|
||||
// synchronization need only be performed in context of the new
|
||||
// connection
|
||||
synchronized (connection) {
|
||||
connection.sendMessage(instruction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Guacamole instruction along the outbound WebSocket
|
||||
* connection to the connected Guacamole client. If an instruction
|
||||
* is already in the process of being sent by another thread, this
|
||||
* function will block until in-progress instructions are complete.
|
||||
*
|
||||
* @param instruction
|
||||
* The instruction to send.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs preventing the given instruction from being
|
||||
* sent.
|
||||
*/
|
||||
private void sendInstruction(GuacamoleInstruction instruction)
|
||||
throws IOException {
|
||||
sendInstruction(instruction.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String string) {
|
||||
|
||||
@@ -113,7 +177,43 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
// Filter received instructions, handling tunnel-internal
|
||||
// instructions without passing through to guacd
|
||||
GuacamoleWriter writer = new FilteredGuacamoleWriter(tunnel.acquireWriter(), new GuacamoleFilter() {
|
||||
|
||||
@Override
|
||||
public GuacamoleInstruction filter(GuacamoleInstruction instruction)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Filter out all tunnel-internal instructions
|
||||
if (instruction.getOpcode().equals(GuacamoleTunnel.INTERNAL_DATA_OPCODE)) {
|
||||
|
||||
// Respond to ping requests
|
||||
List<String> args = instruction.getArgs();
|
||||
if (args.size() >= 2 && args.get(0).equals(PING_OPCODE)) {
|
||||
|
||||
try {
|
||||
sendInstruction(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
PING_OPCODE, args.get(1)
|
||||
));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("Unable to send \"ping\" response for WebSocket tunnel.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// Pass through all non-internal instructions untouched
|
||||
return instruction;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Write message received
|
||||
try {
|
||||
@@ -133,6 +233,9 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
@Override
|
||||
public void onOpen(final Connection connection) {
|
||||
|
||||
// Store websocket connection for future use via sendInstruction()
|
||||
this.connection = connection;
|
||||
|
||||
try {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
@@ -162,10 +265,10 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
try {
|
||||
|
||||
// Send tunnel UUID
|
||||
connection.sendMessage(new GuacamoleInstruction(
|
||||
sendInstruction(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
tunnel.getUUID().toString()
|
||||
).toString());
|
||||
));
|
||||
|
||||
try {
|
||||
|
||||
@@ -177,7 +280,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
connection.sendMessage(buffer.toString());
|
||||
sendInstruction(buffer.toString());
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus;
|
||||
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
@@ -30,6 +31,8 @@ import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.GuacamoleWriter;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.protocol.FilteredGuacamoleWriter;
|
||||
import org.apache.guacamole.protocol.GuacamoleFilter;
|
||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
import org.slf4j.Logger;
|
||||
@@ -45,6 +48,15 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* The opcode of the instruction used to indicate a connection stability
|
||||
* test ping request or response. Note that this instruction is
|
||||
* encapsulated within an internal tunnel instruction (with the opcode
|
||||
* being the empty string), thus this will actually be the value of the
|
||||
* first element of the received instruction.
|
||||
*/
|
||||
private static final String PING_OPCODE = "ping";
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
@@ -52,10 +64,17 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
|
||||
/**
|
||||
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
|
||||
* as reads/writes to this tunnel.
|
||||
* as reads/writes to this tunnel. This value may be null if no connection
|
||||
* has been established.
|
||||
*/
|
||||
private GuacamoleTunnel tunnel;
|
||||
|
||||
|
||||
/**
|
||||
* Remote (client) side of this connection. This value will always be
|
||||
* non-null if tunnel is non-null.
|
||||
*/
|
||||
private RemoteEndpoint remote;
|
||||
|
||||
/**
|
||||
* Sends the given numeric Guacamole and WebSocket status
|
||||
* codes on the given WebSocket connection and closes the
|
||||
@@ -101,6 +120,52 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Guacamole instruction along the outbound WebSocket connection to
|
||||
* the connected Guacamole client. If an instruction is already in the
|
||||
* process of being sent by another thread, this function will block until
|
||||
* in-progress instructions are complete.
|
||||
*
|
||||
* @param instruction
|
||||
* The instruction to send.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs preventing the given instruction from being
|
||||
* sent.
|
||||
*/
|
||||
private void sendInstruction(String instruction)
|
||||
throws IOException {
|
||||
|
||||
// NOTE: Synchronization on the non-final remote field here is
|
||||
// intentional. The remote (the outbound websocket connection) is only
|
||||
// sensitive to simultaneous attempts to send messages with respect to
|
||||
// itself. If the remote changes, then the outbound websocket
|
||||
// connection has changed, and synchronization need only be performed
|
||||
// in context of the new remote.
|
||||
synchronized (remote) {
|
||||
remote.sendString(instruction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Guacamole instruction along the outbound WebSocket connection to
|
||||
* the connected Guacamole client. If an instruction is already in the
|
||||
* process of being sent by another thread, this function will block until
|
||||
* in-progress instructions are complete.
|
||||
*
|
||||
* @param instruction
|
||||
* The instruction to send.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs preventing the given instruction from being
|
||||
* sent.
|
||||
*/
|
||||
private void sendInstruction(GuacamoleInstruction instruction)
|
||||
throws IOException {
|
||||
sendInstruction(instruction.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new tunnel for the given session. How this tunnel is created
|
||||
* or retrieved is implementation-dependent.
|
||||
@@ -117,6 +182,9 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
@Override
|
||||
public void onWebSocketConnect(final Session session) {
|
||||
|
||||
// Store underlying remote for future use via sendInstruction()
|
||||
remote = session.getRemote();
|
||||
|
||||
try {
|
||||
|
||||
// Get tunnel
|
||||
@@ -137,11 +205,6 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
// Prepare read transfer thread
|
||||
Thread readThread = new Thread() {
|
||||
|
||||
/**
|
||||
* Remote (client) side of this connection
|
||||
*/
|
||||
private final RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -152,10 +215,10 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
try {
|
||||
|
||||
// Send tunnel UUID
|
||||
remote.sendString(new GuacamoleInstruction(
|
||||
sendInstruction(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
tunnel.getUUID().toString()
|
||||
).toString());
|
||||
));
|
||||
|
||||
try {
|
||||
|
||||
@@ -167,7 +230,7 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
remote.sendString(buffer.toString());
|
||||
sendInstruction(buffer.toString());
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
@@ -219,7 +282,43 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
// Filter received instructions, handling tunnel-internal instructions
|
||||
// without passing through to guacd
|
||||
GuacamoleWriter writer = new FilteredGuacamoleWriter(tunnel.acquireWriter(), new GuacamoleFilter() {
|
||||
|
||||
@Override
|
||||
public GuacamoleInstruction filter(GuacamoleInstruction instruction)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Filter out all tunnel-internal instructions
|
||||
if (instruction.getOpcode().equals(GuacamoleTunnel.INTERNAL_DATA_OPCODE)) {
|
||||
|
||||
// Respond to ping requests
|
||||
List<String> args = instruction.getArgs();
|
||||
if (args.size() >= 2 && args.get(0).equals(PING_OPCODE)) {
|
||||
|
||||
try {
|
||||
sendInstruction(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
PING_OPCODE, args.get(1)
|
||||
));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("Unable to send \"ping\" response for WebSocket tunnel.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// Pass through all non-internal instructions untouched
|
||||
return instruction;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
try {
|
||||
// Write received message
|
||||
|
@@ -35,6 +35,8 @@ import org.apache.catalina.websocket.WebSocketServlet;
|
||||
import org.apache.catalina.websocket.WsOutbound;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.protocol.FilteredGuacamoleWriter;
|
||||
import org.apache.guacamole.protocol.GuacamoleFilter;
|
||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
@@ -52,6 +54,15 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* The opcode of the instruction used to indicate a connection stability
|
||||
* test ping request or response. Note that this instruction is
|
||||
* encapsulated within an internal tunnel instruction (with the opcode
|
||||
* being the empty string), thus this will actually be the value of the
|
||||
* first element of the received instruction.
|
||||
*/
|
||||
private static final String PING_OPCODE = "ping";
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
@@ -130,6 +141,58 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
*/
|
||||
private GuacamoleTunnel tunnel = null;
|
||||
|
||||
/**
|
||||
* The outbound half of the WebSocket connection. This value will
|
||||
* always be non-null if tunnel is non-null.
|
||||
*/
|
||||
private WsOutbound outbound = null;
|
||||
|
||||
/**
|
||||
* Sends a Guacamole instruction along the outbound WebSocket
|
||||
* connection to the connected Guacamole client. If an instruction
|
||||
* is already in the process of being sent by another thread, this
|
||||
* function will block until in-progress instructions are complete.
|
||||
*
|
||||
* @param instruction
|
||||
* The instruction to send.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs preventing the given instruction from
|
||||
* being sent.
|
||||
*/
|
||||
private void sendInstruction(CharSequence instruction)
|
||||
throws IOException {
|
||||
|
||||
// NOTE: Synchronization on the non-final remote field here is
|
||||
// intentional. The outbound websocket connection is only
|
||||
// sensitive to simultaneous attempts to send messages with
|
||||
// respect to itself. If the connection changes, then
|
||||
// synchronization need only be performed in context of the new
|
||||
// connection
|
||||
synchronized (outbound) {
|
||||
outbound.writeTextMessage(CharBuffer.wrap(instruction));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Guacamole instruction along the outbound WebSocket
|
||||
* connection to the connected Guacamole client. If an instruction
|
||||
* is already in the process of being sent by another thread, this
|
||||
* function will block until in-progress instructions are complete.
|
||||
*
|
||||
* @param instruction
|
||||
* The instruction to send.
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs preventing the given instruction from being
|
||||
* sent.
|
||||
*/
|
||||
private void sendInstruction(GuacamoleInstruction instruction)
|
||||
throws IOException {
|
||||
sendInstruction(instruction.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTextData(Reader reader) throws IOException {
|
||||
|
||||
@@ -137,7 +200,43 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
// Filter received instructions, handling tunnel-internal
|
||||
// instructions without passing through to guacd
|
||||
GuacamoleWriter writer = new FilteredGuacamoleWriter(tunnel.acquireWriter(), new GuacamoleFilter() {
|
||||
|
||||
@Override
|
||||
public GuacamoleInstruction filter(GuacamoleInstruction instruction)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Filter out all tunnel-internal instructions
|
||||
if (instruction.getOpcode().equals(GuacamoleTunnel.INTERNAL_DATA_OPCODE)) {
|
||||
|
||||
// Respond to ping requests
|
||||
List<String> args = instruction.getArgs();
|
||||
if (args.size() >= 2 && args.get(0).equals(PING_OPCODE)) {
|
||||
|
||||
try {
|
||||
sendInstruction(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
PING_OPCODE, args.get(1)
|
||||
));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("Unable to send \"ping\" response for WebSocket tunnel.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// Pass through all non-internal instructions untouched
|
||||
return instruction;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Write all available data
|
||||
try {
|
||||
@@ -162,6 +261,9 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
@Override
|
||||
public void onOpen(final WsOutbound outbound) {
|
||||
|
||||
// Store outbound connection for future use via sendInstruction()
|
||||
this.outbound = outbound;
|
||||
|
||||
try {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
@@ -191,10 +293,10 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
try {
|
||||
|
||||
// Send tunnel UUID
|
||||
outbound.writeTextMessage(CharBuffer.wrap(new GuacamoleInstruction(
|
||||
sendInstruction(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
tunnel.getUUID().toString()
|
||||
).toString()));
|
||||
));
|
||||
|
||||
try {
|
||||
|
||||
@@ -206,7 +308,7 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
outbound.writeTextMessage(CharBuffer.wrap(buffer));
|
||||
sendInstruction(CharBuffer.wrap(buffer));
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user