GUACAMOLE-44: Allow intercepted streams to report errors.

This commit is contained in:
Michael Jumper
2016-06-05 16:00:03 -07:00
parent 2bb5260144
commit e79d019fe6
5 changed files with 136 additions and 12 deletions

View File

@@ -136,7 +136,12 @@ public class TunnelRESTService {
@Override @Override
public void write(OutputStream output) throws IOException { public void write(OutputStream output) throws IOException {
tunnel.interceptStream(streamIndex, output); try {
tunnel.interceptStream(streamIndex, output);
}
catch (GuacamoleException e) {
throw new IOException(e);
}
} }
}; };
@@ -167,8 +172,9 @@ public class TunnelRESTService {
* stream. * stream.
* *
* @throws GuacamoleException * @throws GuacamoleException
* If the session associated with the given auth token cannot be * If the session associated with the given auth
* retrieved, or if no such tunnel exists. * token cannot be retrieved, if no such tunnel exists, or if the
* intercepted stream itself closes with an error.
*/ */
@POST @POST
@Consumes(MediaType.WILDCARD) @Consumes(MediaType.WILDCARD)

View File

@@ -27,6 +27,7 @@ import javax.xml.bind.DatatypeConverter;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.protocol.GuacamoleInstruction; import org.apache.guacamole.protocol.GuacamoleInstruction;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -164,8 +165,24 @@ public class InputStreamInterceptingFilter
// Terminate stream if an error is encountered // Terminate stream if an error is encountered
if (!status.equals("0")) { if (!status.equals("0")) {
// Parse status code as integer
int code;
try {
code = Integer.parseInt(status);
}
// Assume internal error if parsing fails
catch (NumberFormatException e) {
logger.debug("Translating invalid status code \"{}\" to SERVER_ERROR.", status);
code = GuacamoleStatus.SERVER_ERROR.getGuacamoleStatusCode();
}
// Flag error and close stream
stream.setStreamError(code, args.get(1));
closeInterceptedStream(stream); closeInterceptedStream(stream);
return; return;
} }
// Send next blob // Send next blob

View File

@@ -20,6 +20,8 @@
package org.apache.guacamole.tunnel; package org.apache.guacamole.tunnel;
import java.io.Closeable; import java.io.Closeable;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.protocol.GuacamoleStatus;
/** /**
* A simple pairing of the index of an intercepted Guacamole stream with the * A simple pairing of the index of an intercepted Guacamole stream with the
@@ -45,6 +47,13 @@ public class InterceptedStream<T extends Closeable> {
*/ */
private final T stream; private final T stream;
/**
* The exception which prevented the stream from completing successfully,
* if any. If the stream completed successfully, or has not encountered any
* exception yet, this will be null.
*/
private GuacamoleException streamError = null;
/** /**
* Creates a new InterceptedStream which associated the given Guacamole * Creates a new InterceptedStream which associated the given Guacamole
* stream index with the given stream object. * stream index with the given stream object.
@@ -83,4 +92,70 @@ public class InterceptedStream<T extends Closeable> {
return stream; return stream;
} }
/**
* Reports that this InterceptedStream did not complete successfully due to
* the given GuacamoleException, which could not be thrown at the time due
* to asynchronous handling of the stream contents.
*
* @param streamError
* The exception which prevented the stream from completing
* successfully.
*/
public void setStreamError(GuacamoleException streamError) {
this.streamError = streamError;
}
/**
* Reports that this InterceptedStream did not complete successfully due to
* an error described by the given status code and human-readable message.
* The error reported by this call can later be retrieved as a
* GuacamoleStreamException by calling getStreamError().
*
* @param code
* The Guacamole protocol status code which described the error that
* occurred. This should be taken directly from the "ack" instruction
* that reported the error witin the intercepted stream.
*
* @param message
* A human-readable message describing the error that occurred. This
* should be taken directly from the "ack" instruction that reported
* the error witin the intercepted stream.
*/
public void setStreamError(int code, String message) {
// Map status code to GuacamoleStatus, assuming SERVER_ERROR by default
GuacamoleStatus status = GuacamoleStatus.fromGuacamoleStatusCode(code);
if (status == null)
status = GuacamoleStatus.SERVER_ERROR;
// Associate stream with corresponding GuacamoleStreamException
setStreamError(new GuacamoleStreamException(status, message));
}
/**
* Returns whether an error has prevented this InterceptedStream from
* completing successfully. This will return false if the stream has
* completed successfully OR if the stream simply has not yet completed.
*
* @return
* true if an error has prevented this InterceptedStream from
* completing successfully, false otherwise.
*/
public boolean hasStreamError() {
return streamError != null;
}
/**
* Returns a GuacamoleException which describes why this InterceptedStream
* did not complete successfully.
*
* @return
* An exception describing the error that prevented the stream from
* completing successfully, or null if no such error has occurred.
*/
public GuacamoleException getStreamError() {
return streamError;
}
} }

