From d1ec32d066647b236ea8d412e7ec53f58f7f89c3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 6 Nov 2014 12:41:07 -0800 Subject: [PATCH] GUAC-921: Move WebSocket support loading into independent modules. --- .../guacamole/net/basic/TunnelLoader.java | 44 ++++++ .../guacamole/net/basic/TunnelModule.java | 126 +++++------------- .../websocket/WebSocketTunnelModule.java | 112 ++++++++++++++++ .../jetty8/WebSocketTunnelModule.java | 81 +++++++++++ .../jetty9/WebSocketTunnelModule.java | 81 +++++++++++ .../tomcat/WebSocketTunnelModule.java | 81 +++++++++++ 6 files changed, 434 insertions(+), 91 deletions(-) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelLoader.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelLoader.java new file mode 100644 index 000000000..efcc8a333 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelLoader.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 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; + +import com.google.inject.Module; + +/** + * Generic means of loading a tunnel without adding explicit dependencies within + * the main ServletModule, as not all servlet containers may have the classes + * required by all tunnel implementations. + * + * @author Michael Jumper + */ +public interface TunnelLoader extends Module { + + /** + * Checks whether this type of tunnel is supported by the servlet container. + * + * @return true if this type of tunnel is supported and can be loaded + * without errors, false otherwise. + */ + public boolean isSupported(); + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java index 4466aecbf..afce169b5 100644 --- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/TunnelModule.java @@ -22,15 +22,9 @@ package org.glyptodon.guacamole.net.basic; -import com.google.inject.Provider; import com.google.inject.servlet.ServletModule; -import java.util.Arrays; -import javax.servlet.http.HttpServlet; -import javax.websocket.DeploymentException; -import javax.websocket.server.ServerContainer; -import javax.websocket.server.ServerEndpointConfig; +import java.lang.reflect.InvocationTargetException; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.net.basic.websocket.BasicGuacamoleWebSocketTunnelEndpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,66 +41,50 @@ public class TunnelModule extends ServletModule { private final Logger logger = LoggerFactory.getLogger(TunnelModule.class); /** - * Classnames of all legacy (non-JSR) WebSocket tunnel implementations. + * Classnames of all implementation-specific WebSocket tunnel modules. */ - private static final String[] WEBSOCKET_CLASSES = { - "org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet", - "org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet", - "org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet" + private static final String[] WEBSOCKET_MODULES = { + "org.glyptodon.guacamole.net.basic.websocket.WebSocketTunnelModule", + "org.glyptodon.guacamole.net.basic.websocket.jetty8.WebSocketTunnelModule", + "org.glyptodon.guacamole.net.basic.websocket.jetty9.WebSocketTunnelModule", + "org.glyptodon.guacamole.net.basic.websocket.tomcat.WebSocketTunnelModule" }; - /** - * Checks for JSR 356 support, returning true if such support is found, and - * false otherwise. - * - * @return true if support for JSR 356 is found, false otherwise. - */ - private boolean implementsJSR_356() { + private boolean loadWebSocketModule(String classname) { try { - // Attempt to find WebSocket servlet - GuacamoleClassLoader.getInstance().findClass("javax.websocket.Endpoint"); - - // JSR 356 found - return true; - - } - - // If no such servlet class, this particular WebSocket support - // is not present - catch (ClassNotFoundException e) {} - catch (NoClassDefFoundError e) {} - - // Log all GuacamoleExceptions - catch (GuacamoleException e) { - logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); - logger.debug("Error loading/detecting WebSocket support.", e); - } - - // JSR 356 not found - return false; - - } - - private boolean loadWebSocketTunnel(String classname) { - - try { - - // Attempt to find WebSocket servlet - Class servlet = (Class) + // Attempt to find WebSocket module + Class module = (Class) GuacamoleClassLoader.getInstance().findClass(classname); - // Add WebSocket servlet - serve("/websocket-tunnel").with(servlet); - return true; + // Create loader + TunnelLoader loader = module.getConstructor().newInstance(); + + // Install module, if supported + if (loader.isSupported()) { + install(loader); + return true; + } } - // If no such servlet class, this particular WebSocket support - // is not present + // If no such class or constructor, etc., then this particular + // WebSocket support is not present catch (ClassNotFoundException e) {} catch (NoClassDefFoundError e) {} + catch (NoSuchMethodException e) {} + + // Log errors which indicate bugs + catch (InstantiationException e) { + logger.debug("Error instantiating WebSocket module.", e); + } + catch (IllegalAccessException e) { + logger.debug("Error instantiating WebSocket module.", e); + } + catch (InvocationTargetException e) { + logger.debug("Error instantiating WebSocket module.", e); + } // Log all GuacamoleExceptions catch (GuacamoleException e) { @@ -127,44 +105,10 @@ public class TunnelModule extends ServletModule { // Set up HTTP tunnel serve("/tunnel").with(BasicGuacamoleTunnelServlet.class); - // Check for JSR 356 support - if (implementsJSR_356()) { - - logger.info("JSR-356 WebSocket support present."); - - // Get container - ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer"); - if (container == null) { - logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support."); - return; - } - - Provider tunnelRequestServiceProvider = getProvider(TunnelRequestService.class); - - // Build configuration for WebSocket tunnel - ServerEndpointConfig config = - ServerEndpointConfig.Builder.create(BasicGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel") - .configurator(new BasicGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider)) - .subprotocols(Arrays.asList(new String[]{"guacamole"})) - .build(); - - try { - - // Add configuration to container - container.addEndpoint(config); - - } - catch (DeploymentException e) { - logger.error("Unable to deploy WebSocket tunnel.", e); - } - - return; - } - // Try to load each WebSocket tunnel in sequence - for (String classname : WEBSOCKET_CLASSES) { - if (loadWebSocketTunnel(classname)) { - logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname); + for (String classname : WEBSOCKET_MODULES) { + if (loadWebSocketModule(classname)) { + logger.debug("WebSocket module loaded: {}", classname); return; } } diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java new file mode 100644 index 000000000..1a42bc2fb --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketTunnelModule.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2014 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; + +import com.google.inject.Provider; +import com.google.inject.servlet.ServletModule; +import java.util.Arrays; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; +import org.glyptodon.guacamole.net.basic.TunnelLoader; +import org.glyptodon.guacamole.net.basic.TunnelRequestService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loads the JSR-356 WebSocket tunnel implementation. + * + * @author Michael Jumper + */ +public class WebSocketTunnelModule extends ServletModule implements TunnelLoader { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class); + + @Override + public boolean isSupported() { + + try { + + // Attempt to find WebSocket servlet + GuacamoleClassLoader.getInstance().findClass("javax.websocket.Endpoint"); + + // Support found + return true; + + } + + // If no such servlet class, this particular WebSocket support + // is not present + catch (ClassNotFoundException e) {} + catch (NoClassDefFoundError e) {} + + // Log all GuacamoleExceptions + catch (GuacamoleException e) { + logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); + logger.debug("Error loading/detecting WebSocket support.", e); + } + + // Support not found + return false; + + } + + @Override + public void configureServlets() { + + logger.info("Loading JSR-356 WebSocket support..."); + + // Get container + ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer"); + if (container == null) { + logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support."); + return; + } + + Provider tunnelRequestServiceProvider = getProvider(TunnelRequestService.class); + + // Build configuration for WebSocket tunnel + ServerEndpointConfig config = + ServerEndpointConfig.Builder.create(BasicGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel") + .configurator(new BasicGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider)) + .subprotocols(Arrays.asList(new String[]{"guacamole"})) + .build(); + + try { + + // Add configuration to container + container.addEndpoint(config); + + } + catch (DeploymentException e) { + logger.error("Unable to deploy WebSocket tunnel.", e); + } + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java new file mode 100644 index 000000000..8bf0a4a42 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty8/WebSocketTunnelModule.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 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 com.google.inject.servlet.ServletModule; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; +import org.glyptodon.guacamole.net.basic.TunnelLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loads the Jetty 8 WebSocket tunnel implementation. + * + * @author Michael Jumper + */ +public class WebSocketTunnelModule extends ServletModule implements TunnelLoader { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class); + + @Override + public boolean isSupported() { + + try { + + // Attempt to find WebSocket servlet + GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet"); + + // Support found + return true; + + } + + // If no such servlet class, this particular WebSocket support + // is not present + catch (ClassNotFoundException e) {} + catch (NoClassDefFoundError e) {} + + // Log all GuacamoleExceptions + catch (GuacamoleException e) { + logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); + logger.debug("Error loading/detecting WebSocket support.", e); + } + + // Support not found + return false; + + } + + @Override + public void configureServlets() { + + logger.info("Loading Jetty 8 WebSocket support..."); + serve("/websocket-tunnel").with(BasicGuacamoleWebSocketTunnelServlet.class); + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java new file mode 100644 index 000000000..6a67eae49 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelModule.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 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.jetty9; + +import com.google.inject.servlet.ServletModule; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; +import org.glyptodon.guacamole.net.basic.TunnelLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loads the Jetty 9 WebSocket tunnel implementation. + * + * @author Michael Jumper + */ +public class WebSocketTunnelModule extends ServletModule implements TunnelLoader { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class); + + @Override + public boolean isSupported() { + + try { + + // Attempt to find WebSocket servlet + GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet"); + + // Support found + return true; + + } + + // If no such servlet class, this particular WebSocket support + // is not present + catch (ClassNotFoundException e) {} + catch (NoClassDefFoundError e) {} + + // Log all GuacamoleExceptions + catch (GuacamoleException e) { + logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); + logger.debug("Error loading/detecting WebSocket support.", e); + } + + // Support not found + return false; + + } + + @Override + public void configureServlets() { + + logger.info("Loading Jetty 9 WebSocket support..."); + serve("/websocket-tunnel").with(BasicGuacamoleWebSocketTunnelServlet.class); + + } + +} diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java new file mode 100644 index 000000000..4c59b78c5 --- /dev/null +++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/tomcat/WebSocketTunnelModule.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 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.tomcat; + +import com.google.inject.servlet.ServletModule; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.basic.GuacamoleClassLoader; +import org.glyptodon.guacamole.net.basic.TunnelLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loads the Jetty 9 WebSocket tunnel implementation. + * + * @author Michael Jumper + */ +public class WebSocketTunnelModule extends ServletModule implements TunnelLoader { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class); + + @Override + public boolean isSupported() { + + try { + + // Attempt to find WebSocket servlet + GuacamoleClassLoader.getInstance().findClass("org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet"); + + // Support found + return true; + + } + + // If no such servlet class, this particular WebSocket support + // is not present + catch (ClassNotFoundException e) {} + catch (NoClassDefFoundError e) {} + + // Log all GuacamoleExceptions + catch (GuacamoleException e) { + logger.error("Unable to load/detect WebSocket support: {}", e.getMessage()); + logger.debug("Error loading/detecting WebSocket support.", e); + } + + // Support not found + return false; + + } + + @Override + public void configureServlets() { + + logger.info("Loading Tomcat 7 WebSocket support..."); + serve("/websocket-tunnel").with(BasicGuacamoleWebSocketTunnelServlet.class); + + } + +}