guacamole-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jmuehl...@apache.org
Subject [01/51] [abbrv] incubator-guacamole-client git commit: GUACAMOLE-1: Remove useless .net.basic subpackage, now that everything is being renamed.
Date Tue, 29 Mar 2016 04:20:10 GMT
Repository: incubator-guacamole-client
Updated Branches:
  refs/heads/master 2358d8868 -> 0d39a04a1


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..832faee
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/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.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.HTTPTunnelRequest;
+import org.apache.guacamole.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/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty8/WebSocketTunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty8/WebSocketTunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty8/WebSocketTunnelModule.java
new file mode 100644
index 0000000..16c17a1
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/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.websocket.jetty8;
+
+import com.google.inject.servlet.ServletModule;
+import org.apache.guacamole.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(BasicGuacamoleWebSocketTunnelServlet.class);
+
+    }
+
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketCreator.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketCreator.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketCreator.java
new file mode 100644
index 0000000..9528509
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketCreator.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.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.TunnelRequestService;
+
+/**
+ * WebSocketCreator which selects the appropriate WebSocketListener
+ * implementation if the "guacamole" subprotocol is in use.
+ * 
+ * @author Michael Jumper
+ */
+public class BasicGuacamoleWebSocketCreator 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 BasicGuacamoleWebSocketCreator(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 BasicGuacamoleWebSocketTunnelListener(tunnelRequestService);
+            }
+
+        }
+
+        // Invalid protocol
+        return null;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java
new file mode 100644
index 0000000..2c2e91c
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.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.websocket.jetty9;
+
+import org.eclipse.jetty.websocket.api.Session;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.TunnelRequestService;
+
+/**
+ * WebSocket listener implementation which properly parses connection IDs
+ * included in the connection request.
+ * 
+ * @author Michael Jumper
+ */
+public class BasicGuacamoleWebSocketTunnelListener 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 BasicGuacamoleWebSocketTunnelListener(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/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..f16558d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.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.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.TunnelRequestService;
+
+/**
+ * A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
+ *
+ * @author Michael Jumper
+ */
+@Singleton
+public class BasicGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
+
+    /**
+     * Service for handling tunnel requests.
+     */
+    @Inject
+    private TunnelRequestService tunnelRequestService;
+ 
+    @Override
+    public void configure(WebSocketServletFactory factory) {
+
+        // Register WebSocket implementation
+        factory.setCreator(new BasicGuacamoleWebSocketCreator(tunnelRequestService));
+        
+    }
+    
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/GuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
new file mode 100644
index 0000000..368913d
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/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.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(BasicGuacamoleWebSocketTunnelServlet.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/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/WebSocketTunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/WebSocketTunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/WebSocketTunnelModule.java
new file mode 100644
index 0000000..aa62797
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/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.websocket.jetty9;
+
+import com.google.inject.servlet.ServletModule;
+import org.apache.guacamole.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(BasicGuacamoleWebSocketTunnelServlet.class);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/WebSocketTunnelRequest.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/WebSocketTunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/WebSocketTunnelRequest.java
new file mode 100644
index 0000000..4625be3
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/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.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.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/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/package-info.java b/guacamole/src/main/java/org/apache/guacamole/websocket/jetty9/package-info.java
new file mode 100644
index 0000000..9b46c78
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/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.websocket.jetty9;
+

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

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..73843e8
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/BasicGuacamoleWebSocketTunnelServlet.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.websocket.tomcat;
+
+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.TunnelRequestService;
+import org.apache.guacamole.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 BasicGuacamoleWebSocketTunnelServlet 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/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 0000000..2927584
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java
@@ -0,0 +1,265 @@
+/*
+ * 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.websocket.tomcat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.List;
+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.apache.catalina.websocket.StreamInbound;
+import org.apache.catalina.websocket.WebSocketServlet;
+import org.apache.catalina.websocket.WsOutbound;
+import org.apache.guacamole.GuacamoleClientException;
+import org.apache.guacamole.GuacamoleConnectionClosedException;
+import org.apache.guacamole.HTTPTunnelRequest;
+import org.apache.guacamole.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 {
+
+    /**
+     * The default, minimum buffer size for instructions.
+     */
+    private static final int BUFFER_SIZE = 8192;
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
+
+    /**
+     * Sends the given status on the given WebSocket connection and closes the
+     * connection.
+     *
+     * @param outbound The outbound WebSocket connection to close.
+     * @param guac_status The status to send.
+     */
+    public void closeConnection(WsOutbound outbound, GuacamoleStatus guac_status) {
+
+        try {
+            byte[] message = Integer.toString(guac_status.getGuacamoleStatusCode()).getBytes("UTF-8");
+            outbound.close(guac_status.getWebSocketCode(), ByteBuffer.wrap(message));
+        }
+        catch (IOException e) {
+            logger.debug("Unable to close WebSocket tunnel.", e);
+        }
+
+    }
+
+    @Override
+    protected String selectSubProtocol(List<String> subProtocols) {
+
+        // Search for expected protocol
+        for (String protocol : subProtocols)
+            if ("guacamole".equals(protocol))
+                return "guacamole";
+        
+        // Otherwise, fail
+        return null;
+
+    }
+
+    @Override
+    public StreamInbound createWebSocketInbound(String protocol,
+            HttpServletRequest request) {
+
+        final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
+
+        // Return new WebSocket which communicates through tunnel
+        return new StreamInbound() {
+
+            /**
+             * The GuacamoleTunnel associated with the connected WebSocket. If
+             * the WebSocket has not yet been connected, this will be null.
+             */
+            private GuacamoleTunnel tunnel = null;
+
+            @Override
+            protected void onTextData(Reader reader) throws IOException {
+
+                // Ignore inbound messages if there is no associated tunnel
+                if (tunnel == null)
+                    return;
+
+                GuacamoleWriter writer = tunnel.acquireWriter();
+
+                // Write all available data
+                try {
+
+                    char[] buffer = new char[BUFFER_SIZE];
+
+                    int num_read;
+                    while ((num_read = reader.read(buffer)) > 0)
+                        writer.write(buffer, 0, num_read);
+
+                }
+                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 WsOutbound outbound) {
+
+                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(outbound, e.getStatus());
+                    return;
+                }
+
+                // Do not start connection if tunnel does not exist
+                if (tunnel == null) {
+                    closeConnection(outbound, 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) {
+                                        outbound.writeTextMessage(CharBuffer.wrap(buffer));
+                                        buffer.setLength(0);
+                                    }
+
+                                }
+
+                                // No more data
+                                closeConnection(outbound, 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(outbound, e.getStatus());
+                            }
+                            catch (GuacamoleConnectionClosedException e) {
+                                logger.debug("Connection to guacd closed.", e);
+                                closeConnection(outbound, GuacamoleStatus.SUCCESS);
+                            }
+                            catch (GuacamoleException e) {
+                                logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
+                                logger.debug("Internal error during connection to guacd.", e);
+                                closeConnection(outbound, e.getStatus());
+                            }
+
+                        }
+                        catch (IOException e) {
+                            logger.debug("I/O error prevents further reads.", e);
+                        }
+
+                    }
+
+                };
+
+                readThread.start();
+
+            }
+
+            @Override
+            public void onClose(int i) {
+                try {
+                    if (tunnel != null)
+                        tunnel.close();
+                }
+                catch (GuacamoleException e) {
+                    logger.debug("Unable to close connection to guacd.", e);
+                }
+            }
+
+            @Override
+            protected void onBinaryData(InputStream in) throws IOException {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        };
+
+    }
+
+    /**
+     * 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/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/WebSocketTunnelModule.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/WebSocketTunnelModule.java b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/WebSocketTunnelModule.java
new file mode 100644
index 0000000..85caf1b
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/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.websocket.tomcat;
+
+import com.google.inject.servlet.ServletModule;
+import org.apache.guacamole.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.tomcat.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 Tomcat 7 WebSocket support...");
+        serve("/websocket-tunnel").with(BasicGuacamoleWebSocketTunnelServlet.class);
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/package-info.java
----------------------------------------------------------------------
diff --git a/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/package-info.java b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/package-info.java
new file mode 100644
index 0000000..7083a3e
--- /dev/null
+++ b/guacamole/src/main/java/org/apache/guacamole/websocket/tomcat/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * Tomcat WebSocket tunnel implementation. The classes here require at least
+ * Tomcat 7.0, and may change significantly as there is no common WebSocket
+ * API for Java yet.
+ */
+package org.apache.guacamole.websocket.tomcat;
+

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/648a6c96/guacamole/src/main/webapp/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/guacamole/src/main/webapp/WEB-INF/web.xml b/guacamole/src/main/webapp/WEB-INF/web.xml
index e1f535e..6e105f8 100644
--- a/guacamole/src/main/webapp/WEB-INF/web.xml
+++ b/guacamole/src/main/webapp/WEB-INF/web.xml
@@ -42,7 +42,7 @@
     </filter-mapping>
 
     <listener>
-        <listener-class>org.apache.guacamole.net.basic.BasicServletContextListener</listener-class>
+        <listener-class>org.apache.guacamole.BasicServletContextListener</listener-class>
     </listener>
 
     <!-- Audio file mimetype mappings -->


Mime
View raw message