mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
GUAC-1427: Implement automatic tracking and expiration of HTTP tunnels, independent of explicit read/write requests.
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user