diff --git a/guacamole/pom.xml b/guacamole/pom.xml
index 01d075359..57c199906 100644
--- a/guacamole/pom.xml
+++ b/guacamole/pom.xml
@@ -150,6 +150,14 @@
provided
+
+
+ org.eclipse.jetty.websocket
+ websocket-servlet
+ 9.0.3.v20130506
+ provided
+
+
org.apache.tomcat
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java
index 723f685cb..e233d60e1 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/WebSocketSupportLoader.java
@@ -56,6 +56,7 @@ public class WebSocketSupportLoader implements ServletContextListener {
*/
private static final String[] WEBSOCKET_CLASSES = {
"org.glyptodon.guacamole.net.basic.websocket.jetty8.BasicGuacamoleWebSocketTunnelServlet",
+ "org.glyptodon.guacamole.net.basic.websocket.jetty9.BasicGuacamoleWebSocketTunnelServlet",
"org.glyptodon.guacamole.net.basic.websocket.tomcat.BasicGuacamoleWebSocketTunnelServlet"
};
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/GuacamoleWebSocketTunnelServlet.java
deleted file mode 100644
index c72edcd2c..000000000
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/GuacamoleWebSocketTunnelServlet.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2013 Glyptodon LLC
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-package org.glyptodon.guacamole.net.basic.websocket.jetty;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import org.glyptodon.guacamole.GuacamoleException;
-import org.glyptodon.guacamole.io.GuacamoleReader;
-import org.glyptodon.guacamole.io.GuacamoleWriter;
-import org.glyptodon.guacamole.net.GuacamoleTunnel;
-import org.eclipse.jetty.websocket.WebSocket;
-import org.eclipse.jetty.websocket.WebSocket.Connection;
-import org.eclipse.jetty.websocket.WebSocketServlet;
-import org.glyptodon.guacamole.GuacamoleClientException;
-import org.glyptodon.guacamole.protocol.GuacamoleStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
- *
- * @author Michael Jumper
- */
-public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
-
- /**
- * Logger for this class.
- */
- private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
-
- /**
- * The default, minimum buffer size for instructions.
- */
- private static final int BUFFER_SIZE = 8192;
-
- /**
- * Sends the given status on the given WebSocket connection and closes the
- * connection.
- *
- * @param connection The WebSocket connection to close.
- * @param guac_status The status to send.
- */
- public static void closeConnection(Connection connection,
- GuacamoleStatus guac_status) {
-
- connection.close(guac_status.getWebSocketCode(),
- Integer.toString(guac_status.getGuacamoleStatusCode()));
-
- }
-
- @Override
- public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
-
- // Get tunnel
- final GuacamoleTunnel tunnel;
-
- try {
- tunnel = doConnect(request);
- }
- catch (GuacamoleException e) {
- logger.error("Error connecting WebSocket tunnel.", e);
- return null;
- }
-
- // Return new WebSocket which communicates through tunnel
- return new WebSocket.OnTextMessage() {
-
- @Override
- public void onMessage(String string) {
- GuacamoleWriter writer = tunnel.acquireWriter();
-
- // Write message received
- try {
- writer.write(string.toCharArray());
- }
- catch (GuacamoleException e) {
- logger.debug("Tunnel write failed.", e);
- }
-
- tunnel.releaseWriter();
- }
-
- @Override
- public void onOpen(final Connection connection) {
-
- Thread readThread = new Thread() {
-
- @Override
- public void run() {
-
- StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
- GuacamoleReader reader = tunnel.acquireReader();
- char[] readMessage;
-
- try {
-
- try {
-
- // Attempt to read
- while ((readMessage = reader.read()) != null) {
-
- // Buffer message
- buffer.append(readMessage);
-
- // Flush if we expect to wait or buffer is getting full
- if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
- connection.sendMessage(buffer.toString());
- buffer.setLength(0);
- }
-
- }
-
- // No more data
- closeConnection(connection, GuacamoleStatus.SUCCESS);
-
- }
-
- // Catch any thrown guacamole exception and attempt
- // to pass within the WebSocket connection, logging
- // each error appropriately.
- catch (GuacamoleClientException e) {
- logger.warn("Client request rejected: {}", e.getMessage());
- closeConnection(connection, e.getStatus());
- }
- catch (GuacamoleException e) {
- logger.error("Internal server error.", e);
- closeConnection(connection, e.getStatus());
- }
-
- }
- catch (IOException e) {
- logger.debug("Tunnel read failed due to I/O error.", e);
- }
-
- }
-
- };
-
- readThread.start();
-
- }
-
- @Override
- public void onClose(int i, String string) {
- try {
- tunnel.close();
- }
- catch (GuacamoleException e) {
- logger.debug("Unable to close WebSocket tunnel.", e);
- }
- }
-
- };
-
- }
-
- protected abstract GuacamoleTunnel doConnect(HttpServletRequest request)
- throws GuacamoleException;
-
-}
-
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketCreator.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketCreator.java
new file mode 100644
index 000000000..0547da04a
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketCreator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.glyptodon.guacamole.net.basic.websocket.jetty9;
+
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.eclipse.jetty.websocket.api.UpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+
+/**
+ * WebSocketCreator which selects the appropriate WebSocketListener
+ * implementation if the "guacamole" subprotocol is in use.
+ *
+ * @author Michael Jumper
+ */
+public class BasicGuacamoleWebSocketCreator implements WebSocketCreator {
+
+ @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();
+ }
+
+ }
+
+ // Invalid protocol
+ return null;
+
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java
similarity index 66%
rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java
rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java
index 0a9d680c5..093bcbfaa 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/BasicGuacamoleWebSocketTunnelServlet.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Glyptodon LLC
+ * 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
@@ -20,25 +20,24 @@
* THE SOFTWARE.
*/
-package org.glyptodon.guacamole.net.basic.websocket.jetty;
+package org.glyptodon.guacamole.net.basic.websocket.jetty9;
-import javax.servlet.http.HttpServletRequest;
+import org.eclipse.jetty.websocket.api.Session;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.basic.BasicTunnelRequestUtility;
-import org.glyptodon.guacamole.net.basic.HTTPTunnelRequest;
/**
- * Tunnel servlet implementation which uses WebSocket as a tunnel backend,
- * rather than HTTP, properly parsing connection IDs included in the connection
- * request.
+ * WebSocket listener implementation which properly parses connection IDs
+ * included in the connection request.
+ *
+ * @author Michael Jumper
*/
-public class BasicGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
+public class BasicGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
@Override
- protected GuacamoleTunnel doConnect(HttpServletRequest request)
- throws GuacamoleException {
- return BasicTunnelRequestUtility.createTunnel(new HTTPTunnelRequest(request));
+ protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
+ return BasicTunnelRequestUtility.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
}
}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java
new file mode 100644
index 000000000..a9f0f2824
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.glyptodon.guacamole.net.basic.websocket.jetty9;
+
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/**
+ * A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
+ *
+ * @author Michael Jumper
+ */
+public class BasicGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
+
+ @Override
+ public void configure(WebSocketServletFactory factory) {
+
+ // Register WebSocket implementation
+ factory.setCreator(new BasicGuacamoleWebSocketCreator());
+
+ }
+
+}
+
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
new file mode 100644
index 000000000..fee776442
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.glyptodon.guacamole.net.basic.websocket.jetty9;
+
+import 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.glyptodon.guacamole.GuacamoleClientException;
+import org.glyptodon.guacamole.GuacamoleException;
+import org.glyptodon.guacamole.io.GuacamoleReader;
+import org.glyptodon.guacamole.io.GuacamoleWriter;
+import org.glyptodon.guacamole.net.GuacamoleTunnel;
+import org.glyptodon.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.error("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("Error connecting WebSocket tunnel.", e);
+ closeConnection(session, e.getStatus());
+ return;
+ }
+
+ // Set accepted subprotocol
+ for (String subprotocol : session.getUpgradeRequest().getSubProtocols()) {
+ if ("guacamole".equals(subprotocol)) {
+ session.getUpgradeResponse().setAcceptedSubProtocol(subprotocol);
+ break;
+ }
+ }
+
+ // 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.warn("Client request rejected: {}", e.getMessage());
+ closeConnection(session, e.getStatus());
+ }
+ catch (GuacamoleException e) {
+ logger.error("Internal server error.", 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) {
+
+ GuacamoleWriter writer = tunnel.acquireWriter();
+
+ try {
+ // Write received message
+ writer.write(message.toCharArray());
+ }
+ catch (GuacamoleException e) {
+ logger.debug("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 WebSocket tunnel.", e);
+ }
+
+ }
+
+
+ @Override
+ public void onWebSocketClose(int statusCode, String reason) {
+
+ try {
+ if (tunnel != null)
+ tunnel.close();
+ }
+ catch (GuacamoleException e) {
+ logger.debug("Unable to close WebSocket tunnel.", e);
+ }
+
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java
new file mode 100644
index 000000000..b08a48058
--- /dev/null
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014 Glyptodon LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.glyptodon.guacamole.net.basic.websocket.jetty9;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpSession;
+import org.eclipse.jetty.websocket.api.UpgradeRequest;
+import org.glyptodon.guacamole.net.basic.TunnelRequest;
+
+/**
+ * Jetty 9 WebSocket-specific implementation of TunnelRequest.
+ *
+ * @author Michael Jumper
+ */
+public class WebSocketTunnelRequest implements TunnelRequest {
+
+ /**
+ * The wrapped UpgradeRequest.
+ */
+ private final UpgradeRequest request;
+
+ /**
+ * All parameters passed via HTTP to the WebSocket handshake.
+ */
+ private final Map 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.request = request;
+ this.handshakeParameters = request.getParameterMap();
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return (HttpSession) request.getSession();
+ }
+
+ @Override
+ public String getParameter(String name) {
+
+ // Pull list of values, if present
+ List values = getParameterValues(name);
+ if (values == null || values.isEmpty())
+ return null;
+
+ // Return first parameter value arbitrarily
+ return values.get(0);
+
+ }
+
+ @Override
+ public List getParameterValues(String name) {
+ return Arrays.asList(handshakeParameters.get(name));
+ }
+
+}
diff --git a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/package-info.java b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/package-info.java
similarity index 80%
rename from guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/package-info.java
rename to guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/package-info.java
index 37565aa30..dd41e5581 100644
--- a/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/package-info.java
+++ b/guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Glyptodon LLC
+ * 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
@@ -21,9 +21,8 @@
*/
/**
- * Jetty WebSocket tunnel implementation. The classes here require at least
- * Jetty 8, and may change significantly as there is no common WebSocket
- * API for Java yet.
+ * 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.glyptodon.guacamole.net.basic.websocket.jetty;
+package org.glyptodon.guacamole.net.basic.websocket.jetty9;