This is an automated email from the ASF dual-hosted git repository.

bonampak pushed a commit to branch feature/jakarta-jetty-upgrade
in repository https://gitbox.apache.org/repos/asf/knox.git

commit 0d14f0606b9513036bd68a22b39eb5ca140e234a
Author: bonampak <[email protected]>
AuthorDate: Mon Apr 27 18:17:43 2026 +0200

    KNOX-3238: fix WebsocketServerInitiatedPingTest and 
WebsocketEchoHTTPServiceRoleTest.testGatewayRewriteHttpEcho
---
 .../gateway/websockets/KnoxWebSocketCreator.java   | 18 ++++++--
 .../websockets/WebsocketBackendUrlTest.java        | 48 ++++++++++++++++++++++
 .../WebsocketServerInitiatedPingTest.java          | 44 +++++++++++++++-----
 3 files changed, 96 insertions(+), 14 deletions(-)

diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/websockets/KnoxWebSocketCreator.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/websockets/KnoxWebSocketCreator.java
index d7e65f658..a73db079c 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/websockets/KnoxWebSocketCreator.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/websockets/KnoxWebSocketCreator.java
@@ -242,8 +242,13 @@ public class KnoxWebSocketCreator implements 
WebSocketCreator {
                 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());
+                backend.append(serviceUrl.getPort());
+                // Only append a slash if getPath() doesn't already start with 
one
+                String serviceUrlPath = serviceUrl.getPath();
+                if (StringUtils.isNotEmpty(serviceUrlPath) && 
!serviceUrlPath.startsWith("/")) {
+                    backend.append('/');
+                }
+                backend.append(serviceUrlPath);
                 String pathSuffix = generateUrlSuffix(backend.toString(), 
pathService);
                 backend.append(pathSuffix);
             } else {
@@ -256,8 +261,13 @@ public class KnoxWebSocketCreator implements 
WebSocketCreator {
                 : "ws";
                 backend.append(protocol).append("://");
                 backend.append(serviceUrl.getHost()).append(':');
-                backend.append(serviceUrl.getPort()).append('/');
-                backend.append(serviceUrl.getPath());
+                backend.append(serviceUrl.getPort());
+                // Only append a slash if getPath() doesn't already start with 
one
+                String serviceUrlPath = serviceUrl.getPath();
+                if (StringUtils.isNotEmpty(serviceUrlPath) && 
!serviceUrlPath.startsWith("/")) {
+                    backend.append('/');
+                }
+                backend.append(serviceUrlPath);
             }
             /* in case we have query params */
             if(!StringUtils.isBlank(query)) {
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
index b90948269..ae871e2e2 100644
--- 
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
@@ -17,6 +17,13 @@
  */
 package org.apache.knox.gateway.websockets;
 
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.registry.ServiceDefEntry;
+import org.apache.knox.gateway.services.registry.ServiceDefinitionRegistry;
+import org.apache.knox.gateway.services.registry.ServiceRegistry;
+import org.easymock.EasyMock;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -88,4 +95,45 @@ public class WebsocketBackendUrlTest extends 
WebsocketEchoTestBase {
     String expectedBackendUrl = backendServerUri.toString() + pathContext;
     assertThat(backendUrl, is(expectedBackendUrl));
   }
+
+  @Test
+  public void testMatchedBackendURLDoubleSlashPrevention() throws Exception {
+    GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+    GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
+    ServiceRegistry serviceRegistry = 
EasyMock.createNiceMock(ServiceRegistry.class);
+    ServiceDefinitionRegistry serviceDefinitionRegistry = 
EasyMock.createNiceMock(ServiceDefinitionRegistry.class);
+    ServiceDefEntry serviceDefEntry = 
EasyMock.createNiceMock(ServiceDefEntry.class);
+
+    EasyMock.expect(services.getService(ServiceType.SERVICE_REGISTRY_SERVICE))
+    .andReturn(serviceRegistry).anyTimes();
+    
EasyMock.expect(services.getService(ServiceType.SERVICE_DEFINITION_REGISTRY))
+    .andReturn(serviceDefinitionRegistry).anyTimes();
+
+    
EasyMock.expect(serviceDefinitionRegistry.getMatchingService(EasyMock.anyString()))
+    .andReturn(serviceDefEntry).anyTimes();
+    EasyMock.expect(serviceDefEntry.getName())
+    .andReturn("WEBSOCKET").anyTimes();
+
+    // Simulate the backend topology returning an HTTP URL with a path that 
starts with a slash
+    EasyMock.expect(serviceRegistry.lookupServiceURL(EasyMock.anyString(), 
EasyMock.anyString()))
+    .andReturn("http://localhost:53170/ws";).anyTimes();
+
+    EasyMock.replay(config, services, serviceRegistry, 
serviceDefinitionRegistry, serviceDefEntry);
+
+    KnoxWebSocketCreator creator = new KnoxWebSocketCreator(config, services);
+    URI requestURI = new 
URI("ws://localhost:8443/gateway/websocket/123foo456bar/channels");
+    String backendURL = creator.getMatchedBackendURL(requestURI);
+
+    // Assert that the double slash is prevented
+    assertThat(backendURL, is("ws://localhost:53170/ws"));
+
+    // Let's also verify that HTTPS correctly maps to WSS without double 
slashes
+    EasyMock.reset(serviceRegistry);
+    EasyMock.expect(serviceRegistry.lookupServiceURL(EasyMock.anyString(), 
EasyMock.anyString()))
+    .andReturn("https://localhost:53170/ws";).anyTimes();
+    EasyMock.replay(serviceRegistry);
+
+    backendURL = creator.getMatchedBackendURL(requestURI);
+    assertThat(backendURL, is("wss://localhost:53170/ws"));
+  }
 }
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketServerInitiatedPingTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketServerInitiatedPingTest.java
index da7078497..740a19067 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketServerInitiatedPingTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/websockets/WebsocketServerInitiatedPingTest.java
@@ -18,7 +18,9 @@
 package org.apache.knox.gateway.websockets;
 
 import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.websocket.api.Frame;
 import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.core.OpCode;
 import org.eclipse.jetty.websocket.server.ServerUpgradeRequest;
 import org.eclipse.jetty.websocket.server.ServerUpgradeResponse;
 import org.eclipse.jetty.websocket.server.ServerWebSocketContainer;
@@ -32,6 +34,7 @@ import jakarta.websocket.WebSocketContainer;
 import java.net.URI;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
 import static org.hamcrest.CoreMatchers.is;
@@ -59,13 +62,15 @@ import static org.hamcrest.MatcherAssert.assertThat;
  */
 public class WebsocketServerInitiatedPingTest extends WebsocketEchoTestBase {
 
+  private static WebsocketServerInitiatedPingHandler pingHandler;
   public WebsocketServerInitiatedPingTest() {
     super();
   }
 
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
-    handler = new WebsocketServerInitiatedPingHandler();
+    pingHandler = new WebsocketServerInitiatedPingHandler();
+    handler = pingHandler;
     WebsocketEchoTestBase.setUpBeforeClass();
     WebsocketEchoTestBase.startServers("ws");
   }
@@ -83,13 +88,15 @@ public class WebsocketServerInitiatedPingTest extends 
WebsocketEchoTestBase {
     WebSocketContainer container = ContainerProvider.getWebSocketContainer();
 
     WebsocketClient client = new WebsocketClient();
-    container.connectToServer(client,
-            new URI(serverUri.toString() + 
"gateway/websocket/123foo456bar/channels"));
-
-    //session.getBasicRemote().sendText("Echo");
-    client.messageQueue.awaitMessages(1, 10000, TimeUnit.MILLISECONDS);
-
-    assertThat(client.messageQueue.get(0), is("PingPong"));
+    try (jakarta.websocket.Session session = container.connectToServer(client,
+    new URI(serverUri.toString() + 
"gateway/websocket/123foo456bar/channels"))) {
+      assertThat(session.isOpen(), is(true));
+      //session.getBasicRemote().sendText("Echo");
+      // Wait for the backend server to receive the automatic PONG from Knox's 
JSR-356 container
+      String pongPayload = pingHandler.socket.pongFuture.get(10000, 
TimeUnit.MILLISECONDS);
+
+      assertThat(pongPayload, is("PingPong"));
+    }
   }
 
   /**
@@ -97,7 +104,7 @@ public class WebsocketServerInitiatedPingTest extends 
WebsocketEchoTestBase {
    *
    */
   private static class WebsocketServerInitiatedPingHandler extends 
AbstractWebSocketHandler {
-
+    public final ServerInitiatingPingSocket socket = new 
ServerInitiatingPingSocket();
     @Override
     protected void configure(ServerWebSocketContainer container) {
       container.setMaxTextMessageSize(2 * 1024 * 1024);
@@ -105,7 +112,7 @@ public class WebsocketServerInitiatedPingTest extends 
WebsocketEchoTestBase {
 
     @Override
     public Object createWebSocket(ServerUpgradeRequest req, 
ServerUpgradeResponse resp, Callback callback) {
-      return new ServerInitiatingPingSocket();
+      return socket;
     }
   }
 
@@ -113,6 +120,7 @@ public class WebsocketServerInitiatedPingTest extends 
WebsocketEchoTestBase {
    * A simple socket initiating message on connect
    */
   public static class ServerInitiatingPingSocket extends 
Session.Listener.AbstractAutoDemanding {
+    public final CompletableFuture<String> pongFuture = new 
CompletableFuture<>();
 
     @Override
     public void onWebSocketError(Throwable cause) {
@@ -138,5 +146,21 @@ public class WebsocketServerInitiatedPingTest extends 
WebsocketEchoTestBase {
       // BatchMode and manual flushing are handled automatically by the Jetty 
engine.
       session.sendPing(binaryMessage, 
org.eclipse.jetty.websocket.api.Callback.NOOP);
     }
+
+    @Override
+    public void onWebSocketFrame(Frame frame, 
org.eclipse.jetty.websocket.api.Callback callback) {
+      // Intercept PONG frames returning from Knox
+      if (frame.getOpCode() == OpCode.PONG) {
+        ByteBuffer payload = frame.getPayload();
+        if (payload != null) {
+          byte[] bytes = new byte[payload.remaining()];
+          payload.get(bytes);
+          pongFuture.complete(new String(bytes, StandardCharsets.UTF_8));
+        } else {
+          pongFuture.complete("");
+        }
+      }
+      super.onWebSocketFrame(frame, callback);
+    }
   }
 }

Reply via email to