View File

@@ -177,8 +177,12 @@ public abstract class StreamInterceptingFilter<T extends Closeable>
* @param stream * @param stream
* The stream object which will produce or consume all data for the * The stream object which will produce or consume all data for the
* stream having the given index. * stream having the given index.
*
* @throws GuacamoleException
* If an error occurs while intercepting the stream, or if the stream
* itself reports an error.
*/ */
public void interceptStream(int index, T stream) { public void interceptStream(int index, T stream) throws GuacamoleException {
InterceptedStream<T> interceptedStream; InterceptedStream<T> interceptedStream;
String indexString = Integer.toString(index); String indexString = Integer.toString(index);
@@ -204,6 +208,10 @@ public abstract class StreamInterceptingFilter<T extends Closeable>
// Wait for stream to close // Wait for stream to close
streams.waitFor(interceptedStream); streams.waitFor(interceptedStream);
// Throw any asynchronously-provided exception
if (interceptedStream.hasStreamError())
throw interceptedStream.getStreamError();
} }
} }

View File

@@ -85,18 +85,27 @@ public class StreamInterceptingTunnel extends DelegatingGuacamoleTunnel {
* *
* @param stream * @param stream
* The OutputStream to write all intercepted data to. * The OutputStream to write all intercepted data to.
*
* @throws GuacamoleException
* If an error occurs while intercepting the stream, or if the stream
* itself reports an error.
*/ */
public void interceptStream(int index, OutputStream stream) { public void interceptStream(int index, OutputStream stream)
throws GuacamoleException {
// Log beginning of intercepted stream // Log beginning of intercepted stream
logger.debug("Intercepting output stream #{} of tunnel \"{}\".", logger.debug("Intercepting output stream #{} of tunnel \"{}\".",
index, getUUID()); index, getUUID());
outputStreamFilter.interceptStream(index, new BufferedOutputStream(stream)); try {
outputStreamFilter.interceptStream(index, new BufferedOutputStream(stream));
}
// Log end of intercepted stream // Log end of intercepted stream
logger.debug("Intercepted output stream #{} of tunnel \"{}\" ended.", finally {
index, getUUID()); logger.debug("Intercepted output stream #{} of tunnel \"{}\" ended.",
index, getUUID());
}
} }
@@ -113,18 +122,27 @@ public class StreamInterceptingTunnel extends DelegatingGuacamoleTunnel {
* *
* @param stream * @param stream
* The InputStream to read all blobs data from. * The InputStream to read all blobs data from.
*
* @throws GuacamoleException
* If an error occurs while intercepting the stream, or if the stream
* itself reports an error.
*/ */
public void interceptStream(int index, InputStream stream) { public void interceptStream(int index, InputStream stream)
throws GuacamoleException {
// Log beginning of intercepted stream // Log beginning of intercepted stream
logger.debug("Intercepting input stream #{} of tunnel \"{}\".", logger.debug("Intercepting input stream #{} of tunnel \"{}\".",
index, getUUID()); index, getUUID());
inputStreamFilter.interceptStream(index, new BufferedInputStream(stream)); try {
inputStreamFilter.interceptStream(index, new BufferedInputStream(stream));
}
// Log end of intercepted stream // Log end of intercepted stream
logger.debug("Intercepted input stream #{} of tunnel \"{}\" ended.", finally {
index, getUUID()); logger.debug("Intercepted input stream #{} of tunnel \"{}\" ended.",
index, getUUID());
}
} }