You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by jm...@apache.org on 2016/03/29 06:20:30 UTC

[21/51] [abbrv] incubator-guacamole-client git commit: GUACAMOLE-1: Rename Basic* classes sensibly. Refactor tunnel classes into own packages.

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java
new file mode 100644
index 0000000..3607b3d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java
@@ -0,0 +1,363 @@
+/*
+ * 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.apache.guacamole.tunnel;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.List;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleSecurityException;
+import org.apache.guacamole.GuacamoleSecurityException;
+import org.apache.guacamole.GuacamoleSession;
+import org.apache.guacamole.GuacamoleUnauthorizedException;
+import org.apache.guacamole.GuacamoleUnauthorizedException;
+import org.apache.guacamole.net.DelegatingGuacamoleTunnel;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.ConnectionGroup;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.rest.ObjectRetrievalService;
+import org.apache.guacamole.rest.auth.AuthenticationService;
+import org.apache.guacamole.protocol.GuacamoleClientInformation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class that takes a standard request from the Guacamole JavaScript
+ * client and produces the corresponding GuacamoleTunnel. The implementation
+ * of this utility is specific to the form of request used by the upstream
+ * Guacamole web application, and is not necessarily useful to applications
+ * that use purely the Guacamole API.
+ *
+ * @author Michael Jumper
+ * @author Vasily Loginov
+ */
+@Singleton
+public class TunnelRequestService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
+
+    /**
+     * A service for authenticating users from auth tokens.
+     */
+    @Inject
+    private AuthenticationService authenticationService;
+
+    /**
+     * Service for convenient retrieval of objects.
+     */
+    @Inject
+    private ObjectRetrievalService retrievalService;
+
+    /**
+     * Reads and returns the client information provided within the given
+     * request.
+     *
+     * @param request
+     *     The request describing tunnel to create.
+     *
+     * @return GuacamoleClientInformation
+     *     An object containing information about the client sending the tunnel
+     *     request.
+     *
+     * @throws GuacamoleException
+     *     If the parameters of the tunnel request are invalid.
+     */
+    protected GuacamoleClientInformation getClientInformation(TunnelRequest request)
+        throws GuacamoleException {
+
+        // Get client information
+        GuacamoleClientInformation info = new GuacamoleClientInformation();
+
+        // Set width if provided
+        Integer width = request.getWidth();
+        if (width != null)
+            info.setOptimalScreenWidth(width);
+
+        // Set height if provided
+        Integer height = request.getHeight();
+        if (height != null)
+            info.setOptimalScreenHeight(height);
+
+        // Set resolution if provided
+        Integer dpi = request.getDPI();
+        if (dpi != null)
+            info.setOptimalResolution(dpi);
+
+        // Add audio mimetypes
+        List<String> audioMimetypes = request.getAudioMimetypes();
+        if (audioMimetypes != null)
+            info.getAudioMimetypes().addAll(audioMimetypes);
+
+        // Add video mimetypes
+        List<String> videoMimetypes = request.getVideoMimetypes();
+        if (videoMimetypes != null)
+            info.getVideoMimetypes().addAll(videoMimetypes);
+
+        // Add image mimetypes
+        List<String> imageMimetypes = request.getImageMimetypes();
+        if (imageMimetypes != null)
+            info.getImageMimetypes().addAll(imageMimetypes);
+
+        return info;
+    }
+
+    /**
+     * Creates a new tunnel using which is connected to the connection or
+     * connection group identifier by the given ID. Client information
+     * is specified in the {@code info} parameter.
+     *
+     * @param context
+     *     The UserContext associated with the user for whom the tunnel is
+     *     being created.
+     *
+     * @param type
+     *     The type of object being connected to (connection or group).
+     *
+     * @param id
+     *     The id of the connection or group being connected to.
+     *
+     * @param info
+     *     Information describing the connected Guacamole client.
+     *
+     * @return
+     *     A new tunnel, connected as required by the request.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while creating the tunnel.
+     */
+    protected GuacamoleTunnel createConnectedTunnel(UserContext context,
+            final TunnelRequest.Type type, String id,
+            GuacamoleClientInformation info)
+            throws GuacamoleException {
+
+        // Create connected tunnel from identifier
+        GuacamoleTunnel tunnel = null;
+        switch (type) {
+
+            // Connection identifiers
+            case CONNECTION: {
+
+                // Get connection directory
+                Directory<Connection> directory = context.getConnectionDirectory();
+
+                // Get authorized connection
+                Connection connection = directory.get(id);
+                if (connection == null) {
+                    logger.info("Connection \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
+                    throw new GuacamoleSecurityException("Requested connection is not authorized.");
+                }
+
+                // Connect tunnel
+                tunnel = connection.connect(info);
+                logger.info("User \"{}\" connected to connection \"{}\".", context.self().getIdentifier(), id);
+                break;
+            }
+
+            // Connection group identifiers
+            case CONNECTION_GROUP: {
+
+                // Get connection group directory
+                Directory<ConnectionGroup> directory = context.getConnectionGroupDirectory();
+
+                // Get authorized connection group
+                ConnectionGroup group = directory.get(id);
+                if (group == null) {
+                    logger.info("Connection group \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
+                    throw new GuacamoleSecurityException("Requested connection group is not authorized.");
+                }
+
+                // Connect tunnel
+                tunnel = group.connect(info);
+                logger.info("User \"{}\" connected to group \"{}\".", context.self().getIdentifier(), id);
+                break;
+            }
+
+            // Type is guaranteed to be one of the above
+            default:
+                assert(false);
+
+        }
+
+        return tunnel;
+
+    }
+
+    /**
+     * Associates the given tunnel with the given session, returning a wrapped
+     * version of the same tunnel which automatically handles closure and
+     * removal from the session.
+     *
+     * @param tunnel
+     *     The connected tunnel to wrap and monitor.
+     *
+     * @param authToken
+     *     The authentication token associated with the given session. If
+     *     provided, this token will be automatically invalidated (and the
+     *     corresponding session destroyed) if tunnel errors imply that the
+     *     user is no longer authorized.
+     *
+     * @param session
+     *     The Guacamole session to associate the tunnel with.
+     *
+     * @param type
+     *     The type of object being connected to (connection or group).
+     *
+     * @param id
+     *     The id of the connection or group being connected to.
+     *
+     * @return
+     *     A new tunnel, associated with the given session, which delegates all
+     *     functionality to the given tunnel while monitoring and automatically
+     *     handling closure.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while obtaining the tunnel.
+     */
+    protected GuacamoleTunnel createAssociatedTunnel(GuacamoleTunnel tunnel,
+            final String authToken,  final GuacamoleSession session,
+            final TunnelRequest.Type type, final String id)
+            throws GuacamoleException {
+
+        // Monitor tunnel closure and data
+        GuacamoleTunnel monitoredTunnel = new DelegatingGuacamoleTunnel(tunnel) {
+
+            /**
+             * The time the connection began, measured in milliseconds since
+             * midnight, January 1, 1970 UTC.
+             */
+            private final long connectionStartTime = System.currentTimeMillis();
+
+            @Override
+            public void close() throws GuacamoleException {
+
+                long connectionEndTime = System.currentTimeMillis();
+                long duration = connectionEndTime - connectionStartTime;
+
+                // Log closure
+                switch (type) {
+
+                    // Connection identifiers
+                    case CONNECTION:
+                        logger.info("User \"{}\" disconnected from connection \"{}\". Duration: {} milliseconds",
+                                session.getAuthenticatedUser().getIdentifier(), id, duration);
+                        break;
+
+                    // Connection group identifiers
+                    case CONNECTION_GROUP:
+                        logger.info("User \"{}\" disconnected from connection group \"{}\". Duration: {} milliseconds",
+                                session.getAuthenticatedUser().getIdentifier(), id, duration);
+                        break;
+
+                    // Type is guaranteed to be one of the above
+                    default:
+                        assert(false);
+
+                }
+
+                try {
+
+                    // Close and clean up tunnel
+                    session.removeTunnel(getUUID().toString());
+                    super.close();
+
+                }
+
+                // Ensure any associated session is invalidated if unauthorized
+                catch (GuacamoleUnauthorizedException e) {
+
+                    // If there is an associated auth token, invalidate it
+                    if (authenticationService.destroyGuacamoleSession(authToken))
+                        logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
+
+                    // Continue with exception processing
+                    throw e;
+
+                }
+
+            }
+
+        };
+
+        // Associate tunnel with session
+        session.addTunnel(monitoredTunnel);
+        return monitoredTunnel;
+        
+    }
+
+    /**
+     * Creates a new tunnel using the parameters and credentials present in
+     * the given request.
+     *
+     * @param request
+     *     The request describing the tunnel to create.
+     *
+     * @return
+     *     The created tunnel, or null if the tunnel could not be created.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while creating the tunnel.
+     */
+    public GuacamoleTunnel createTunnel(TunnelRequest request)
+            throws GuacamoleException {
+
+        // Parse request parameters
+        String authToken                = request.getAuthenticationToken();
+        String id                       = request.getIdentifier();
+        TunnelRequest.Type type         = request.getType();
+        String authProviderIdentifier   = request.getAuthenticationProviderIdentifier();
+        GuacamoleClientInformation info = getClientInformation(request);
+
+        GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
+        UserContext userContext = retrievalService.retrieveUserContext(session, authProviderIdentifier);
+
+        try {
+
+            // Create connected tunnel using provided connection ID and client information
+            GuacamoleTunnel tunnel = createConnectedTunnel(userContext, type, id, info);
+
+            // Associate tunnel with session
+            return createAssociatedTunnel(tunnel, authToken, session, type, id);
+
+        }
+
+        // Ensure any associated session is invalidated if unauthorized
+        catch (GuacamoleUnauthorizedException e) {
+
+            // If there is an associated auth token, invalidate it
+            if (authenticationService.destroyGuacamoleSession(authToken))
+                logger.debug("Implicitly invalidated session for token \"{}\".", authToken);
+
+            // Continue with exception processing
+            throw e;
+
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/http/HTTPTunnelRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/http/HTTPTunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/http/HTTPTunnelRequest.java
new file mode 100644
index 0000000..072c792
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/http/HTTPTunnelRequest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.apache.guacamole.tunnel.http;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.tunnel.TunnelRequest;
+
+/**
+ * HTTP-specific implementation of TunnelRequest.
+ *
+ * @author Michael Jumper
+ */
+public class HTTPTunnelRequest extends TunnelRequest {
+
+    /**
+     * A copy of the parameters obtained from the HttpServletRequest used to
+     * construct the HTTPTunnelRequest.
+     */
+    private final Map<String, List<String>> parameterMap =
+            new HashMap<String, List<String>>();
+
+    /**
+     * Creates a HTTPTunnelRequest which copies and exposes the parameters
+     * from the given HttpServletRequest.
+     *
+     * @param request
+     *     The HttpServletRequest to copy parameter values from.
+     */
+    @SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
+    public HTTPTunnelRequest(HttpServletRequest request) {
+
+        // For each parameter
+        for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
+                request.getParameterMap()).entrySet()) {
+
+            // Get parameter name and corresponding values
+            String parameterName = mapEntry.getKey();
+            List<String> parameterValues = Arrays.asList(mapEntry.getValue());
+
+            // Store copy of all values in our own map
+            parameterMap.put(
+                parameterName,
+                new ArrayList<String>(parameterValues)
+            );
+
+        }
+
+    }
+
+    @Override
+    public String getParameter(String name) {
+        List<String> values = getParameterValues(name);
+
+        // Return the first value from the list if available
+        if (values != null && !values.isEmpty())
+            return values.get(0);
+
+        return null;
+    }
+
+    @Override
+    public List<String> getParameterValues(String name) {
+        return parameterMap.get(name);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/http/RestrictedGuacamoleHTTPTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/http/RestrictedGuacamoleHTTPTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/http/RestrictedGuacamoleHTTPTunnelServlet.java
new file mode 100644
index 0000000..986c684
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/http/RestrictedGuacamoleHTTPTunnelServlet.java
@@ -0,0 +1,69 @@
+/*
+ * 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.apache.guacamole.tunnel.http;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.tunnel.TunnelRequestService;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Connects users to a tunnel associated with the authorized connection
+ * having the given ID.
+ *
+ * @author Michael Jumper
+ */
+@Singleton
+public class RestrictedGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    @Inject
+    private TunnelRequestService tunnelRequestService;
+    
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleHTTPTunnelServlet.class);
+
+    @Override
+    protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
+
+        // Attempt to create HTTP tunnel
+        GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
+
+        // If successful, warn of lack of WebSocket
+        logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
+
+        return tunnel;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/http/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/http/package-info.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/http/package-info.java
new file mode 100644
index 0000000..6b732e2
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/http/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes which leverage Guacamole's built-in HTTP tunnel implementation.
+ */
+package org.apache.guacamole.tunnel.http;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/package-info.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/package-info.java
new file mode 100644
index 0000000..b06f8e4
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes which are common to all tunnel implementations.
+ */
+package org.apache.guacamole.tunnel;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/RestrictedGuacamoleWebSocketTunnelEndpoint.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/RestrictedGuacamoleWebSocketTunnelEndpoint.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/RestrictedGuacamoleWebSocketTunnelEndpoint.java
new file mode 100644
index 0000000..09b7487
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/RestrictedGuacamoleWebSocketTunnelEndpoint.java
@@ -0,0 +1,121 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket;
+
+import com.google.inject.Provider;
+import java.util.Map;
+import javax.websocket.EndpointConfig;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.Session;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.tunnel.TunnelRequest;
+import org.apache.guacamole.tunnel.TunnelRequestService;
+import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
+import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
+
+/**
+ * Tunnel implementation which uses WebSocket as a tunnel backend, rather than
+ * HTTP, properly parsing connection IDs included in the connection request.
+ */
+public class RestrictedGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
+
+    /**
+     * Unique string which shall be used to store the TunnelRequest
+     * associated with a WebSocket connection.
+     */
+    private static final String TUNNEL_REQUEST_PROPERTY = "WS_GUAC_TUNNEL_REQUEST";
+
+    /**
+     * Unique string which shall be used to store the TunnelRequestService to
+     * be used for processing TunnelRequests.
+     */
+    private static final String TUNNEL_REQUEST_SERVICE_PROPERTY = "WS_GUAC_TUNNEL_REQUEST_SERVICE";
+
+    /**
+     * Configurator implementation which stores the requested GuacamoleTunnel
+     * within the user properties. The GuacamoleTunnel will be later retrieved
+     * during the connection process.
+     */
+    public static class Configurator extends ServerEndpointConfig.Configurator {
+
+        /**
+         * Provider which provides instances of a service for handling
+         * tunnel requests.
+         */
+        private final Provider<TunnelRequestService> tunnelRequestServiceProvider;
+         
+        /**
+         * Creates a new Configurator which uses the given tunnel request
+         * service provider to retrieve the necessary service to handle new
+         * connections requests.
+         * 
+         * @param tunnelRequestServiceProvider
+         *     The tunnel request service provider to use for all new
+         *     connections.
+         */
+        public Configurator(Provider<TunnelRequestService> tunnelRequestServiceProvider) {
+            this.tunnelRequestServiceProvider = tunnelRequestServiceProvider;
+        }
+        
+        @Override
+        public void modifyHandshake(ServerEndpointConfig config,
+                HandshakeRequest request, HandshakeResponse response) {
+
+            super.modifyHandshake(config, request, response);
+            
+            // Store tunnel request and tunnel request service for retrieval
+            // upon WebSocket open
+            Map<String, Object> userProperties = config.getUserProperties();
+            userProperties.clear();
+            userProperties.put(TUNNEL_REQUEST_PROPERTY, new WebSocketTunnelRequest(request));
+            userProperties.put(TUNNEL_REQUEST_SERVICE_PROPERTY, tunnelRequestServiceProvider.get());
+
+        }
+        
+    }
+    
+    @Override
+    protected GuacamoleTunnel createTunnel(Session session,
+            EndpointConfig config) throws GuacamoleException {
+
+        Map<String, Object> userProperties = config.getUserProperties();
+
+        // Get original tunnel request
+        TunnelRequest tunnelRequest = (TunnelRequest) userProperties.get(TUNNEL_REQUEST_PROPERTY);
+        if (tunnelRequest == null)
+            return null;
+
+        // Get tunnel request service
+        TunnelRequestService tunnelRequestService = (TunnelRequestService) userProperties.get(TUNNEL_REQUEST_SERVICE_PROPERTY);
+        if (tunnelRequestService == null)
+            return null;
+
+        // Create and return tunnel
+        return tunnelRequestService.createTunnel(tunnelRequest);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelModule.java
new file mode 100644
index 0000000..9183b4d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelModule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.apache.guacamole.tunnel.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.apache.guacamole.tunnel.TunnelLoader;
+import org.apache.guacamole.tunnel.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
+            Class.forName("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) {}
+
+        // 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(RestrictedGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel")
+                                            .configurator(new RestrictedGuacamoleWebSocketTunnelEndpoint.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);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelRequest.java
new file mode 100644
index 0000000..e5d424d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/WebSocketTunnelRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket;
+
+import java.util.List;
+import java.util.Map;
+import javax.websocket.server.HandshakeRequest;
+import org.apache.guacamole.tunnel.TunnelRequest;
+
+/**
+ * WebSocket-specific implementation of TunnelRequest.
+ *
+ * @author Michael Jumper
+ */
+public class WebSocketTunnelRequest extends TunnelRequest {
+
+    /**
+     * All parameters passed via HTTP to the WebSocket handshake.
+     */
+    private final Map<String, List<String>> handshakeParameters;
+    
+    /**
+     * Creates a TunnelRequest implementation which delegates parameter and
+     * session retrieval to the given HandshakeRequest.
+     *
+     * @param request The HandshakeRequest to wrap.
+     */
+    public WebSocketTunnelRequest(HandshakeRequest request) {
+        this.handshakeParameters = request.getParameterMap();
+    }
+
+    @Override
+    public String getParameter(String name) {
+
+        // Pull list of values, if present
+        List<String> values = getParameterValues(name);
+        if (values == null || values.isEmpty())
+            return null;
+
+        // Return first parameter value arbitrarily
+        return values.get(0);
+
+    }
+
+    @Override
+    public List<String> getParameterValues(String name) {
+        return handshakeParameters.get(name);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..619c606
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java
@@ -0,0 +1,232 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty8;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.io.GuacamoleReader;
+import org.apache.guacamole.io.GuacamoleWriter;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.eclipse.jetty.websocket.WebSocket;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+import org.eclipse.jetty.websocket.WebSocketServlet;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleConnectionClosedException;
+import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
+import org.apache.guacamole.tunnel.TunnelRequest;
+import org.apache.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) {
+
+        final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
+
+        // Return new WebSocket which communicates through tunnel
+        return new WebSocket.OnTextMessage() {
+
+            /**
+             * The GuacamoleTunnel associated with the connected WebSocket. If
+             * the WebSocket has not yet been connected, this will be null.
+             */
+            private GuacamoleTunnel tunnel = null;
+
+            @Override
+            public void onMessage(String string) {
+
+                // Ignore inbound messages if there is no associated tunnel
+                if (tunnel == null)
+                    return;
+
+                GuacamoleWriter writer = tunnel.acquireWriter();
+
+                // Write message received
+                try {
+                    writer.write(string.toCharArray());
+                }
+                catch (GuacamoleConnectionClosedException e) {
+                    logger.debug("Connection to guacd closed.", e);
+                }
+                catch (GuacamoleException e) {
+                    logger.debug("WebSocket tunnel write failed.", e);
+                }
+
+                tunnel.releaseWriter();
+
+            }
+
+            @Override
+            public void onOpen(final Connection connection) {
+
+                try {
+                    tunnel = doConnect(tunnelRequest);
+                }
+                catch (GuacamoleException e) {
+                    logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
+                    logger.debug("Error connecting WebSocket tunnel.", e);
+                    closeConnection(connection, e.getStatus());
+                    return;
+                }
+
+                // Do not start connection if tunnel does not exist
+                if (tunnel == null) {
+                    closeConnection(connection, GuacamoleStatus.RESOURCE_NOT_FOUND);
+                    return;
+                }
+
+                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.info("WebSocket connection terminated: {}", e.getMessage());
+                                logger.debug("WebSocket connection terminated due to client error.", e);
+                                closeConnection(connection, e.getStatus());
+                            }
+                            catch (GuacamoleConnectionClosedException e) {
+                                logger.debug("Connection to guacd closed.", e);
+                                closeConnection(connection, GuacamoleStatus.SUCCESS);
+                            }
+                            catch (GuacamoleException e) {
+                                logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
+                                logger.debug("Internal error during connection to guacd.", e);
+                                closeConnection(connection, e.getStatus());
+                            }
+
+                        }
+                        catch (IOException e) {
+                            logger.debug("WebSocket tunnel read failed due to I/O error.", e);
+                        }
+
+                    }
+
+                };
+
+                readThread.start();
+
+            }
+
+            @Override
+            public void onClose(int i, String string) {
+                try {
+                    if (tunnel != null)
+                        tunnel.close();
+                }
+                catch (GuacamoleException e) {
+                    logger.debug("Unable to close connection to guacd.", e);
+                }
+            }
+
+        };
+
+    }
+
+    /**
+     * Called whenever the JavaScript Guacamole client makes a connection
+     * request. It it up to the implementor of this function to define what
+     * conditions must be met for a tunnel to be configured and returned as a
+     * result of this connection request (whether some sort of credentials must
+     * be specified, for example).
+     *
+     * @param request
+     *     The TunnelRequest associated with the connection request received.
+     *     Any parameters specified along with the connection request can be
+     *     read from this object.
+     *
+     * @return
+     *     A newly constructed GuacamoleTunnel if successful, null otherwise.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while constructing the GuacamoleTunnel, or if the
+     *     conditions required for connection are not met.
+     */
+    protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
+            throws GuacamoleException;
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/RestrictedGuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/RestrictedGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/RestrictedGuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..b7a82b9
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/RestrictedGuacamoleWebSocketTunnelServlet.java
@@ -0,0 +1,52 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty8;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.tunnel.TunnelRequestService;
+import org.apache.guacamole.tunnel.TunnelRequest;
+
+/**
+ * Tunnel servlet implementation which uses WebSocket as a tunnel backend,
+ * rather than HTTP, properly parsing connection IDs included in the connection
+ * request.
+ */
+@Singleton
+public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    @Inject
+    private TunnelRequestService tunnelRequestService;
+ 
+    @Override
+    protected GuacamoleTunnel doConnect(TunnelRequest request)
+            throws GuacamoleException {
+        return tunnelRequestService.createTunnel(request);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/WebSocketTunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/WebSocketTunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/WebSocketTunnelModule.java
new file mode 100644
index 0000000..79a09db
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/WebSocketTunnelModule.java
@@ -0,0 +1,73 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty8;
+
+import com.google.inject.servlet.ServletModule;
+import org.apache.guacamole.tunnel.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
+            Class.forName("org.apache.guacamole.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) {}
+
+        // Support not found
+        return false;
+        
+    }
+    
+    @Override
+    public void configureServlets() {
+
+        logger.info("Loading Jetty 8 WebSocket support...");
+        serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/package-info.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/package-info.java
new file mode 100644
index 0000000..85e5758
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/package-info.java
@@ -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.apache.guacamole.tunnel.websocket.jetty8;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
new file mode 100644
index 0000000..4d118fb
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
@@ -0,0 +1,243 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty9;
+
+import java.io.IOException;
+import org.eclipse.jetty.websocket.api.CloseStatus;
+import org.eclipse.jetty.websocket.api.RemoteEndpoint;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.WebSocketListener;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleConnectionClosedException;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.io.GuacamoleReader;
+import org.apache.guacamole.io.GuacamoleWriter;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.protocol.GuacamoleStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * WebSocket listener implementation which provides a Guacamole tunnel
+ * 
+ * @author Michael Jumper
+ */
+public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListener {
+
+    /**
+     * The default, minimum buffer size for instructions.
+     */
+    private static final int BUFFER_SIZE = 8192;
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleWebSocketTunnelServlet.class);
+
+    /**
+     * The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
+     * as reads/writes to this tunnel.
+     */
+    private GuacamoleTunnel tunnel;
+ 
+    /**
+     * Sends the given status on the given WebSocket connection and closes the
+     * connection.
+     *
+     * @param session The outbound WebSocket connection to close.
+     * @param guac_status The status to send.
+     */
+    private void closeConnection(Session session, GuacamoleStatus guac_status) {
+
+        try {
+            int code = guac_status.getWebSocketCode();
+            String message = Integer.toString(guac_status.getGuacamoleStatusCode());
+            session.close(new CloseStatus(code, message));
+        }
+        catch (IOException e) {
+            logger.debug("Unable to close WebSocket connection.", e);
+        }
+
+    }
+
+    /**
+     * Returns a new tunnel for the given session. How this tunnel is created
+     * or retrieved is implementation-dependent.
+     *
+     * @param session The session associated with the active WebSocket
+     *                connection.
+     * @return A connected tunnel, or null if no such tunnel exists.
+     * @throws GuacamoleException If an error occurs while retrieving the
+     *                            tunnel, or if access to the tunnel is denied.
+     */
+    protected abstract GuacamoleTunnel createTunnel(Session session)
+            throws GuacamoleException;
+
+    @Override
+    public void onWebSocketConnect(final Session session) {
+
+        try {
+
+            // Get tunnel
+            tunnel = createTunnel(session);
+            if (tunnel == null) {
+                closeConnection(session, GuacamoleStatus.RESOURCE_NOT_FOUND);
+                return;
+            }
+
+        }
+        catch (GuacamoleException e) {
+            logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
+            logger.debug("Error connecting WebSocket tunnel.", e);
+            closeConnection(session, e.getStatus());
+            return;
+        }
+
+        // Prepare read transfer thread
+        Thread readThread = new Thread() {
+
+            /**
+             * Remote (client) side of this connection
+             */
+            private final RemoteEndpoint remote = session.getRemote();
+                
+            @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) {
+                                remote.sendString(buffer.toString());
+                                buffer.setLength(0);
+                            }
+
+                        }
+
+                        // No more data
+                        closeConnection(session, GuacamoleStatus.SUCCESS);
+
+                    }
+
+                    // Catch any thrown guacamole exception and attempt
+                    // to pass within the WebSocket connection, logging
+                    // each error appropriately.
+                    catch (GuacamoleClientException e) {
+                        logger.info("WebSocket connection terminated: {}", e.getMessage());
+                        logger.debug("WebSocket connection terminated due to client error.", e);
+                        closeConnection(session, e.getStatus());
+                    }
+                    catch (GuacamoleConnectionClosedException e) {
+                        logger.debug("Connection to guacd closed.", e);
+                        closeConnection(session, GuacamoleStatus.SUCCESS);
+                    }
+                    catch (GuacamoleException e) {
+                        logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
+                        logger.debug("Internal error during connection to guacd.", e);
+                        closeConnection(session, e.getStatus());
+                    }
+
+                }
+                catch (IOException e) {
+                    logger.debug("I/O error prevents further reads.", e);
+                }
+
+            }
+
+        };
+
+        readThread.start();
+
+    }
+
+    @Override
+    public void onWebSocketText(String message) {
+
+        // Ignore inbound messages if there is no associated tunnel
+        if (tunnel == null)
+            return;
+
+        GuacamoleWriter writer = tunnel.acquireWriter();
+
+        try {
+            // Write received message
+            writer.write(message.toCharArray());
+        }
+        catch (GuacamoleConnectionClosedException e) {
+            logger.debug("Connection to guacd closed.", e);
+        }
+        catch (GuacamoleException e) {
+            logger.debug("WebSocket tunnel write failed.", e);
+        }
+
+        tunnel.releaseWriter();
+
+    }
+
+    @Override
+    public void onWebSocketBinary(byte[] payload, int offset, int length) {
+        throw new UnsupportedOperationException("Binary WebSocket messages are not supported.");
+    }
+
+    @Override
+    public void onWebSocketError(Throwable t) {
+
+        logger.debug("WebSocket tunnel closing due to error.", t);
+        
+        try {
+            if (tunnel != null)
+                tunnel.close();
+        }
+        catch (GuacamoleException e) {
+            logger.debug("Unable to close connection to guacd.", e);
+        }
+
+     }
+
+   
+    @Override
+    public void onWebSocketClose(int statusCode, String reason) {
+
+        try {
+            if (tunnel != null)
+                tunnel.close();
+        }
+        catch (GuacamoleException e) {
+            logger.debug("Unable to close connection to guacd.", e);
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketCreator.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketCreator.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketCreator.java
new file mode 100644
index 0000000..c15215d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketCreator.java
@@ -0,0 +1,72 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty9;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.apache.guacamole.tunnel.TunnelRequestService;
+
+/**
+ * WebSocketCreator which selects the appropriate WebSocketListener
+ * implementation if the "guacamole" subprotocol is in use.
+ * 
+ * @author Michael Jumper
+ */
+public class RestrictedGuacamoleWebSocketCreator implements WebSocketCreator {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    private final TunnelRequestService tunnelRequestService;
+
+    /**
+     * Creates a new WebSocketCreator which uses the given TunnelRequestService
+     * to create new GuacamoleTunnels for inbound requests.
+     *
+     * @param tunnelRequestService The service to use for inbound tunnel
+     *                             requests.
+     */
+    public RestrictedGuacamoleWebSocketCreator(TunnelRequestService tunnelRequestService) {
+        this.tunnelRequestService = tunnelRequestService;
+    }
+
+    @Override
+    public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
+
+        // Validate and use "guacamole" subprotocol
+        for (String subprotocol : request.getSubProtocols()) {
+
+            if ("guacamole".equals(subprotocol)) {
+                response.setAcceptedSubProtocol(subprotocol);
+                return new RestrictedGuacamoleWebSocketTunnelListener(tunnelRequestService);
+            }
+
+        }
+
+        // Invalid protocol
+        return null;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelListener.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelListener.java
new file mode 100644
index 0000000..f24663d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelListener.java
@@ -0,0 +1,59 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty9;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.tunnel.TunnelRequestService;
+
+/**
+ * WebSocket listener implementation which properly parses connection IDs
+ * included in the connection request.
+ * 
+ * @author Michael Jumper
+ */
+public class RestrictedGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    private final TunnelRequestService tunnelRequestService;
+
+    /**
+     * Creates a new WebSocketListener which uses the given TunnelRequestService
+     * to create new GuacamoleTunnels for inbound requests.
+     *
+     * @param tunnelRequestService The service to use for inbound tunnel
+     *                             requests.
+     */
+    public RestrictedGuacamoleWebSocketTunnelListener(TunnelRequestService tunnelRequestService) {
+        this.tunnelRequestService = tunnelRequestService;
+    }
+
+    @Override
+    protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
+        return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..84b25b9
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/RestrictedGuacamoleWebSocketTunnelServlet.java
@@ -0,0 +1,54 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty9;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.apache.guacamole.tunnel.TunnelRequestService;
+
+/**
+ * A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
+ *
+ * @author Michael Jumper
+ */
+@Singleton
+public class RestrictedGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    @Inject
+    private TunnelRequestService tunnelRequestService;
+ 
+    @Override
+    public void configure(WebSocketServletFactory factory) {
+
+        // Register WebSocket implementation
+        factory.setCreator(new RestrictedGuacamoleWebSocketCreator(tunnelRequestService));
+        
+    }
+    
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelModule.java
new file mode 100644
index 0000000..3477894
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelModule.java
@@ -0,0 +1,73 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty9;
+
+import com.google.inject.servlet.ServletModule;
+import org.apache.guacamole.tunnel.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
+            Class.forName("org.apache.guacamole.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) {}
+
+        // Support not found
+        return false;
+        
+    }
+    
+    @Override
+    public void configureServlets() {
+
+        logger.info("Loading Jetty 9 WebSocket support...");
+        serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelRequest.java
new file mode 100644
index 0000000..56c7726
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/WebSocketTunnelRequest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.apache.guacamole.tunnel.websocket.jetty9;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.apache.guacamole.tunnel.TunnelRequest;
+
+/**
+ * Jetty 9 WebSocket-specific implementation of TunnelRequest.
+ *
+ * @author Michael Jumper
+ */
+public class WebSocketTunnelRequest extends TunnelRequest {
+
+    /**
+     * All parameters passed via HTTP to the WebSocket handshake.
+     */
+    private final Map<String, String[]> handshakeParameters;
+    
+    /**
+     * Creates a TunnelRequest implementation which delegates parameter and
+     * session retrieval to the given UpgradeRequest.
+     *
+     * @param request The UpgradeRequest to wrap.
+     */
+    public WebSocketTunnelRequest(UpgradeRequest request) {
+        this.handshakeParameters = request.getParameterMap();
+    }
+
+    @Override
+    public String getParameter(String name) {
+
+        // Pull list of values, if present
+        List<String> values = getParameterValues(name);
+        if (values == null || values.isEmpty())
+            return null;
+
+        // Return first parameter value arbitrarily
+        return values.get(0);
+
+    }
+
+    @Override
+    public List<String> getParameterValues(String name) {
+
+        String[] values = handshakeParameters.get(name);
+        if (values == null)
+            return null;
+
+        return Arrays.asList(values);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/package-info.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/package-info.java
new file mode 100644
index 0000000..13f2f0b
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Jetty 9 WebSocket tunnel implementation. The classes here require at least
+ * Jetty 9, prior to Jetty 9.1 (when support for JSR 356 was implemented).
+ */
+package org.apache.guacamole.tunnel.websocket.jetty9;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/713fc7f8/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/package-info.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/package-info.java
new file mode 100644
index 0000000..2c273a5
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Standard WebSocket tunnel implementation. The classes here require a recent
+ * servlet container that supports JSR 356.
+ */
+package org.apache.guacamole.tunnel.websocket;
+