From 9228f3e1e39db6dcb2ba8bce762f308aa10914de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:02:56 +0000 Subject: [PATCH 1/2] Replace Jetty WebSocket adapter with JDK HttpClient-based implementation - Add JdkWebSocketAdapter using java.net.http.HttpClient and java.net.http.WebSocket - Remove JettyWebSocketAdapter and htmlunit-websocket-client dependency - Update WebClient to default to JdkWebSocketAdapterFactory - Update module-info.java to require java.net.http instead of htmlunit.websocket.client - Update ArchitectureTest to remove Jetty-specific exclusions Agent-Logs-Url: https://github.com/HtmlUnit/htmlunit/sessions/b3db8ccb-ee0a-436b-8c39-85fd25db4747 Co-authored-by: rbri <2544132+rbri@users.noreply.github.com> --- javac.20260412_170050.args | 9 + pom.xml | 6 - src/main/java/org/htmlunit/WebClient.java | 4 +- .../websocket/JdkWebSocketAdapter.java | 315 ++++++++++++++++++ .../websocket/JettyWebSocketAdapter.java | 248 -------------- src/main/module-info/module-info.java | 2 +- .../htmlunit/archunit/ArchitectureTest.java | 5 +- 7 files changed, 328 insertions(+), 261 deletions(-) create mode 100644 javac.20260412_170050.args create mode 100644 src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java delete mode 100644 src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java diff --git a/javac.20260412_170050.args b/javac.20260412_170050.args new file mode 100644 index 0000000000..77ee6f5f69 --- /dev/null +++ b/javac.20260412_170050.args @@ -0,0 +1,9 @@ +-d +/tmp/test-compile +-sourcepath +src/main/java +--module-path + +-cp + +src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java diff --git a/pom.xml b/pom.xml index bdab1fc068..61c98e931f 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,6 @@ 5.0.0-SNAPSHOT 5.0.0-SNAPSHOT 5.0.0-SNAPSHOT - 4.21.0 5.0.0-SNAPSHOT 4.5.14 @@ -1407,11 +1406,6 @@ htmlunit-csp ${htmlunit-csp.version} - - org.htmlunit - htmlunit-websocket-client - ${htmlunit-websocketclient.version} - org.apache.commons diff --git a/src/main/java/org/htmlunit/WebClient.java b/src/main/java/org/htmlunit/WebClient.java index 18e7fd39d0..eae5d7e5c5 100644 --- a/src/main/java/org/htmlunit/WebClient.java +++ b/src/main/java/org/htmlunit/WebClient.java @@ -100,7 +100,7 @@ import org.htmlunit.util.NameValuePair; import org.htmlunit.util.StringUtils; import org.htmlunit.util.UrlUtils; -import org.htmlunit.websocket.JettyWebSocketAdapter.JettyWebSocketAdapterFactory; +import org.htmlunit.websocket.JdkWebSocketAdapter.JdkWebSocketAdapterFactory; import org.htmlunit.websocket.WebSocketAdapter; import org.htmlunit.websocket.WebSocketAdapterFactory; import org.htmlunit.websocket.WebSocketListener; @@ -341,7 +341,7 @@ public WebClient(final BrowserVersion browserVersion, final boolean javaScriptEn } loadQueue_ = new ArrayList<>(); - webSocketAdapterFactory_ = new JettyWebSocketAdapterFactory(); + webSocketAdapterFactory_ = new JdkWebSocketAdapterFactory(); // The window must be constructed AFTER the script engine. currentWindowTracker_ = new CurrentWindowTracker(this, true); diff --git a/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java b/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java new file mode 100644 index 0000000000..cb128c1955 --- /dev/null +++ b/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2002-2026 Gargoyle Software Inc. + * + * 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 + * https://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.htmlunit.websocket; + +import java.io.IOException; +import java.net.CookieHandler; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Builder; +import java.nio.ByteBuffer; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.htmlunit.WebClient; +import org.htmlunit.WebClientOptions; + +/** + * JDK based implementation of the {@link WebSocketAdapter}. + * Uses the {@link java.net.http.HttpClient} and {@link java.net.http.WebSocket} + * APIs available since JDK 11. + * + * @author Ronald Brill + */ +public final class JdkWebSocketAdapter implements WebSocketAdapter { + + /** + * Our {@link WebSocketAdapterFactory}. + */ + public static final class JdkWebSocketAdapterFactory implements WebSocketAdapterFactory { + /** + * {@inheritDoc} + */ + @Override + public WebSocketAdapter buildWebSocketAdapter(final WebClient webClient, + final WebSocketListener webSocketListener) { + return new JdkWebSocketAdapter(webClient, webSocketListener); + } + } + + private final Object clientLock_ = new Object(); + private HttpClient httpClient_; + private final WebClient webClient_; + private final WebSocketListener listener_; + + private volatile java.net.http.WebSocket incomingSession_; + private java.net.http.WebSocket outgoingSession_; + + /** + * Ctor. + * @param webClient the {@link WebClient} + * @param listener the {@link WebSocketListener} + */ + public JdkWebSocketAdapter(final WebClient webClient, final WebSocketListener listener) { + super(); + webClient_ = webClient; + listener_ = listener; + } + + /** + * {@inheritDoc} + */ + @Override + public void start() throws Exception { + synchronized (clientLock_) { + final WebClientOptions options = webClient_.getOptions(); + final Executor executor = webClient_.getExecutor(); + + final Builder builder = HttpClient.newBuilder() + .executor(executor) + .cookieHandler(new WebSocketCookieHandler(webClient_)); + + if (options.isUseInsecureSSL()) { + builder.sslContext(createInsecureSslContext()); + } + + httpClient_ = builder.build(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void connect(final URI url) throws Exception { + synchronized (clientLock_) { + final Executor executor = webClient_.getExecutor(); + final CompletableFuture connectFuture = + httpClient_.newWebSocketBuilder() + .buildAsync(url, new JdkWebSocketListenerImpl()); + + executor.execute(() -> { + try { + listener_.onWebSocketConnecting(); + incomingSession_ = connectFuture.join(); + } + catch (final Exception e) { + listener_.onWebSocketConnectError(e); + } + }); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void send(final Object content) throws IOException { + try { + if (content instanceof String string) { + outgoingSession_.sendText(string, true).join(); + } + else if (content instanceof ByteBuffer buffer) { + outgoingSession_.sendBinary(buffer, true).join(); + } + else { + throw new IllegalStateException( + "Not Yet Implemented: WebSocket.send() was used to send non-string value"); + } + } + catch (final IllegalStateException e) { + throw e; + } + catch (final Exception e) { + throw new IOException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void closeIncommingSession() { + if (incomingSession_ != null) { + incomingSession_.sendClose(java.net.http.WebSocket.NORMAL_CLOSURE, "").join(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void closeOutgoingSession() { + if (outgoingSession_ != null) { + outgoingSession_.sendClose(java.net.http.WebSocket.NORMAL_CLOSURE, "").join(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void closeClient() throws Exception { + synchronized (clientLock_) { + // HttpClient does not have a close() in Java 17; + // simply drop the reference so it can be garbage-collected + httpClient_ = null; + } + } + + private static SSLContext createInsecureSslContext() + throws NoSuchAlgorithmException, KeyManagementException { + final TrustManager[] trustAllCerts = { + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(final X509Certificate[] certs, final String authType) { + // trust all + } + + @Override + public void checkServerTrusted(final X509Certificate[] certs, final String authType) { + // trust all + } + } + }; + final SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + return sslContext; + } + + /** + * A {@link CookieHandler} that bridges to the {@link WebClient} cookie store. + */ + private static class WebSocketCookieHandler extends CookieHandler { + private final WebSocketCookieStore cookieStore_; + + WebSocketCookieHandler(final WebClient webClient) { + cookieStore_ = new WebSocketCookieStore(webClient); + } + + @Override + public java.util.Map> get( + final URI uri, + final java.util.Map> requestHeaders) { + final java.util.List cookies = cookieStore_.get(uri); + final java.util.Map> result = new java.util.HashMap<>(); + if (!cookies.isEmpty()) { + final java.util.List cookieValues = new java.util.ArrayList<>(); + for (final java.net.HttpCookie cookie : cookies) { + cookieValues.add(cookie.toString()); + } + result.put("Cookie", cookieValues); + } + return result; + } + + @Override + public void put(final URI uri, + final java.util.Map> responseHeaders) { + // not needed for WebSocket connections + } + } + + private class JdkWebSocketListenerImpl implements java.net.http.WebSocket.Listener { + + private StringBuilder textAccumulator_; + private ByteBuffer binaryAccumulator_; + + JdkWebSocketListenerImpl() { + super(); + } + + @Override + public void onOpen(final java.net.http.WebSocket webSocket) { + outgoingSession_ = webSocket; + listener_.onWebSocketConnect(); + webSocket.request(1); + } + + @Override + public CompletionStage onText(final java.net.http.WebSocket webSocket, + final CharSequence data, final boolean last) { + if (textAccumulator_ == null) { + textAccumulator_ = new StringBuilder(); + } + textAccumulator_.append(data); + + if (last) { + final String message = textAccumulator_.toString(); + textAccumulator_ = null; + listener_.onWebSocketText(message); + } + + webSocket.request(1); + return null; + } + + @Override + public CompletionStage onBinary(final java.net.http.WebSocket webSocket, + final ByteBuffer data, final boolean last) { + if (binaryAccumulator_ == null) { + binaryAccumulator_ = ByteBuffer.allocate(data.remaining()); + binaryAccumulator_.put(data); + } + else { + final ByteBuffer newBuffer = ByteBuffer.allocate( + binaryAccumulator_.position() + data.remaining()); + binaryAccumulator_.flip(); + newBuffer.put(binaryAccumulator_); + newBuffer.put(data); + binaryAccumulator_ = newBuffer; + } + + if (last) { + binaryAccumulator_.flip(); + final byte[] bytes = new byte[binaryAccumulator_.remaining()]; + binaryAccumulator_.get(bytes); + binaryAccumulator_ = null; + listener_.onWebSocketBinary(bytes, 0, bytes.length); + } + + webSocket.request(1); + return null; + } + + @Override + public CompletionStage onClose(final java.net.http.WebSocket webSocket, + final int statusCode, final String reason) { + outgoingSession_ = null; + listener_.onWebSocketClose(statusCode, reason); + return null; + } + + @Override + public void onError(final java.net.http.WebSocket webSocket, final Throwable error) { + outgoingSession_ = null; + listener_.onWebSocketError(error); + } + } +} diff --git a/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java b/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java deleted file mode 100644 index 71eb29b340..0000000000 --- a/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2002-2026 Gargoyle Software Inc. - * - * 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 - * https://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.htmlunit.websocket; - -import java.io.IOException; -import java.net.URI; -import java.nio.ByteBuffer; -import java.util.concurrent.Future; - -import org.htmlunit.WebClient; -import org.htmlunit.WebClientOptions; -import org.htmlunit.jetty.util.ssl.SslContextFactory; -import org.htmlunit.jetty.websocket.api.Session; -import org.htmlunit.jetty.websocket.api.WebSocketPolicy; -import org.htmlunit.jetty.websocket.client.WebSocketClient; - -/** - * Jetty9 based impl of the WebSocketAdapter. - * To avoid conflicts with other jetty versions used by projects, we use - * our own shaded version of jetty9 (https://github.com/HtmlUnit/htmlunit-websocket-client). - * - * @author Ronald Brill - */ -public final class JettyWebSocketAdapter implements WebSocketAdapter { - - /** - * Our {@link WebSocketAdapterFactory}. - */ - public static final class JettyWebSocketAdapterFactory implements WebSocketAdapterFactory { - /** - * {@inheritDoc} - */ - @Override - public WebSocketAdapter buildWebSocketAdapter(final WebClient webClient, - final WebSocketListener webSocketListener) { - return new JettyWebSocketAdapter(webClient, webSocketListener); - } - } - - private final Object clientLock_ = new Object(); - private WebSocketClient client_; - private final WebSocketListener listener_; - - private volatile Session incomingSession_; - private Session outgoingSession_; - - /** - * Ctor. - * @param webClient the {@link WebClient} - * @param listener the {@link WebSocketListener} - */ - public JettyWebSocketAdapter(final WebClient webClient, final WebSocketListener listener) { - super(); - final WebClientOptions options = webClient.getOptions(); - - if (webClient.getOptions().isUseInsecureSSL()) { - client_ = new WebSocketClient(new SslContextFactory(true), null, null); - // still use the deprecated method here to be backward compatible with older jetty versions - // see https://github.com/HtmlUnit/htmlunit/issues/36 - // client_ = new WebSocketClient(new SslContextFactory.Client(true), null, null); - } - else { - client_ = new WebSocketClient(); - } - - listener_ = listener; - - // use the same executor as the rest - client_.setExecutor(webClient.getExecutor()); - - client_.getHttpClient().setCookieStore(new WebSocketCookieStore(webClient)); - - final WebSocketPolicy policy = client_.getPolicy(); - int size = options.getWebSocketMaxBinaryMessageSize(); - if (size > 0) { - policy.setMaxBinaryMessageSize(size); - } - size = options.getWebSocketMaxBinaryMessageBufferSize(); - if (size > 0) { - policy.setMaxBinaryMessageBufferSize(size); - } - size = options.getWebSocketMaxTextMessageSize(); - if (size > 0) { - policy.setMaxTextMessageSize(size); - } - size = options.getWebSocketMaxTextMessageBufferSize(); - if (size > 0) { - policy.setMaxTextMessageBufferSize(size); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void start() throws Exception { - synchronized (clientLock_) { - client_.start(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void connect(final URI url) throws Exception { - synchronized (clientLock_) { - final Future connectFuture = client_.connect(new JettyWebSocketAdapterImpl(), url); - client_.getExecutor().execute(() -> { - try { - listener_.onWebSocketConnecting(); - incomingSession_ = connectFuture.get(); - } - catch (final Exception e) { - listener_.onWebSocketConnectError(e); - } - }); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void send(final Object content) throws IOException { - if (content instanceof String string) { - outgoingSession_.getRemote().sendString(string); - } - else if (content instanceof ByteBuffer buffer) { - outgoingSession_.getRemote().sendBytes(buffer); - } - else { - throw new IllegalStateException( - "Not Yet Implemented: WebSocket.send() was used to send non-string value"); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void closeIncommingSession() { - if (incomingSession_ != null) { - incomingSession_.close(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void closeOutgoingSession() { - if (outgoingSession_ != null) { - outgoingSession_.close(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void closeClient() throws Exception { - synchronized (clientLock_) { - if (client_ != null) { - client_.stop(); - client_.destroy(); - - // TODO finally ? - client_ = null; - } - } - } - - private class JettyWebSocketAdapterImpl extends org.htmlunit.jetty.websocket.api.WebSocketAdapter { - - /** - * Ctor. - */ - JettyWebSocketAdapterImpl() { - super(); - } - - /** - * {@inheritDoc} - */ - @Override - public void onWebSocketConnect(final Session session) { - super.onWebSocketConnect(session); - outgoingSession_ = session; - - listener_.onWebSocketConnect(); - } - - /** - * {@inheritDoc} - */ - @Override - public void onWebSocketClose(final int statusCode, final String reason) { - super.onWebSocketClose(statusCode, reason); - outgoingSession_ = null; - - listener_.onWebSocketClose(statusCode, reason); - } - - /** - * {@inheritDoc} - */ - @Override - public void onWebSocketText(final String message) { - super.onWebSocketText(message); - - listener_.onWebSocketText(message); - } - - /** - * {@inheritDoc} - */ - @Override - public void onWebSocketBinary(final byte[] data, final int offset, final int length) { - super.onWebSocketBinary(data, offset, length); - - listener_.onWebSocketBinary(data, offset, length); - } - - /** - * {@inheritDoc} - */ - @Override - public void onWebSocketError(final Throwable cause) { - super.onWebSocketError(cause); - outgoingSession_ = null; - - listener_.onWebSocketError(cause); - } - } -} diff --git a/src/main/module-info/module-info.java b/src/main/module-info/module-info.java index 157c19b0c4..5c381bb53c 100644 --- a/src/main/module-info/module-info.java +++ b/src/main/module-info/module-info.java @@ -22,7 +22,7 @@ requires java.xml; requires jdk.xml.dom; - requires htmlunit.websocket.client; + requires java.net.http; requires org.htmlunit.cssparser; requires org.htmlunit.corejs; diff --git a/src/test/java/org/htmlunit/archunit/ArchitectureTest.java b/src/test/java/org/htmlunit/archunit/ArchitectureTest.java index f830133fb3..cc44f0a20c 100644 --- a/src/test/java/org/htmlunit/archunit/ArchitectureTest.java +++ b/src/test/java/org/htmlunit/archunit/ArchitectureTest.java @@ -173,15 +173,12 @@ public class ArchitectureTest { .should().dependOnClassesThat().haveFullyQualifiedName("org.apache.commons.lang3.math.NumberUtils"); /** - * The jetty websocket stuff is only used by one class. + * The jetty websocket stuff is no longer used (replaced by JDK HttpClient WebSocket). */ @ArchTest public static final ArchRule webSocketPackageRule = noClasses() .that() .resideOutsideOfPackage("org.htmlunit.jetty..") - .and().doNotHaveFullyQualifiedName("org.htmlunit.websocket.JettyWebSocketAdapter") - .and().doNotHaveFullyQualifiedName("org.htmlunit.websocket.JettyWebSocketAdapter$JettyWebSocketAdapterFactory") - .and().doNotHaveFullyQualifiedName("org.htmlunit.websocket.JettyWebSocketAdapter$JettyWebSocketAdapterImpl") .should() .dependOnClassesThat().resideInAnyPackage("org.htmlunit.jetty.."); From 1212576af4c1dd70092dec749f0bc383327da575 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:06:12 +0000 Subject: [PATCH 2/2] Address code review feedback: fix typos, improve error message, optimize binary accumulation - Rename closeIncommingSession -> closeIncomingSession in interface and all callers - Fix 'WebSockt' typos in Javadoc - Update error message to accurately describe supported content types - Use ByteArrayOutputStream for efficient binary message accumulation Agent-Logs-Url: https://github.com/HtmlUnit/htmlunit/sessions/b3db8ccb-ee0a-436b-8c39-85fd25db4747 Co-authored-by: rbri <2544132+rbri@users.noreply.github.com> --- javac.20260412_170050.args | 9 ------- .../htmlunit/javascript/host/WebSocket.java | 2 +- .../websocket/JdkWebSocketAdapter.java | 27 +++++++++---------- .../htmlunit/websocket/WebSocketAdapter.java | 6 ++--- .../htmlunit/websocket/WebSocketListener.java | 2 +- 5 files changed, 18 insertions(+), 28 deletions(-) delete mode 100644 javac.20260412_170050.args diff --git a/javac.20260412_170050.args b/javac.20260412_170050.args deleted file mode 100644 index 77ee6f5f69..0000000000 --- a/javac.20260412_170050.args +++ /dev/null @@ -1,9 +0,0 @@ --d -/tmp/test-compile --sourcepath -src/main/java ---module-path - --cp - -src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java diff --git a/src/main/java/org/htmlunit/javascript/host/WebSocket.java b/src/main/java/org/htmlunit/javascript/host/WebSocket.java index 072c878d3b..96335a2beb 100644 --- a/src/main/java/org/htmlunit/javascript/host/WebSocket.java +++ b/src/main/java/org/htmlunit/javascript/host/WebSocket.java @@ -421,7 +421,7 @@ public void close() throws IOException { public void close(final Object code, final Object reason) { if (readyState_ != CLOSED) { try { - webSocketImpl_.closeIncommingSession(); + webSocketImpl_.closeIncomingSession(); } catch (final Throwable e) { LOG.error("WS close error - incomingSession_.close() failed", e); diff --git a/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java b/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java index cb128c1955..bf9760176e 100644 --- a/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java +++ b/src/main/java/org/htmlunit/websocket/JdkWebSocketAdapter.java @@ -14,6 +14,7 @@ */ package org.htmlunit.websocket; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.CookieHandler; import java.net.URI; @@ -135,7 +136,7 @@ else if (content instanceof ByteBuffer buffer) { } else { throw new IllegalStateException( - "Not Yet Implemented: WebSocket.send() was used to send non-string value"); + "Unsupported content type for WebSocket.send(): expected String or ByteBuffer"); } } catch (final IllegalStateException e) { @@ -150,7 +151,7 @@ else if (content instanceof ByteBuffer buffer) { * {@inheritDoc} */ @Override - public void closeIncommingSession() { + public void closeIncomingSession() { if (incomingSession_ != null) { incomingSession_.sendClose(java.net.http.WebSocket.NORMAL_CLOSURE, "").join(); } @@ -239,7 +240,7 @@ public void put(final URI uri, private class JdkWebSocketListenerImpl implements java.net.http.WebSocket.Listener { private StringBuilder textAccumulator_; - private ByteBuffer binaryAccumulator_; + private ByteArrayOutputStream binaryAccumulator_; JdkWebSocketListenerImpl() { super(); @@ -274,22 +275,20 @@ public CompletionStage onText(final java.net.http.WebSocket webSocket, public CompletionStage onBinary(final java.net.http.WebSocket webSocket, final ByteBuffer data, final boolean last) { if (binaryAccumulator_ == null) { - binaryAccumulator_ = ByteBuffer.allocate(data.remaining()); - binaryAccumulator_.put(data); + binaryAccumulator_ = new ByteArrayOutputStream(); + } + if (data.hasArray()) { + binaryAccumulator_.write(data.array(), + data.arrayOffset() + data.position(), data.remaining()); } else { - final ByteBuffer newBuffer = ByteBuffer.allocate( - binaryAccumulator_.position() + data.remaining()); - binaryAccumulator_.flip(); - newBuffer.put(binaryAccumulator_); - newBuffer.put(data); - binaryAccumulator_ = newBuffer; + final byte[] temp = new byte[data.remaining()]; + data.get(temp); + binaryAccumulator_.write(temp, 0, temp.length); } if (last) { - binaryAccumulator_.flip(); - final byte[] bytes = new byte[binaryAccumulator_.remaining()]; - binaryAccumulator_.get(bytes); + final byte[] bytes = binaryAccumulator_.toByteArray(); binaryAccumulator_ = null; listener_.onWebSocketBinary(bytes, 0, bytes.length); } diff --git a/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java b/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java index 4442162856..ea3c1007b4 100644 --- a/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java +++ b/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java @@ -18,7 +18,7 @@ import java.net.URI; /** - * Helper to have no direct dependency to the WebSockt client + * Helper to have no direct dependency to the WebSocket client * implementation used by HtmlUnit. * * @author Ronald Brill @@ -49,11 +49,11 @@ public interface WebSocketAdapter { void send(Object content) throws IOException; /** - * Close the incomming session. + * Close the incoming session. * * @throws Exception in case of error */ - void closeIncommingSession() throws Exception; + void closeIncomingSession() throws Exception; /** * Close the outgoing session. diff --git a/src/main/java/org/htmlunit/websocket/WebSocketListener.java b/src/main/java/org/htmlunit/websocket/WebSocketListener.java index 327d96e3fe..589b0789c6 100644 --- a/src/main/java/org/htmlunit/websocket/WebSocketListener.java +++ b/src/main/java/org/htmlunit/websocket/WebSocketListener.java @@ -15,7 +15,7 @@ package org.htmlunit.websocket; /** - * Helper to have no direct dependency to the WebSockt client + * Helper to have no direct dependency to the WebSocket client * implementation used by HtmlUnit. * * @author Ronald Brill