mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 00:53:21 +00:00 
			
		
		
		
	GUAC-878: Generalize loading of WebSocket classes in WebSocketSupportLoader. Note that Jetty support is specific to Jetty 8. Update documentation.
This commit is contained in:
		| @@ -30,8 +30,6 @@ import javax.servlet.ServletContextEvent; | ||||
| import javax.servlet.ServletContextListener; | ||||
| import org.glyptodon.guacamole.GuacamoleException; | ||||
| import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; | ||||
| import org.glyptodon.guacamole.properties.BooleanGuacamoleProperty; | ||||
| import org.glyptodon.guacamole.properties.GuacamoleProperties; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| @@ -44,17 +42,6 @@ import org.slf4j.LoggerFactory; | ||||
|  * not be available or needed if WebSocket is not desired, the 3.0 API is | ||||
|  * detected and invoked dynamically via reflection. | ||||
|  *  | ||||
|  * Tests have shown that while WebSocket is negligibly more responsive than | ||||
|  * Guacamole's native HTTP tunnel, downstream performance is not yet a match. | ||||
|  * This may be because browser WebSocket implementations are not optimized for | ||||
|  * throughput, or it may be because servlet container WebSocket implementations | ||||
|  * are in their infancy, or it may be that OUR WebSocket-backed tunnel | ||||
|  * implementations are not efficient. Because of this, WebSocket support is | ||||
|  * disabled by default. To enable it, add the following property to | ||||
|  * your guacamole.properties: | ||||
|  *  | ||||
|  *     enable-websocket: true | ||||
|  * | ||||
|  * @author Michael Jumper | ||||
|  */ | ||||
| public class WebSocketSupportLoader implements ServletContextListener { | ||||
| @@ -65,16 +52,12 @@ public class WebSocketSupportLoader implements ServletContextListener { | ||||
|     private final Logger logger = LoggerFactory.getLogger(WebSocketSupportLoader.class); | ||||
|  | ||||
|     /** | ||||
|      * Classname of the Jetty-specific WebSocket tunnel implementation. | ||||
|      * Classnames of all legacy (non-JSR) WebSocket tunnel implementations. | ||||
|      */ | ||||
|     private static final String JETTY_WEBSOCKET = | ||||
|         "org.glyptodon.guacamole.net.basic.websocket.jetty.BasicGuacamoleWebSocketTunnelServlet"; | ||||
|  | ||||
|     /** | ||||
|      * Classname of the Tomcat-specific WebSocket tunnel implementation. | ||||
|      */ | ||||
|     private static final String TOMCAT_WEBSOCKET = | ||||
|         "org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet"; | ||||
|     private static final String[] WEBSOCKET_CLASSES = { | ||||
|         "org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet", | ||||
|         "org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet" | ||||
|     }; | ||||
|  | ||||
|     private boolean loadWebSocketTunnel(ServletContext context, String classname) { | ||||
|  | ||||
| @@ -101,7 +84,6 @@ public class WebSocketSupportLoader implements ServletContextListener { | ||||
|  | ||||
|                 // If we succesfully load and register the WebSocket tunnel servlet, | ||||
|                 // WebSocket is supported. | ||||
|                 logger.info("WebSocket support found and loaded."); | ||||
|                 return true; | ||||
|  | ||||
|             } | ||||
| @@ -146,12 +128,16 @@ public class WebSocketSupportLoader implements ServletContextListener { | ||||
|     @Override | ||||
|     public void contextInitialized(ServletContextEvent sce) { | ||||
|  | ||||
|         // Try to load websocket support for Jetty | ||||
|         if (loadWebSocketTunnel(sce.getServletContext(), JETTY_WEBSOCKET)) | ||||
|             return; | ||||
|         // Try to load each WebSocket tunnel in sequence | ||||
|         for (String classname : WEBSOCKET_CLASSES) { | ||||
|             if (loadWebSocketTunnel(sce.getServletContext(), classname)) { | ||||
|                 logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Failing that, try to load websocket support for Tomcat | ||||
|         loadWebSocketTunnel(sce.getServletContext(), TOMCAT_WEBSOCKET); | ||||
|         // No legacy WebSocket support found (usually good) | ||||
|         logger.debug("Legacy WebSocket support NOT loaded."); | ||||
|  | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
|  * Copyright (C) 2013 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.net.basic.websocket.jetty8; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import org.glyptodon.guacamole.GuacamoleException; | ||||
| import org.glyptodon.guacamole.net.GuacamoleTunnel; | ||||
| import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility; | ||||
| import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest; | ||||
|  | ||||
| /** | ||||
|  * Tunnel servlet implementation which uses WebSocket as a tunnel backend, | ||||
|  * rather than HTTP, properly parsing connection IDs included in the connection | ||||
|  * request. | ||||
|  */ | ||||
| public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet { | ||||
|  | ||||
|     @Override | ||||
|     protected GuacamoleTunnel doConnect(HttpServletRequest request) | ||||
|             throws GuacamoleException { | ||||
|         return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,181 @@ | ||||
| /* | ||||
|  * Copyright (C) 2013 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.net.basic.websocket.jetty8; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import org.glyptodon.guacamole.GuacamoleException; | ||||
| import org.glyptodon.guacamole.io.GuacamoleReader; | ||||
| import org.glyptodon.guacamole.io.GuacamoleWriter; | ||||
| import org.glyptodon.guacamole.net.GuacamoleTunnel; | ||||
| import org.eclipse.jetty.websocket.WebSocket; | ||||
| import org.eclipse.jetty.websocket.WebSocket.Connection; | ||||
| import org.eclipse.jetty.websocket.WebSocketServlet; | ||||
| import org.glyptodon.guacamole.GuacamoleClientException; | ||||
| import org.glyptodon.guacamole.protocol.GuacamoleStatus; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet. | ||||
|  * | ||||
|  * @author Michael Jumper | ||||
|  */ | ||||
| public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class); | ||||
|      | ||||
|     /** | ||||
|      * The default, minimum buffer size for instructions. | ||||
|      */ | ||||
|     private static final int BUFFER_SIZE = 8192; | ||||
|  | ||||
|     /** | ||||
|      * Sends the given status on the given WebSocket connection and closes the | ||||
|      * connection. | ||||
|      * | ||||
|      * @param connection The WebSocket connection to close. | ||||
|      * @param guac_status The status to send. | ||||
|      */ | ||||
|     public static void closeConnection(Connection connection, | ||||
|             GuacamoleStatus guac_status) { | ||||
|  | ||||
|         connection.close(guac_status.getWebSocketCode(), | ||||
|                 Integer.toString(guac_status.getGuacamoleStatusCode())); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { | ||||
|  | ||||
|         // Get tunnel | ||||
|         final GuacamoleTunnel tunnel; | ||||
|  | ||||
|         try { | ||||
|             tunnel = doConnect(request); | ||||
|         } | ||||
|         catch (GuacamoleException e) { | ||||
|             logger.error("Error connecting WebSocket tunnel.", e); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // Return new WebSocket which communicates through tunnel | ||||
|         return new WebSocket.OnTextMessage() { | ||||
|  | ||||
|             @Override | ||||
|             public void onMessage(String string) { | ||||
|                 GuacamoleWriter writer = tunnel.acquireWriter(); | ||||
|  | ||||
|                 // Write message received | ||||
|                 try { | ||||
|                     writer.write(string.toCharArray()); | ||||
|                 } | ||||
|                 catch (GuacamoleException e) { | ||||
|                     logger.debug("Tunnel write failed.", e); | ||||
|                 } | ||||
|  | ||||
|                 tunnel.releaseWriter(); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onOpen(final Connection connection) { | ||||
|  | ||||
|                 Thread readThread = new Thread() { | ||||
|  | ||||
|                     @Override | ||||
|                     public void run() { | ||||
|  | ||||
|                         StringBuilder buffer = new StringBuilder(BUFFER_SIZE); | ||||
|                         GuacamoleReader reader = tunnel.acquireReader(); | ||||
|                         char[] readMessage; | ||||
|  | ||||
|                         try { | ||||
|  | ||||
|                             try { | ||||
|  | ||||
|                                 // Attempt to read | ||||
|                                 while ((readMessage = reader.read()) != null) { | ||||
|  | ||||
|                                     // Buffer message | ||||
|                                     buffer.append(readMessage); | ||||
|  | ||||
|                                     // Flush if we expect to wait or buffer is getting full | ||||
|                                     if (!reader.available() || buffer.length() >= BUFFER_SIZE) { | ||||
|                                         connection.sendMessage(buffer.toString()); | ||||
|                                         buffer.setLength(0); | ||||
|                                     } | ||||
|  | ||||
|                                 } | ||||
|  | ||||
|                                 // No more data | ||||
|                                 closeConnection(connection, GuacamoleStatus.SUCCESS); | ||||
|                                  | ||||
|                             } | ||||
|  | ||||
|                             // Catch any thrown guacamole exception and attempt | ||||
|                             // to pass within the WebSocket connection, logging | ||||
|                             // each error appropriately. | ||||
|                             catch (GuacamoleClientException e) { | ||||
|                                 logger.warn("Client request rejected: {}", e.getMessage()); | ||||
|                                 closeConnection(connection, e.getStatus()); | ||||
|                             } | ||||
|                             catch (GuacamoleException e) { | ||||
|                                 logger.error("Internal server error.", e); | ||||
|                                 closeConnection(connection, e.getStatus()); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                         catch (IOException e) { | ||||
|                             logger.debug("Tunnel read failed due to I/O error.", e); | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|  | ||||
|                 }; | ||||
|  | ||||
|                 readThread.start(); | ||||
|  | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onClose(int i, String string) { | ||||
|                 try { | ||||
|                     tunnel.close(); | ||||
|                 } | ||||
|                 catch (GuacamoleException e) { | ||||
|                     logger.debug("Unable to close WebSocket tunnel.", e); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     protected abstract GuacamoleTunnel doConnect(HttpServletRequest request) | ||||
|             throws GuacamoleException; | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,27 @@ | ||||
| /* | ||||
|  * Copyright (C) 2013 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Jetty 8 WebSocket tunnel implementation. The classes here require Jetty 8. | ||||
|  */ | ||||
| package org.glyptodon.guacamole.net.basic.websocket.jetty8; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user