diff --git a/webclient/websocket/src/main/java/io/helidon/webclient/websocket/ClientWsConnection.java b/webclient/websocket/src/main/java/io/helidon/webclient/websocket/ClientWsConnection.java index 35f324ee8a8..6b7c57827a8 100644 --- a/webclient/websocket/src/main/java/io/helidon/webclient/websocket/ClientWsConnection.java +++ b/webclient/websocket/src/main/java/io/helidon/webclient/websocket/ClientWsConnection.java @@ -175,12 +175,18 @@ private ClientWsConnection send(ClientWsFrame frame) { opCodeFull |= opCode.code(); sendBuffer.write(opCodeFull); - long payloadLength = frame.payloadLength(); - if (frame.payloadLength() < 126) { - // this is a masked frame (all client frames MUST be masked) - payloadLength = payloadLength | 0b10000000; - sendBuffer.write((int) payloadLength); - // TODO finish other options (payload longer than 126 bytes) + long length = frame.payloadLength(); + if (length < 126) { + sendBuffer.write((int) length | 0b10000000); + } else if (length < 1 << 16) { + sendBuffer.write(126 | 0b10000000); + sendBuffer.write((int) (length >>> 8)); + sendBuffer.write((int) (length & 0xFF)); + } else { + sendBuffer.write(127 | 0b10000000); + for (int i = 56; i >= 0; i -= 8){ + sendBuffer.write((int) (length >>> i) & 0xFF); + } } // write masking key diff --git a/webserver/tests/websocket/pom.xml b/webserver/tests/websocket/pom.xml index 05e183a8539..450dbd62e13 100644 --- a/webserver/tests/websocket/pom.xml +++ b/webserver/tests/websocket/pom.xml @@ -38,6 +38,11 @@ helidon-webserver-testing-junit5 test + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5-websocket + test + org.junit.jupiter junit-jupiter-api diff --git a/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketClientTest.java b/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketClientTest.java new file mode 100644 index 00000000000..2c387d25f36 --- /dev/null +++ b/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketClientTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed 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 io.helidon.webserver.tests.websocket; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import io.helidon.webclient.websocket.WsClient; +import io.helidon.webserver.Router; +import io.helidon.webserver.WebServerConfig; +import io.helidon.webserver.testing.junit5.ServerTest; +import io.helidon.webserver.testing.junit5.SetUpRoute; +import io.helidon.webserver.testing.junit5.SetUpServer; +import io.helidon.webserver.websocket.WsConfig; +import io.helidon.webserver.websocket.WsRouting; +import io.helidon.websocket.WsCloseCodes; +import io.helidon.websocket.WsListener; +import io.helidon.websocket.WsSession; +import org.junit.jupiter.api.Test; + +import static io.helidon.webserver.tests.websocket.WebSocketTest.randomString; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@ServerTest +public class WebSocketClientTest { + + private final WsClient wsClient; + + private static final String[] text = { + randomString(100), + randomString(1000), + randomString(10000), + randomString(100000) + }; + + WebSocketClientTest(WsClient wsClient) { + this.wsClient = wsClient; + } + + @SetUpServer + public static void setup(WebServerConfig.Builder builder) { + builder.addProtocol( + WsConfig.builder() + .maxFrameLength(100000) // overrides application.yaml + .build()); + } + + @SetUpRoute + static void router(Router.RouterBuilder router) { + router.addRouting(WsRouting.builder().endpoint("/echo", new EchoService())); + } + + /** + * Tests sending long text messages using Helidon's WS client. + */ + @Test + void testLongTextMessages() throws InterruptedException { + Set messages = new HashSet<>(); + CountDownLatch messageLatch = new CountDownLatch(text.length); + + wsClient.connect("/echo", new WsListener() { + @Override + public void onMessage(WsSession session, String text, boolean last) { + messages.add(text); + messageLatch.countDown(); + if (messageLatch.getCount() == 0) { + session.close(WsCloseCodes.NORMAL_CLOSE, "Bye!"); + } + } + + @Override + public void onOpen(WsSession session) { + for (String s : text) { + session.send(s, false); + } + } + }); + + boolean await = messageLatch.await(10, TimeUnit.SECONDS); + assertThat(await, is(true)); + assertThat(messages, hasItems(text)); + } +} diff --git a/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketTest.java b/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketTest.java index dc3aa21b0ee..f4715c869c0 100644 --- a/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketTest.java +++ b/webserver/tests/websocket/src/test/java/io/helidon/webserver/tests/websocket/WebSocketTest.java @@ -215,7 +215,7 @@ Results results() throws ExecutionException, InterruptedException, TimeoutExcept } } - private static String randomString(int length) { + static String randomString(int length) { int leftLimit = 97; // letter 'a' int rightLimit = 122; // letter 'z' return new Random().ints(leftLimit, rightLimit + 1)