Merge pull request #304 from glyptodon/stuck-http-tunnels

GUAC-1427: Ensure HTTP tunnels cannot become stuck.
This commit is contained in:
James Muehlner
2015-12-14 20:09:08 -08:00
6 changed files with 491 additions and 153 deletions

View File

@@ -23,7 +23,6 @@
package org.glyptodon.guacamole.net.example;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleSocket;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
@@ -32,7 +31,6 @@ import org.glyptodon.guacamole.net.SimpleGuacamoleTunnel;
import org.glyptodon.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
import org.glyptodon.guacamole.servlet.GuacamoleHTTPTunnelServlet;
import org.glyptodon.guacamole.servlet.GuacamoleSession;
/**
* Simple tunnel example with hard-coded configuration parameters.
@@ -44,8 +42,6 @@ public class DummyGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
HttpSession httpSession = request.getSession(true);
// guacd connection information
String hostname = "localhost";
int port = 4822;
@@ -65,11 +61,6 @@ public class DummyGuacamoleTunnelServlet extends GuacamoleHTTPTunnelServlet {
// Create tunnel from now-configured socket
GuacamoleTunnel tunnel = new SimpleGuacamoleTunnel(socket);
// Attach tunnel
GuacamoleSession session = new GuacamoleSession(httpSession);
session.attachTunnel(tunnel);
return tunnel;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013 Glyptodon LLC
* Copyright (C) 2015 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
@@ -21,7 +21,6 @@
*/
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.properties.GuacamoleProperties;
import net.sourceforge.guacamole.net.GuacamoleSocket;
@@ -29,7 +28,6 @@ import net.sourceforge.guacamole.net.GuacamoleTunnel;
import net.sourceforge.guacamole.net.InetGuacamoleSocket;
import net.sourceforge.guacamole.protocol.GuacamoleConfiguration;
import net.sourceforge.guacamole.protocol.ConfiguredGuacamoleSocket;
import net.sourceforge.guacamole.servlet.GuacamoleSession;
import net.sourceforge.guacamole.servlet.GuacamoleHTTPTunnelServlet;
public class ExampleTunnelServlet extends GuacamoleHTTPTunnelServlet {
@@ -38,8 +36,6 @@ public class ExampleTunnelServlet extends GuacamoleHTTPTunnelServlet {
protected GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException {
HttpSession httpSession = request.getSession(true);
String hostname = GuacamoleProperties.getProperty(
GuacamoleProperties.GUACD_HOSTNAME);
@@ -57,12 +53,8 @@ public class ExampleTunnelServlet extends GuacamoleHTTPTunnelServlet {
config
);
// Create and return tunnel
GuacamoleTunnel tunnel = new GuacamoleTunnel(socket);
// Attach tunnel
GuacamoleSession session = new GuacamoleSession(httpSession);
session.attachTunnel(tunnel);
return tunnel;
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2015 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.servlet;
import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
/**
* Tracks the last time a particular GuacamoleTunnel was accessed. This
* information is not necessary for tunnels associated with WebSocket
* connections, as each WebSocket connection has its own read thread which
* continuously checks the state of the tunnel and which will automatically
* timeout when the underlying socket times out, but the HTTP tunnel has no
* such thread. Because the HTTP tunnel requires the stream to be split across
* multiple requests, tracking of activity on the tunnel must be performed
* independently of the HTTP requests.
*
* @author Michael Jumper
*/
class GuacamoleHTTPTunnel extends DelegatingGuacamoleTunnel {
/**
* The last time this tunnel was accessed.
*/
private long lastAccessedTime;
/**
* Creates a new GuacamoleHTTPTunnel which wraps the given tunnel.
* Absolutely all function calls on this new GuacamoleHTTPTunnel will be
* delegated to the underlying GuacamoleTunnel.
*
* @param wrappedTunnel
* The GuacamoleTunnel to wrap within this GuacamoleHTTPTunnel.
*/
public GuacamoleHTTPTunnel(GuacamoleTunnel wrappedTunnel) {
super(wrappedTunnel);
}
/**
* Updates this tunnel, marking it as recently accessed.
*/
public void access() {
lastAccessedTime = System.currentTimeMillis();
}
/**
* Returns the time this tunnel was last accessed, as the number of
* milliseconds since midnight January 1, 1970 GMT. Tunnel access must
* be explicitly marked through calls to the access() function.
*
* @return
* The time this tunnel was last accessed.
*/
public long getLastAccessedTime() {
return lastAccessedTime;
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2015 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.servlet;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Map-style object which tracks in-use HTTP tunnels, automatically removing
* and closing tunnels which have not been used recently. This class is
* intended for use only within the GuacamoleHTTPTunnelServlet implementation,
* and has no real utility outside that implementation.
*
* @author Michael Jumper
*/
class GuacamoleHTTPTunnelMap {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelMap.class);
/**
* The number of seconds to wait between tunnel accesses before timing out
* Note that this will be enforced only within a factor of 2. If a tunnel
* is unused, it will take between TUNNEL_TIMEOUT and TUNNEL_TIMEOUT*2
* seconds before that tunnel is closed and removed.
*/
private static final int TUNNEL_TIMEOUT = 15;
/**
* Executor service which runs the periodic tunnel timeout task.
*/
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
/**
* Map of all tunnels that are using HTTP, indexed by tunnel UUID.
*/
private final Map<String, GuacamoleHTTPTunnel> tunnelMap =
Collections.synchronizedMap(new LinkedHashMap<String, GuacamoleHTTPTunnel>(16, 0.75f, true));
/**
* Creates a new GuacamoleHTTPTunnelMap which automatically closes and
* removes HTTP tunnels which are no longer in use.
*/
public GuacamoleHTTPTunnelMap() {
// Check for unused tunnels every few seconds
executor.scheduleAtFixedRate(
new TunnelTimeoutTask(TUNNEL_TIMEOUT * 1000l),
TUNNEL_TIMEOUT, TUNNEL_TIMEOUT, TimeUnit.SECONDS);
}
/**
* Task which iterates through all registered tunnels, removing and those
* tunnels which have not been accessed for a given number of milliseconds.
*/
private class TunnelTimeoutTask implements Runnable {
/**
* The maximum amount of time to allow between accesses to any one
* HTTP tunnel, in milliseconds.
*/
private final long tunnelTimeout;
/**
* Creates a new task which automatically closes and removes tunnels
* which have not been accessed for at least the given number of
* milliseconds.
*
* @param tunnelTimeout
* The maximum amount of time to allow between separate tunnel
* read/write requests, in milliseconds.
*/
public TunnelTimeoutTask(long tunnelTimeout) {
this.tunnelTimeout = tunnelTimeout;
}
@Override
public void run() {
// Get current time
long now = System.currentTimeMillis();
// For each tunnel, close and remove any tunnels which have expired
Iterator<Map.Entry<String, GuacamoleHTTPTunnel>> entries = tunnelMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, GuacamoleHTTPTunnel> entry = entries.next();
GuacamoleHTTPTunnel tunnel = entry.getValue();
// Get elapsed time since last access
long age = now - tunnel.getLastAccessedTime();
// If tunnel is too old, close and remove it
if (age >= tunnelTimeout) {
logger.debug("HTTP tunnel \"{}\" has timed out.", entry.getKey());
entries.remove();
try {
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close expired HTTP tunnel.", e);
}
}
// Otherwise, this tunnel has been recently used, as have all
// other tunnels following this one within tunnelMap
else
break;
}
}
}
/**
* Returns the GuacamoleTunnel having the given UUID, wrapped within a
* GuacamoleHTTPTunnel. If the no tunnel having the given UUID is
* available, null is returned.
*
* @param uuid
* The UUID of the tunnel to retrieve.
*
* @return
* The GuacamoleTunnel having the given UUID, wrapped within a
* GuacamoleHTTPTunnel, if such a tunnel exists, or null if there is no
* such tunnel.
*/
public GuacamoleHTTPTunnel get(String uuid) {
// Update the last access time
GuacamoleHTTPTunnel tunnel = tunnelMap.get(uuid);
if (tunnel != null)
tunnel.access();
// Return tunnel, if any
return tunnel;
}
/**
* Registers that a new connection has been established using HTTP via the
* given GuacamoleTunnel.
*
* @param uuid
* The UUID of the tunnel being added (registered).
*
* @param tunnel
* The GuacamoleTunnel being registered, its associated connection
* having just been established via HTTP.
*/
public void put(String uuid, GuacamoleTunnel tunnel) {
tunnelMap.put(uuid, new GuacamoleHTTPTunnel(tunnel));
}
/**
* Removes the GuacamoleTunnel having the given UUID, if such a tunnel
* exists. The original tunnel is returned wrapped within a
* GuacamoleHTTPTunnel.
*
* @param uuid
* The UUID of the tunnel to remove (deregister).
*
* @return
* The GuacamoleTunnel having the given UUID, if such a tunnel exists,
* wrapped within a GuacamoleHTTPTunnel, or null if no such tunnel
* exists and no removal was performed.
*/
public GuacamoleHTTPTunnel remove(String uuid) {
return tunnelMap.remove(uuid);
}
/**
* Shuts down this tunnel map, disallowing future tunnels from being
* registered and reclaiming any resources.
*/
public void shutdown() {
executor.shutdownNow();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013 Glyptodon LLC
* Copyright (C) 2015 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
@@ -22,7 +22,6 @@
package org.glyptodon.guacamole.servlet;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -33,7 +32,6 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
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;
@@ -57,7 +55,12 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
/**
* Logger for this class.
*/
private Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelServlet.class);
private final Logger logger = LoggerFactory.getLogger(GuacamoleHTTPTunnelServlet.class);
/**
* Map of absolutely all active tunnels using HTTP, indexed by tunnel UUID.
*/
private final GuacamoleHTTPTunnelMap tunnels = new GuacamoleHTTPTunnelMap();
/**
* The prefix of the query string which denotes a tunnel read operation.
@@ -84,6 +87,56 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
*/
private static final int UUID_LENGTH = 36;
/**
* Registers the given tunnel such that future read/write requests to that
* tunnel will be properly directed.
*
* @param tunnel
* The tunnel to register.
*/
protected void registerTunnel(GuacamoleTunnel tunnel) {
tunnels.put(tunnel.getUUID().toString(), tunnel);
logger.debug("Registered tunnel \"{}\".", tunnel.getUUID());
}
/**
* Deregisters the given tunnel such that future read/write requests to
* that tunnel will be rejected.
*
* @param tunnel
* The tunnel to deregister.
*/
protected void deregisterTunnel(GuacamoleTunnel tunnel) {
tunnels.remove(tunnel.getUUID().toString());
logger.debug("Deregistered tunnel \"{}\".", tunnel.getUUID());
}
/**
* Returns the tunnel with the given UUID, if it has been registered with
* registerTunnel() and not yet deregistered with deregisterTunnel().
*
* @param tunnelUUID
* The UUID of registered tunnel.
*
* @return
* The tunnel corresponding to the given UUID.
*
* @throws GuacamoleException
* If the requested tunnel does not exist because it has not yet been
* registered or it has been deregistered.
*/
protected GuacamoleTunnel getTunnel(String tunnelUUID)
throws GuacamoleException {
// Pull tunnel from map
GuacamoleTunnel tunnel = tunnels.get(tunnelUUID);
if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No such tunnel.");
return tunnel;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
handleTunnelRequest(request, response);
@@ -98,24 +151,29 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
* Sends an error on the given HTTP response using the information within
* the given GuacamoleStatus.
*
* @param response The HTTP response to use to send the error.
* @param guac_status The status to send
* @param message A human-readable message that can be presented to the
* user.
* @throws ServletException If an error prevents sending of the error
* code.
* @param response
* The HTTP response to use to send the error.
*
* @param guacStatus
* The status to send
*
* @param message
* A human-readable message that can be presented to the user.
*
* @throws ServletException
* If an error prevents sending of the error code.
*/
public static void sendError(HttpServletResponse response,
GuacamoleStatus guac_status, String message)
protected void sendError(HttpServletResponse response,
GuacamoleStatus guacStatus, String message)
throws ServletException {
try {
// If response not committed, send error code and message
if (!response.isCommitted()) {
response.addHeader("Guacamole-Status-Code", Integer.toString(guac_status.getGuacamoleStatusCode()));
response.addHeader("Guacamole-Status-Code", Integer.toString(guacStatus.getGuacamoleStatusCode()));
response.addHeader("Guacamole-Error-Message", message);
response.sendError(guac_status.getHttpStatusCode());
response.sendError(guacStatus.getHttpStatusCode());
}
}
@@ -133,13 +191,19 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
* Dispatches every HTTP GET and POST request to the appropriate handler
* function based on the query string.
*
* @param request The HttpServletRequest associated with the GET or POST
* request received.
* @param response The HttpServletResponse associated with the GET or POST
* request received.
* @throws ServletException If an error occurs while servicing the request.
* @param request
* The HttpServletRequest associated with the GET or POST request
* received.
*
* @param response
* The HttpServletResponse associated with the GET or POST request
* received.
*
* @throws ServletException
* If an error occurs while servicing the request.
*/
protected void handleTunnelRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
protected void handleTunnelRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
try {
@@ -154,12 +218,8 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
GuacamoleTunnel tunnel = doConnect(request);
if (tunnel != null) {
// Get session
HttpSession httpSession = request.getSession(true);
GuacamoleSession session = new GuacamoleSession(httpSession);
// Attach tunnel to session
session.attachTunnel(tunnel);
// Register newly-created tunnel
registerTunnel(tunnel);
try {
// Ensure buggy browsers do not cache response
@@ -215,48 +275,53 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
/**
* Called whenever the JavaScript Guacamole client makes a connection
* request. It it up to the implementor of this function to define what
* conditions must be met for a tunnel to be configured and returned as a
* result of this connection request (whether some sort of credentials must
* be specified, for example).
* request via HTTP. It it up to the implementor of this function to define
* what conditions must be met for a tunnel to be configured and returned
* as a result of this connection request (whether some sort of credentials
* must be specified, for example).
*
* @param request The HttpServletRequest associated with the connection
* request received. Any parameters specified along with
* the connection request can be read from this object.
* @return A newly constructed GuacamoleTunnel if successful,
* null otherwise.
* @throws GuacamoleException If an error occurs while constructing the
* GuacamoleTunnel, or if the conditions
* required for connection are not met.
* @param request
* The HttpServletRequest associated with the connection request
* received. Any parameters specified along with the connection request
* can be read from this object.
*
* @return
* A newly constructed GuacamoleTunnel if successful, null otherwise.
*
* @throws GuacamoleException
* If an error occurs while constructing the GuacamoleTunnel, or if the
* conditions required for connection are not met.
*/
protected abstract GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException;
protected abstract GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException;
/**
* Called whenever the JavaScript Guacamole client makes a read request.
* This function should in general not be overridden, as it already
* contains a proper implementation of the read operation.
*
* @param request The HttpServletRequest associated with the read request
* received.
* @param response The HttpServletResponse associated with the write request
* received. Any data to be sent to the client in response
* to the write request should be written to the response
* body of this HttpServletResponse.
* @param tunnelUUID The UUID of the tunnel to read from, as specified in
* the write request. This tunnel must be attached to
* the Guacamole session.
* @throws GuacamoleException If an error occurs while handling the read
* request.
* @param request
* The HttpServletRequest associated with the read request received.
*
* @param response
* The HttpServletResponse associated with the write request received.
* Any data to be sent to the client in response to the write request
* should be written to the response body of this HttpServletResponse.
*
* @param tunnelUUID
* The UUID of the tunnel to read from, as specified in the write
* request. This tunnel must have been created by a previous call to
* doConnect().
*
* @throws GuacamoleException
* If an error occurs while handling the read request.
*/
protected void doRead(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
HttpSession httpSession = request.getSession(false);
GuacamoleSession session = new GuacamoleSession(httpSession);
protected void doRead(HttpServletRequest request,
HttpServletResponse response, String tunnelUUID)
throws GuacamoleException {
// Get tunnel, ensure tunnel exists
GuacamoleTunnel tunnel = session.getTunnel(tunnelUUID);
if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No such tunnel.");
GuacamoleTunnel tunnel = getTunnel(tunnelUUID);
// Ensure tunnel is open
if (!tunnel.isOpen())
@@ -280,8 +345,8 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// Stream data to response, ensuring output stream is closed
try {
// Detach tunnel and throw error if EOF (and we haven't sent any
// data yet.
// Deregister tunnel and throw error if we reach EOF without
// having ever sent any data
char[] message = reader.read();
if (message == null)
throw new GuacamoleConnectionClosedException("Tunnel reached end of stream.");
@@ -306,7 +371,7 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// Close tunnel immediately upon EOF
if (message == null) {
session.detachTunnel(tunnel);
deregisterTunnel(tunnel);
tunnel.close();
}
@@ -319,8 +384,8 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// Send end-of-stream marker and close tunnel if connection is closed
catch (GuacamoleConnectionClosedException e) {
// Detach and close
session.detachTunnel(tunnel);
// Deregister and close
deregisterTunnel(tunnel);
tunnel.close();
// End-of-instructions marker
@@ -332,8 +397,8 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
catch (GuacamoleException e) {
// Detach and close
session.detachTunnel(tunnel);
// Deregister and close
deregisterTunnel(tunnel);
tunnel.close();
throw e;
@@ -350,8 +415,8 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
// Log typically frequent I/O error if desired
logger.debug("Error writing to servlet output stream", e);
// Detach and close
session.detachTunnel(tunnel);
// Deregister and close
deregisterTunnel(tunnel);
tunnel.close();
}
@@ -366,25 +431,27 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
* This function should in general not be overridden, as it already
* contains a proper implementation of the write operation.
*
* @param request The HttpServletRequest associated with the write request
* received. Any data to be written will be specified within
* the body of this request.
* @param response The HttpServletResponse associated with the write request
* received.
* @param tunnelUUID The UUID of the tunnel to write to, as specified in
* the write request. This tunnel must be attached to
* the Guacamole session.
* @throws GuacamoleException If an error occurs while handling the write
* request.
* @param request
* The HttpServletRequest associated with the write request received.
* Any data to be written will be specified within the body of this
* request.
*
* @param response
* The HttpServletResponse associated with the write request received.
*
* @param tunnelUUID
* The UUID of the tunnel to write to, as specified in the write
* request. This tunnel must have been created by a previous call to
* doConnect().
*
* @throws GuacamoleException
* If an error occurs while handling the write request.
*/
protected void doWrite(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
protected void doWrite(HttpServletRequest request,
HttpServletResponse response, String tunnelUUID)
throws GuacamoleException {
HttpSession httpSession = request.getSession(false);
GuacamoleSession session = new GuacamoleSession(httpSession);
GuacamoleTunnel tunnel = session.getTunnel(tunnelUUID);
if (tunnel == null)
throw new GuacamoleResourceNotFoundException("No such tunnel.");
GuacamoleTunnel tunnel = getTunnel(tunnelUUID);
// We still need to set the content type to avoid the default of
// text/html, as such a content type would cause some browsers to
@@ -430,8 +497,8 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
}
catch (IOException e) {
// Detach and close
session.detachTunnel(tunnel);
// Deregister and close
deregisterTunnel(tunnel);
tunnel.close();
throw new GuacamoleServerException("I/O Error sending data to server: " + e.getMessage(), e);
@@ -442,6 +509,11 @@ public abstract class GuacamoleHTTPTunnelServlet extends HttpServlet {
}
@Override
public void destroy() {
tunnels.shutdown();
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013 Glyptodon LLC
* Copyright (C) 2015 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
@@ -22,12 +22,7 @@
package org.glyptodon.guacamole.servlet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpSession;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,75 +32,70 @@ import org.slf4j.LoggerFactory;
*
* @author Michael Jumper
*/
@Deprecated
public class GuacamoleSession {
/**
* Logger for this class.
*/
private Logger logger = LoggerFactory.getLogger(GuacamoleSession.class);
private final Logger logger = LoggerFactory.getLogger(GuacamoleSession.class);
/**
* Map of all currently attached tunnels, indexed by tunnel UUID.
*/
private ConcurrentMap<String, GuacamoleTunnel> tunnels;
/**
* Creates a new GuacamoleSession, storing and retrieving tunnels from the
* given HttpSession. Note that the true Guacamole session is tied to the
* HttpSession provided, thus creating a new GuacamoleSession does not
* create a new Guacamole session; it merely creates a new object for
* accessing the tunnels of an existing Guacamole session represented by
* the provided HttpSession.
* Creates a new GuacamoleSession. In prior versions of Guacamole, the
* GuacamoleSession object stored the tunnels associated with a particular
* user's use of the HTTP tunnel. The HTTP tunnel now stores all of these
* tunnels itself, and thus this class is no longer necessary. Its use will
* result in a warning being logged, and its functions will have no effect.
*
* @param session The HttpSession to use as tunnel storage.
* @throws GuacamoleException If session is null.
* @param session
* The HttpSession that older versions of Guacamole would use as tunnel
* storage. This parameter is now ignored, and the GuacamoleSession
* class overall is deprecated.
*/
@SuppressWarnings("unchecked")
public GuacamoleSession(HttpSession session) throws GuacamoleException {
if (session == null)
throw new GuacamoleSecurityException("User has no session.");
synchronized (session) {
tunnels = (ConcurrentMap<String, GuacamoleTunnel>) session.getAttribute("GUAC_TUNNELS");
if (tunnels == null) {
tunnels = new ConcurrentHashMap<String, GuacamoleTunnel>();
session.setAttribute("GUAC_TUNNELS", tunnels);
}
}
public GuacamoleSession(HttpSession session) {
logger.warn("GuacamoleSession is deprecated. It is no longer "
+ "necessary and its use will have no effect.");
}
/**
* Attaches the given tunnel to this GuacamoleSession.
* @param tunnel The tunnel to attach to this GucacamoleSession.
* Attaches the given tunnel to this GuacamoleSession. The GuacamoleSession
* class is now deprecated, and this function has no effect.
*
* @param tunnel
* The tunnel to attach to this GucacamoleSession.
*/
public void attachTunnel(GuacamoleTunnel tunnel) {
tunnels.put(tunnel.getUUID().toString(), tunnel);
logger.debug("Attached tunnel {}.", tunnel.getUUID());
// Deprecated - no effect
}
/**
* Detaches the given tunnel to this GuacamoleSession.
* @param tunnel The tunnel to detach to this GucacamoleSession.
* Detaches the given tunnel to this GuacamoleSession. The GuacamoleSession
* class is now deprecated, and this function has no effect.
*
* @param tunnel
* The tunnel to detach to this GucacamoleSession.
*/
public void detachTunnel(GuacamoleTunnel tunnel) {
tunnels.remove(tunnel.getUUID().toString());
logger.debug("Detached tunnel {}.", tunnel.getUUID());
// Deprecated - no effect
}
/**
* Returns the tunnel with the given UUID attached to this GuacamoleSession,
* if any.
* if any. The GuacamoleSession class is now deprecated, and this function
* has no effect. It will ALWAYS return null.
*
* @param tunnelUUID The UUID of an attached tunnel.
* @return The tunnel corresponding to the given UUID, if attached, or null
* if no such tunnel is attached.
* @param tunnelUUID
* The UUID of an attached tunnel.
*
* @return
* The tunnel corresponding to the given UUID, if attached, or null if
* if no such tunnel is attached.
*/
public GuacamoleTunnel getTunnel(String tunnelUUID) {
return tunnels.get(tunnelUUID);
// Deprecated - no effect
return null;
}
}