KNOX-1996: Adding changes to remove extra / while generating backedn … (#142)
* KNOX-1996: Adding changes to remove extra / while generating backedn url. Additionally, proxy messages to websocket even if service uri has http/https protocol
* KNOX-1996: Code review fixes
* KNOX-1996: More code review fixes. Creating base class for WebsocketEcho and WebsocketEcho1 tests
* KNOX-1996: Code review fixes - Renaming test case and adding new test case for backedn url validation
* KNOX-1996: Code review fixes in test case
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/websockets/GatewayWebsocketHandler.java b/gateway-server/src/main/java/org/apache/knox/gateway/websockets/GatewayWebsocketHandler.java
index 138a3fe..7d8ce36 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/websockets/GatewayWebsocketHandler.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/websockets/GatewayWebsocketHandler.java
@@ -114,7 +114,8 @@
final String path = requestURI.getPath();
/* URL used to connect to websocket backend */
- final String backendURL = getMatchedBackendURL(path);
+ final String backendURL = getMatchedBackendURL(path, requestURI);
+ LOG.debugLog("Generated backend URL for websocket connection: " + backendURL);
/* Upgrade happens here */
return new ProxyWebSocketAdapter(URI.create(backendURL), pool, getClientEndpointConfig(req));
@@ -149,7 +150,7 @@
* ws://{host}:{port} which might or might not be right.
* @return Websocket backend url
*/
- private synchronized String getMatchedBackendURL(final String path) {
+ protected synchronized String getMatchedBackendURL(final String path, URI requestURI) {
final ServiceRegistry serviceRegistryService = services
.getService(ServiceType.SERVICE_REGISTRY_SERVICE);
@@ -173,12 +174,28 @@
/* URL used to connect to websocket backend */
String backendURL = urlFromServiceDefinition(serviceRegistryService, entry, path);
+ LOG.debugLog("Url obtained from services definition: " + backendURL);
StringBuilder backend = new StringBuilder();
try {
-
- /* if we do not find websocket URL we default to HTTP */
- if (!StringUtils.containsAny(backendURL, WEBSOCKET_PROTOCOL_STRING, SECURE_WEBSOCKET_PROTOCOL_STRING)) {
+ if (StringUtils.containsAny(backendURL, WEBSOCKET_PROTOCOL_STRING, SECURE_WEBSOCKET_PROTOCOL_STRING)) {
+ LOG.debugLog("ws or wss protocol found in service url");
+ URI serviceUri = new URI(backendURL);
+ backend.append(serviceUri);
+ String pathSuffix = generateUrlSuffix(backend.toString(), pathService);
+ backend.append(pathSuffix);
+ } else if (StringUtils.containsAny(requestURI.toString(), WEBSOCKET_PROTOCOL_STRING, SECURE_WEBSOCKET_PROTOCOL_STRING)) {
+ LOG.debugLog("ws or wss protocol found in request url");
+ URL serviceUrl = new URL(backendURL);
+ final String protocol = (serviceUrl.getProtocol().equals("https")) ? "wss" : "ws";
+ backend.append(protocol).append("://");
+ backend.append(serviceUrl.getHost()).append(':');
+ backend.append(serviceUrl.getPort()).append('/');
+ backend.append(serviceUrl.getPath());
+ String pathSuffix = generateUrlSuffix(backend.toString(), pathService);
+ backend.append(pathSuffix);
+ } else {
+ LOG.debugLog("ws or wss protocol not found in service url or request url");
URL serviceUrl = new URL(backendURL);
/* Use http host:port if ws url not configured */
@@ -190,16 +207,7 @@
backend.append(serviceUrl.getPort()).append('/');
backend.append(serviceUrl.getPath());
}
- else {
- URI serviceUri = new URI(backendURL);
- backend.append(serviceUri);
- /* Avoid Zeppelin Regression - as this would require ambari changes and break current knox websocket use case*/
- if (!StringUtils.endsWith(backend.toString(), "/ws") && pathService.length > 0 && pathService[1] != null) {
- backend.append(pathService[1]);
- }
- }
backendURL = backend.toString();
-
} catch (MalformedURLException e){
LOG.badUrlError(e);
throw new RuntimeException(e.toString());
@@ -224,4 +232,17 @@
return serviceRegistry.lookupServiceURL(contexts[2],
entry.getName().toUpperCase(Locale.ROOT));
}
+
+ private String generateUrlSuffix(String backendPart, String[] pathService) {
+ /* Avoid Zeppelin Regression - as this would require ambari changes and break current knox websocket use case*/
+ if (!StringUtils.endsWith(backendPart, "/ws") && pathService.length > 0
+ && pathService[1] != null) {
+ String newPathSuffix = pathService[1];
+ if ((backendPart.endsWith("/")) && (pathService[1].startsWith("/"))) {
+ newPathSuffix = pathService[1].substring(1);
+ }
+ return newPathSuffix;
+ }
+ return "";
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/websockets/WebsocketLogMessages.java b/gateway-server/src/main/java/org/apache/knox/gateway/websockets/WebsocketLogMessages.java
index 9681d9d..fc8484e 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/websockets/WebsocketLogMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/websockets/WebsocketLogMessages.java
@@ -56,4 +56,8 @@
text = "Websocket connection to backend server {0} closed")
void onConnectionClose(String backend);
+ @Message(level = MessageLevel.DEBUG,
+ text = "{0}")
+ void debugLog(String message);
+
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketBackendUrlTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketBackendUrlTest.java
new file mode 100644
index 0000000..492f07e
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketBackendUrlTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.websockets;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.Locale;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * A basic test that attempts to test the backend url generated by gateway proxy.
+ */
+public class WebsocketBackendUrlTest extends WebsocketEchoTestBase {
+ private static Server backendServer;
+ public static URI backendServerUri;
+
+ public WebsocketBackendUrlTest() {
+ super();
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ WebsocketEchoTestBase.setUpBeforeClass();
+
+ backendServer = new Server();
+ ServerConnector connector = new ServerConnector(backendServer);
+ backendServer.addConnector(connector);
+
+ String host = connector.getHost();
+ if (host == null) {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ serverUri = new URI(String.format(Locale.ROOT, "ws://%s:%d/", host, port));
+ backendServerUri = new URI(String.format(Locale.ROOT, "ws://%s:%d/testpart/", host, port));
+ WebsocketEchoTestBase.setupGatewayConfig(backendServerUri.toString());
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() {
+ WebsocketEchoTestBase.cleanupFiles();
+ }
+
+ /*
+ * Test url generated for websocket backend connection
+ */
+ @Test
+ public void testWebsocketBackendUrl() throws Exception {
+ URI requestURI = new URI(serverUri.toString() + "gateway/websocket/123foo456bar/channels");
+ final String path = requestURI.getPath();
+ GatewayWebsocketHandler gwh = new GatewayWebsocketHandler(gatewayConfig, services);
+ String backendUrl = gwh.getMatchedBackendURL(path, requestURI);
+ String expectedBackendUrl = backendServerUri.toString() + "channels";
+ assertThat(backendUrl, is(expectedBackendUrl));
+ }
+}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoHTTPServiceRoleTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoHTTPServiceRoleTest.java
new file mode 100644
index 0000000..e66015a
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoHTTPServiceRoleTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.websockets;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * A basic test that attempts to proxy websocket connections through Knox
+ * gateway.
+ * <p>
+ * The way the test is set up is as follows: <br>
+ * <ul>
+ * <li>A Mock Websocket server is setup which simply echos the responses sent by
+ * client.
+ * <li>Knox Gateway is set up with websocket handler
+ * {@link GatewayWebsocketHandler} that can proxy the requests.
+ * <li>Appropriate Topology and service definition files are set up with the
+ * address of the Websocket server.
+ * <li>A mock client is setup to connect to gateway.
+ * </ul>
+ *
+ * The test is to confirm whether the message is sent all the way to the backend
+ * Websocket server through Knox and back when backend service URI has http
+ * protocol defined in service definition
+ *
+ * @since 0.10
+ */
+public class WebsocketEchoHTTPServiceRoleTest extends WebsocketEchoTestBase {
+
+ public WebsocketEchoHTTPServiceRoleTest() {
+ super();
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ WebsocketEchoTestBase.setUpBeforeClass();
+ WebsocketEchoTestBase.startServers("http");
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() {
+ WebsocketEchoTestBase.tearDownAfterClass();
+ }
+
+ /*
+ * Test websocket rewrite rules proxying through gateway.
+ */
+ @Test
+ public void testGatewayRewriteHttpEcho() throws Exception {
+ WebSocketContainer container = ContainerProvider.getWebSocketContainer();
+
+ WebsocketClient client = new WebsocketClient();
+ Session session = container.connectToServer(client,
+ new URI(serverUri.toString() + "gateway/websocket/123foo456bar/channels"));
+
+ session.getBasicRemote().sendText("Echo");
+ client.messageQueue.awaitMessages(1, 1000, TimeUnit.MILLISECONDS);
+
+ assertThat(client.messageQueue.get(0), is("Echo"));
+ }
+}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTest.java
index 58c09c9..7a08b9e 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTest.java
@@ -17,25 +17,6 @@
*/
package org.apache.knox.gateway.websockets;
-import com.mycila.xmltool.XMLDoc;
-import com.mycila.xmltool.XMLTag;
-import org.apache.commons.io.FileUtils;
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
-import org.apache.knox.gateway.deploy.DeploymentFactory;
-import org.apache.knox.gateway.services.DefaultGatewayServices;
-import org.apache.knox.gateway.services.ServiceType;
-import org.apache.knox.gateway.services.GatewayServices;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.topology.TopologyService;
-import org.apache.knox.gateway.topology.TopologyEvent;
-import org.apache.knox.gateway.topology.TopologyListener;
-import org.apache.knox.test.TestUtils;
-import org.easymock.EasyMock;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.ContextHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -43,28 +24,9 @@
import javax.websocket.ContainerProvider;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
import java.net.URI;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
import java.util.concurrent.TimeUnit;
-import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS;
-import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_SIGNING_KEYSTORE_TYPE;
-import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_SIGNING_KEY_PASSPHRASE_ALIAS;
-import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_IDENTITY_KEYSTORE_PASSWORD_ALIAS;
-import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_IDENTITY_KEYSTORE_TYPE;
-import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_IDENTITY_KEY_PASSPHRASE_ALIAS;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -88,40 +50,7 @@
*
* @since 0.10
*/
-public class WebsocketEchoTest {
- private static final String TEST_KEY_ALIAS = "test-identity";
-
- /**
- * Simulate backend websocket
- */
- private static Server backendServer;
- /**
- * URI for backend websocket server
- */
- private static URI backendServerUri;
-
- /**
- * Mock Gateway server
- */
- private static Server gatewayServer;
-
- /**
- * Mock gateway config
- */
- private static GatewayConfig gatewayConfig;
-
- private static GatewayServices services;
-
- /**
- * URI for gateway server
- */
- private static URI serverUri;
-
- private static File topoDir;
- private static Path dataDir;
- private static Path securityDir;
- private static Path keystoresDir;
- private static Path keystoreFile;
+public class WebsocketEchoTest extends WebsocketEchoTestBase {
public WebsocketEchoTest() {
super();
@@ -129,27 +58,13 @@
@BeforeClass
public static void setUpBeforeClass() throws Exception {
- topoDir = createDir();
- dataDir = Paths.get(topoDir.getAbsolutePath(), "data").toAbsolutePath();
- securityDir = dataDir.resolve("security");
- keystoresDir = securityDir.resolve("keystores");
- keystoreFile = keystoresDir.resolve("tls.jks");
-
- startWebsocketServer();
- startGatewayServer();
+ WebsocketEchoTestBase.setUpBeforeClass();
+ WebsocketEchoTestBase.startServers("ws");
}
@AfterClass
public static void tearDownAfterClass() {
- try {
- gatewayServer.stop();
- backendServer.stop();
- } catch (final Exception e) {
- e.printStackTrace(System.err);
- }
-
- /* Cleanup the created files */
- FileUtils.deleteQuietly(topoDir);
+ WebsocketEchoTestBase.tearDownAfterClass();
}
/*
@@ -200,242 +115,4 @@
assertThat(client.messageQueue.get(0), is("Echo"));
}
-
- /**
- * Start Mock Websocket server that acts as backend.
- * @throws Exception exception on websocket server start
- */
- private static void startWebsocketServer() throws Exception {
-
- backendServer = new Server();
- ServerConnector connector = new ServerConnector(backendServer);
- backendServer.addConnector(connector);
-
- final WebsocketEchoHandler handler = new WebsocketEchoHandler();
-
- ContextHandler context = new ContextHandler();
- context.setContextPath("/");
- context.setHandler(handler);
- backendServer.setHandler(context);
-
- // Start Server
- backendServer.start();
-
- String host = connector.getHost();
- if (host == null) {
- host = "localhost";
- }
- int port = connector.getLocalPort();
- backendServerUri = new URI(String.format(Locale.ROOT, "ws://%s:%d/ws", host, port));
- }
-
- /**
- * Start Gateway Server.
- * @throws Exception exception on server start
- */
- private static void startGatewayServer() throws Exception {
- gatewayServer = new Server();
- final ServerConnector connector = new ServerConnector(gatewayServer);
- gatewayServer.addConnector(connector);
-
- /* workaround so we can add our handler later at runtime */
- HandlerCollection handlers = new HandlerCollection(true);
-
- /* add some initial handlers */
- ContextHandler context = new ContextHandler();
- context.setContextPath("/");
- handlers.addHandler(context);
-
- gatewayServer.setHandler(handlers);
-
- // Start Server
- gatewayServer.start();
-
- String host = connector.getHost();
- if (host == null) {
- host = "localhost";
- }
- int port = connector.getLocalPort();
- serverUri = new URI(String.format(Locale.ROOT, "ws://%s:%d/", host, port));
-
- /* Setup websocket handler */
- setupGatewayConfig(backendServerUri.toString());
-
- final GatewayWebsocketHandler gatewayWebsocketHandler = new GatewayWebsocketHandler(
- gatewayConfig, services);
- handlers.addHandler(gatewayWebsocketHandler);
- gatewayWebsocketHandler.start();
- }
-
- /**
- * Initialize the configs and components required for this test.
- * @param backend topology to use
- * @throws IOException exception on setting up the gateway
- */
- private static void setupGatewayConfig(final String backend) throws IOException {
- services = new DefaultGatewayServices();
-
- URL serviceUrl = ClassLoader.getSystemResource("websocket-services");
-
- final File descriptor = new File(topoDir, "websocket.xml");
- try(OutputStream stream = Files.newOutputStream(descriptor.toPath())) {
- createKnoxTopology(backend).toStream(stream);
- }
-
- final TestTopologyListener topoListener = new TestTopologyListener();
-
- final Map<String, String> options = new HashMap<>();
- options.put("persist-master", "false");
- options.put("master", "password");
-
- gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
- EasyMock.expect(gatewayConfig.getGatewayTopologyDir())
- .andReturn(topoDir.toString()).anyTimes();
-
- EasyMock.expect(gatewayConfig.getGatewayProvidersConfigDir())
- .andReturn(topoDir.getAbsolutePath() + "/shared-providers").anyTimes();
-
- EasyMock.expect(gatewayConfig.getGatewayDescriptorsDir())
- .andReturn(topoDir.getAbsolutePath() + "/descriptors").anyTimes();
-
- EasyMock.expect(gatewayConfig.getGatewayServicesDir())
- .andReturn(serviceUrl.getFile()).anyTimes();
-
- EasyMock.expect(gatewayConfig.getEphemeralDHKeySize()).andReturn("2048")
- .anyTimes();
-
- /* Websocket configs */
- EasyMock.expect(gatewayConfig.isWebsocketEnabled()).andReturn(true)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketMaxTextMessageSize())
- .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketMaxBinaryMessageSize())
- .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_BINARY_MESSAGE_SIZE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketMaxTextMessageBufferSize())
- .andReturn(
- GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_TEXT_MESSAGE_BUFFER_SIZE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketMaxBinaryMessageBufferSize())
- .andReturn(
- GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_BINARY_MESSAGE_BUFFER_SIZE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketInputBufferSize())
- .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_INPUT_BUFFER_SIZE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketAsyncWriteTimeout())
- .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_ASYNC_WRITE_TIMEOUT)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getWebsocketIdleTimeout())
- .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_IDLE_TIMEOUT).anyTimes();
-
- EasyMock.expect(gatewayConfig.getRemoteRegistryConfigurationNames())
- .andReturn(Collections.emptyList())
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getGatewayDataDir())
- .andReturn(dataDir.toString())
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getGatewaySecurityDir())
- .andReturn(securityDir.toString())
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getGatewayKeystoreDir())
- .andReturn(keystoresDir.toString())
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getIdentityKeystorePath())
- .andReturn(keystoreFile.toString())
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getIdentityKeystoreType())
- .andReturn(DEFAULT_IDENTITY_KEYSTORE_TYPE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getIdentityKeystorePasswordAlias())
- .andReturn(DEFAULT_IDENTITY_KEYSTORE_PASSWORD_ALIAS)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getIdentityKeyAlias())
- .andReturn(TEST_KEY_ALIAS)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getIdentityKeyPassphraseAlias())
- .andReturn(DEFAULT_IDENTITY_KEY_PASSPHRASE_ALIAS)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getSigningKeystorePasswordAlias())
- .andReturn(DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getSigningKeyPassphraseAlias())
- .andReturn(DEFAULT_SIGNING_KEY_PASSPHRASE_ALIAS)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getSigningKeystorePath())
- .andReturn(keystoreFile.toString())
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getSigningKeystoreType())
- .andReturn(DEFAULT_SIGNING_KEYSTORE_TYPE)
- .anyTimes();
-
- EasyMock.expect(gatewayConfig.getSigningKeyAlias())
- .andReturn(TEST_KEY_ALIAS)
- .anyTimes();
-
-
- EasyMock.replay(gatewayConfig);
-
- try {
- services.init(gatewayConfig, options);
- } catch (ServiceLifecycleException e) {
- e.printStackTrace();
- }
-
- DeploymentFactory.setGatewayServices(services);
- final TopologyService monitor = services
- .getService(ServiceType.TOPOLOGY_SERVICE);
- monitor.addTopologyChangeListener(topoListener);
- monitor.reloadTopologies();
- }
-
- private static File createDir() throws IOException {
- return TestUtils
- .createTempDir(WebsocketEchoTest.class.getSimpleName() + "-");
- }
-
- private static XMLTag createKnoxTopology(final String backend) {
- return XMLDoc.newDocument(true).addRoot("topology").addTag("service")
- .addTag("role").addText("WEBSOCKET").addTag("url").addText(backend)
- .gotoParent().gotoRoot();
- }
-
- private static class TestTopologyListener implements TopologyListener {
- public List<List<TopologyEvent>> events = new ArrayList<>();
-
- @Override
- public void handleTopologyEvent(List<TopologyEvent> events) {
- this.events.add(events);
-
- synchronized (this) {
- for (TopologyEvent event : events) {
- if (!event.getType().equals(TopologyEvent.Type.DELETED)) {
- /* for this test we only care about this part */
- DeploymentFactory.createDeployment(gatewayConfig,
- event.getTopology());
- }
- }
- }
- }
- }
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTestBase.java b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTestBase.java
new file mode 100644
index 0000000..a62d330
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketEchoTestBase.java
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.websockets;
+
+import com.mycila.xmltool.XMLDoc;
+import com.mycila.xmltool.XMLTag;
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
+import org.apache.knox.gateway.deploy.DeploymentFactory;
+import org.apache.knox.gateway.services.DefaultGatewayServices;
+import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.topology.TopologyService;
+import org.apache.knox.gateway.topology.TopologyEvent;
+import org.apache.knox.gateway.topology.TopologyListener;
+import org.apache.knox.test.TestUtils;
+import org.easymock.EasyMock;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS;
+import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_SIGNING_KEYSTORE_TYPE;
+import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_SIGNING_KEY_PASSPHRASE_ALIAS;
+import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_IDENTITY_KEYSTORE_PASSWORD_ALIAS;
+import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_IDENTITY_KEYSTORE_TYPE;
+import static org.apache.knox.gateway.config.GatewayConfig.DEFAULT_IDENTITY_KEY_PASSPHRASE_ALIAS;
+
+/**
+ * Base class for tests that attempt to proxy websocket connections through Knox
+ * gateway. It setups a websocket socket connection that simply echoes data back.
+ *
+ */
+public class WebsocketEchoTestBase {
+ private static final String TEST_KEY_ALIAS = "test-identity";
+
+ /**
+ * Simulate backend websocket
+ */
+ private static Server backendServer;
+ /**
+ * URI for backend websocket server
+ */
+ public static URI backendServerUri;
+
+ /**
+ * Mock Gateway server
+ */
+ private static Server gatewayServer;
+
+ /**
+ * Mock gateway config
+ */
+ public static GatewayConfig gatewayConfig;
+
+ public static GatewayServices services;
+
+ /**
+ * URI for gateway server
+ */
+ public static URI serverUri;
+
+ private static File topoDir;
+ private static Path dataDir;
+ private static Path securityDir;
+ private static Path keystoresDir;
+ private static Path keystoreFile;
+
+ public WebsocketEchoTestBase() {
+ super();
+ }
+
+ public static void setUpBeforeClass() throws Exception {
+ topoDir = createDir();
+ dataDir = Paths.get(topoDir.getAbsolutePath(), "data").toAbsolutePath();
+ securityDir = dataDir.resolve("security");
+ keystoresDir = securityDir.resolve("keystores");
+ keystoreFile = keystoresDir.resolve("tls.jks");
+ }
+
+ public static void startServers(String type) throws Exception {
+ startWebsocketServer(type);
+ startGatewayServer();
+ }
+
+ public static void tearDownAfterClass() {
+ try {
+ gatewayServer.stop();
+ backendServer.stop();
+ } catch (final Exception e) {
+ e.printStackTrace(System.err);
+ }
+
+ cleanupFiles();
+ }
+
+ public static void cleanupFiles() {
+ /* Cleanup the created files */
+ FileUtils.deleteQuietly(topoDir);
+ }
+
+ /**
+ * Start Mock Websocket server that acts as backend.
+ * @throws Exception exception on websocket server start
+ */
+ private static void startWebsocketServer(String type) throws Exception {
+
+ backendServer = new Server();
+ ServerConnector connector = new ServerConnector(backendServer);
+ backendServer.addConnector(connector);
+
+ final WebsocketEchoHandler handler = new WebsocketEchoHandler();
+
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/");
+ context.setHandler(handler);
+ backendServer.setHandler(context);
+
+ // Start Server
+ backendServer.start();
+
+ String host = connector.getHost();
+ if (host == null) {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ if ("http".equals(type)) {
+ backendServerUri = new URI(String.format(Locale.ROOT, "http://%s:%d/ws", host, port));
+ } else {
+ backendServerUri = new URI(String.format(Locale.ROOT, "ws://%s:%d/ws", host, port));
+ }
+ }
+
+ /**
+ * Start Gateway Server.
+ * @throws Exception exception on server start
+ */
+ private static void startGatewayServer() throws Exception {
+ gatewayServer = new Server();
+ final ServerConnector connector = new ServerConnector(gatewayServer);
+ gatewayServer.addConnector(connector);
+
+ /* workaround so we can add our handler later at runtime */
+ HandlerCollection handlers = new HandlerCollection(true);
+
+ /* add some initial handlers */
+ ContextHandler context = new ContextHandler();
+ context.setContextPath("/");
+ handlers.addHandler(context);
+
+ gatewayServer.setHandler(handlers);
+
+ // Start Server
+ gatewayServer.start();
+
+ String host = connector.getHost();
+ if (host == null) {
+ host = "localhost";
+ }
+ int port = connector.getLocalPort();
+ serverUri = new URI(String.format(Locale.ROOT, "ws://%s:%d/", host, port));
+
+ /* Setup websocket handler */
+ setupGatewayConfig(backendServerUri.toString());
+
+ final GatewayWebsocketHandler gatewayWebsocketHandler = new GatewayWebsocketHandler(
+ gatewayConfig, services);
+ handlers.addHandler(gatewayWebsocketHandler);
+ gatewayWebsocketHandler.start();
+ }
+
+ /**
+ * Initialize the configs and components required for this test.
+ * @param backend topology to use
+ * @throws IOException exception on setting up the gateway
+ */
+ public static void setupGatewayConfig(final String backend) throws IOException {
+ services = new DefaultGatewayServices();
+
+ URL serviceUrl = ClassLoader.getSystemResource("websocket-services");
+
+ final File descriptor = new File(topoDir, "websocket.xml");
+ try(OutputStream stream = Files.newOutputStream(descriptor.toPath())) {
+ createKnoxTopology(backend).toStream(stream);
+ }
+
+ final TestTopologyListener topoListener = new TestTopologyListener();
+
+ final Map<String, String> options = new HashMap<>();
+ options.put("persist-master", "false");
+ options.put("master", "password");
+
+ gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
+ EasyMock.expect(gatewayConfig.getGatewayTopologyDir())
+ .andReturn(topoDir.toString()).anyTimes();
+
+ EasyMock.expect(gatewayConfig.getGatewayProvidersConfigDir())
+ .andReturn(topoDir.getAbsolutePath() + "/shared-providers").anyTimes();
+
+ EasyMock.expect(gatewayConfig.getGatewayDescriptorsDir())
+ .andReturn(topoDir.getAbsolutePath() + "/descriptors").anyTimes();
+
+ EasyMock.expect(gatewayConfig.getGatewayServicesDir())
+ .andReturn(serviceUrl.getFile()).anyTimes();
+
+ EasyMock.expect(gatewayConfig.getEphemeralDHKeySize()).andReturn("2048")
+ .anyTimes();
+
+ /* Websocket configs */
+ EasyMock.expect(gatewayConfig.isWebsocketEnabled()).andReturn(true)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketMaxTextMessageSize())
+ .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketMaxBinaryMessageSize())
+ .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_BINARY_MESSAGE_SIZE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketMaxTextMessageBufferSize())
+ .andReturn(
+ GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_TEXT_MESSAGE_BUFFER_SIZE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketMaxBinaryMessageBufferSize())
+ .andReturn(
+ GatewayConfigImpl.DEFAULT_WEBSOCKET_MAX_BINARY_MESSAGE_BUFFER_SIZE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketInputBufferSize())
+ .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_INPUT_BUFFER_SIZE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketAsyncWriteTimeout())
+ .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_ASYNC_WRITE_TIMEOUT)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getWebsocketIdleTimeout())
+ .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_IDLE_TIMEOUT).anyTimes();
+
+ EasyMock.expect(gatewayConfig.getRemoteRegistryConfigurationNames())
+ .andReturn(Collections.emptyList())
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getGatewayDataDir())
+ .andReturn(dataDir.toString())
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getGatewaySecurityDir())
+ .andReturn(securityDir.toString())
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getGatewayKeystoreDir())
+ .andReturn(keystoresDir.toString())
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getIdentityKeystorePath())
+ .andReturn(keystoreFile.toString())
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getIdentityKeystoreType())
+ .andReturn(DEFAULT_IDENTITY_KEYSTORE_TYPE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getIdentityKeystorePasswordAlias())
+ .andReturn(DEFAULT_IDENTITY_KEYSTORE_PASSWORD_ALIAS)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getIdentityKeyAlias())
+ .andReturn(TEST_KEY_ALIAS)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getIdentityKeyPassphraseAlias())
+ .andReturn(DEFAULT_IDENTITY_KEY_PASSPHRASE_ALIAS)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getSigningKeystorePasswordAlias())
+ .andReturn(DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getSigningKeyPassphraseAlias())
+ .andReturn(DEFAULT_SIGNING_KEY_PASSPHRASE_ALIAS)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getSigningKeystorePath())
+ .andReturn(keystoreFile.toString())
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getSigningKeystoreType())
+ .andReturn(DEFAULT_SIGNING_KEYSTORE_TYPE)
+ .anyTimes();
+
+ EasyMock.expect(gatewayConfig.getSigningKeyAlias())
+ .andReturn(TEST_KEY_ALIAS)
+ .anyTimes();
+
+
+ EasyMock.replay(gatewayConfig);
+
+ try {
+ services.init(gatewayConfig, options);
+ } catch (ServiceLifecycleException e) {
+ e.printStackTrace();
+ }
+
+ DeploymentFactory.setGatewayServices(services);
+ final TopologyService monitor = services
+ .getService(ServiceType.TOPOLOGY_SERVICE);
+ monitor.addTopologyChangeListener(topoListener);
+ monitor.reloadTopologies();
+ }
+
+ private static File createDir() throws IOException {
+ return TestUtils
+ .createTempDir(WebsocketEchoTest.class.getSimpleName() + "-");
+ }
+
+ private static XMLTag createKnoxTopology(final String backend) {
+ return XMLDoc.newDocument(true).addRoot("topology").addTag("service")
+ .addTag("role").addText("WEBSOCKET").addTag("url").addText(backend)
+ .gotoParent().gotoRoot();
+ }
+
+ private static class TestTopologyListener implements TopologyListener {
+ public List<List<TopologyEvent>> events = new ArrayList<>();
+
+ @Override
+ public void handleTopologyEvent(List<TopologyEvent> events) {
+ this.events.add(events);
+
+ synchronized (this) {
+ for (TopologyEvent event : events) {
+ if (!event.getType().equals(TopologyEvent.Type.DELETED)) {
+ /* for this test we only care about this part */
+ DeploymentFactory.createDeployment(gatewayConfig,
+ event.getTopology());
+ }
+ }
+ }
+ }
+ }
+}