GUAC-921: Move WebSocket support loading into independent modules.

This commit is contained in:
Michael Jumper
2014-11-06 12:41:07 -08:00
parent 3f65e45e0f
commit d1ec32d066
6 changed files with 434 additions and 91 deletions

View File

@@ -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();
}

View File

@@ -22,15 +22,9 @@
package org.glyptodon.guacamole.net.basic; package org.glyptodon.guacamole.net.basic;
import com.google.inject.Provider;
import com.google.inject.servlet.ServletModule; import com.google.inject.servlet.ServletModule;
import java.util.Arrays; import java.lang.reflect.InvocationTargetException;
import javax.servlet.http.HttpServlet;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.glyptodon.guacamole.GuacamoleException; import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.basic.websocket.BasicGuacamoleWebSocketTunnelEndpoint;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -47,66 +41,50 @@ public class TunnelModule extends ServletModule {
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class); 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 = { private static final String[] WEBSOCKET_MODULES = {
"org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet", "org.glyptodon.guacamole.net.basic.websocket.WebSocketTunnelModule",
"org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet", "org.glyptodon.guacamole.net.basic.websocket.jetty8.WebSocketTunnelModule",
"org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet" "org.glyptodon.guacamole.net.basic.websocket.jetty9.WebSocketTunnelModule",
"org.glyptodon.guacamole.net.basic.websocket.tomcat.WebSocketTunnelModule"
}; };
/** private boolean loadWebSocketModule(String classname) {
* 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() {
try { try {
// Attempt to find WebSocket servlet // Attempt to find WebSocket module
GuacamoleClassLoader.getInstance().findClass("javax.websocket.Endpoint"); Class<TunnelLoader> module = (Class<TunnelLoader>)
// 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<HttpServlet> servlet = (Class<HttpServlet>)
GuacamoleClassLoader.getInstance().findClass(classname); GuacamoleClassLoader.getInstance().findClass(classname);
// Add WebSocket servlet // Create loader
serve("/websocket-tunnel").with(servlet); TunnelLoader loader = module.getConstructor().newInstance();
return true;
// Install module, if supported
if (loader.isSupported()) {
install(loader);
return true;
}
} }
// If no such servlet class, this particular WebSocket support // If no such class or constructor, etc., then this particular
// is not present // WebSocket support is not present
catch (ClassNotFoundException e) {} catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError 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 // Log all GuacamoleExceptions
catch (GuacamoleException e) { catch (GuacamoleException e) {
@@ -127,44 +105,10 @@ public class TunnelModule extends ServletModule {
// Set up HTTP tunnel // Set up HTTP tunnel
serve("/tunnel").with(BasicGuacamoleTunnelServlet.class); 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<TunnelRequestService> 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 // Try to load each WebSocket tunnel in sequence
for (String classname : WEBSOCKET_CLASSES) { for (String classname : WEBSOCKET_MODULES) {
if (loadWebSocketTunnel(classname)) { if (loadWebSocketModule(classname)) {
logger.info("Legacy (non-JSR) WebSocket support loaded: {}", classname); logger.debug("WebSocket module loaded: {}", classname);
return; return;
} }
} }

View File

@@ -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<TunnelRequestService> 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}