mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 09:03:21 +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