From e26a4c3b7da792a389b424c0a567ff766705ef2b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 10 Oct 2014 15:12:49 -0700 Subject: [PATCH] GUAC-878: Add support for Jetty 9.0.3 WebSocket. --- guacamole/pom.xml | 8 + .../websocket/WebSocketSupportLoader.java | 1 + .../GuacamoleWebSocketTunnelServlet.java | 181 -------------- .../BasicGuacamoleWebSocketCreator.java | 55 ++++ ...asicGuacamoleWebSocketTunnelListener.java} | 21 +- .../BasicGuacamoleWebSocketTunnelServlet.java | 44 ++++ .../GuacamoleWebSocketTunnelListener.java | 236 ++++++++++++++++++ .../jetty9/WebSocketTunnelRequest.java | 83 ++++++ .../{jetty => jetty9}/package-info.java | 9 +- 9 files changed, 441 insertions(+), 197 deletions(-) delete mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty/GuacamoleWebSocketTunnelServlet.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketCreator.java rename guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/{jetty/BasicGuacamoleWebSocketTunnelServlet.java => jetty9/BasicGuacamoleWebSocketTunnelListener.java} (66%) create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/BasicGuacamoleWebSocketTunnelServlet.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/GuacamoleWebSocketTunnelListener.java create mode 100644 guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/jetty9/WebSocketTunnelRequest.java rename guacamole/src/main/java/org/glyptodon/guacamole/net/basic/websocket/{jetty => jetty9}/package-info.java (80%) 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;