diff --git a/pom.xml b/pom.xml
index aeff337aca..f06aaf3d2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,6 @@
5.0.0-SNAPSHOT
5.0.0-SNAPSHOT
5.0.0-SNAPSHOT
- 4.21.0
5.0.0-SNAPSHOT
4.5.14
@@ -1356,6 +1355,12 @@
+
+ org.eclipse.jetty.websocket
+ jetty-websocket-jetty-client
+ ${jetty.version}
+
+
org.htmlunit
htmlunit-core-js
@@ -1381,11 +1386,6 @@
htmlunit-csp
${htmlunitcsp.version}
-
- org.htmlunit
- htmlunit-websocket-client
- ${htmlunitwebsocketclient.version}
-
org.apache.commons
diff --git a/src/main/java/org/htmlunit/WebClient.java b/src/main/java/org/htmlunit/WebClient.java
index 18e7fd39d0..f530730fe0 100644
--- a/src/main/java/org/htmlunit/WebClient.java
+++ b/src/main/java/org/htmlunit/WebClient.java
@@ -77,6 +77,7 @@
import org.htmlunit.html.XHtmlPage;
import org.htmlunit.html.parser.HTMLParser;
import org.htmlunit.html.parser.HTMLParserListener;
+import org.htmlunit.http.Cookie;
import org.htmlunit.http.HttpStatus;
import org.htmlunit.http.HttpUtils;
import org.htmlunit.httpclient.HttpClientConverter;
@@ -94,7 +95,6 @@
import org.htmlunit.javascript.host.file.Blob;
import org.htmlunit.javascript.host.html.HTMLIFrameElement;
import org.htmlunit.protocol.data.DataURLConnection;
-import org.htmlunit.http.Cookie;
import org.htmlunit.util.HeaderUtils;
import org.htmlunit.util.MimeType;
import org.htmlunit.util.NameValuePair;
@@ -3022,8 +3022,9 @@ public XHtmlPage loadXHtmlCodeIntoCurrentWindow(final String xhtmlCode) throws I
*
* @param webSocketListener the {@link WebSocketListener}
* @return a new {@link WebSocketAdapter}
+ * @throws Exception in case of error
*/
- public WebSocketAdapter buildWebSocketAdapter(final WebSocketListener webSocketListener) {
+ public WebSocketAdapter buildWebSocketAdapter(final WebSocketListener webSocketListener) throws Exception {
return webSocketAdapterFactory_.buildWebSocketAdapter(this, webSocketListener);
}
diff --git a/src/main/java/org/htmlunit/javascript/host/WebSocket.java b/src/main/java/org/htmlunit/javascript/host/WebSocket.java
index c682115159..be5a8c5bef 100644
--- a/src/main/java/org/htmlunit/javascript/host/WebSocket.java
+++ b/src/main/java/org/htmlunit/javascript/host/WebSocket.java
@@ -120,7 +120,7 @@ public void onWebSocketConnecting() {
}
@Override
- public void onWebSocketConnect() {
+ public void onWebSocketOpen() {
setReadyState(OPEN);
final Event openEvent = new Event(Event.TYPE_OPEN);
@@ -159,9 +159,10 @@ public void onWebSocketText(final String message) {
}
@Override
- public void onWebSocketBinary(final byte[] data, final int offset, final int length) {
- final NativeArrayBuffer buffer = new NativeArrayBuffer(length);
- System.arraycopy(data, offset, buffer.getBuffer(), 0, length);
+ public void onWebSocketBinary(final byte[] bytes) {
+ final NativeArrayBuffer buffer = new NativeArrayBuffer(bytes.length);
+ System.arraycopy(bytes, 0, buffer.getBuffer(), 0, bytes.length);
+
buffer.setParentScope(getParentScope());
buffer.setPrototype(ScriptableObject.getClassPrototype(getWindow(), buffer.getClassName()));
@@ -419,17 +420,10 @@ public void close() throws IOException {
public void close(final Object code, final Object reason) {
if (readyState_ != CLOSED) {
try {
- webSocketImpl_.closeIncommingSession();
- }
- catch (final Throwable e) {
- LOG.error("WS close error - incomingSession_.close() failed", e);
- }
-
- try {
- webSocketImpl_.closeOutgoingSession();
+ webSocketImpl_.closeSession();
}
catch (final Throwable e) {
- LOG.error("WS close error - outgoingSession_.close() failed", e);
+ LOG.error("WS close error - session_.close() failed", e);
}
}
@@ -452,10 +446,10 @@ public void send(final Object content) {
if (content instanceof NativeArrayBuffer buffer1) {
final byte[] bytes = buffer1.getBuffer();
final ByteBuffer buffer = ByteBuffer.wrap(bytes);
- webSocketImpl_.send(buffer);
+ webSocketImpl_.sendBinary(buffer);
return;
}
- webSocketImpl_.send(content);
+ webSocketImpl_.sendText(content.toString());
}
catch (final IOException e) {
LOG.error("WS send error", e);
diff --git a/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java b/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java
index 71eb29b340..c68a7673df 100644
--- a/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java
+++ b/src/main/java/org/htmlunit/websocket/JettyWebSocketAdapter.java
@@ -17,14 +17,15 @@
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
-import java.util.concurrent.Future;
+import java.util.function.Consumer;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.api.Callback;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
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.
@@ -44,7 +45,7 @@ public static final class JettyWebSocketAdapterFactory implements WebSocketAdapt
*/
@Override
public WebSocketAdapter buildWebSocketAdapter(final WebClient webClient,
- final WebSocketListener webSocketListener) {
+ final WebSocketListener webSocketListener) throws Exception {
return new JettyWebSocketAdapter(webClient, webSocketListener);
}
}
@@ -52,53 +53,47 @@ public WebSocketAdapter buildWebSocketAdapter(final WebClient webClient,
private final Object clientLock_ = new Object();
private WebSocketClient client_;
private final WebSocketListener listener_;
+ private final JettyWebSocketAdapterImpl adapterImpl_;
- private volatile Session incomingSession_;
- private Session outgoingSession_;
+ private volatile Session session_;
/**
* Ctor.
* @param webClient the {@link WebClient}
* @param listener the {@link WebSocketListener}
+ * @throws Exception in case of error
*/
- public JettyWebSocketAdapter(final WebClient webClient, final WebSocketListener listener) {
+ public JettyWebSocketAdapter(final WebClient webClient, final WebSocketListener listener) throws Exception {
super();
final WebClientOptions options = webClient.getOptions();
+ final HttpClient httpClient = new HttpClient();
+ httpClient.setHttpCookieStore(new WebSocketCookieStore(webClient));
+
+ // Initialize client
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();
- }
+ final SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true);
+ sslContextFactory.setEndpointIdentificationAlgorithm(null);
+ sslContextFactory.setTrustAll(true);
- listener_ = listener;
+ httpClient.setSslContextFactory(sslContextFactory);
+ }
- // use the same executor as the rest
- client_.setExecutor(webClient.getExecutor());
+ httpClient.start();
+ client_ = new WebSocketClient(httpClient);
- client_.getHttpClient().setCookieStore(new WebSocketCookieStore(webClient));
+ adapterImpl_ = new JettyWebSocketAdapterImpl();
+ listener_ = listener;
- final WebSocketPolicy policy = client_.getPolicy();
int size = options.getWebSocketMaxBinaryMessageSize();
if (size > 0) {
- policy.setMaxBinaryMessageSize(size);
- }
- size = options.getWebSocketMaxBinaryMessageBufferSize();
- if (size > 0) {
- policy.setMaxBinaryMessageBufferSize(size);
+ client_.setMaxBinaryMessageSize(size);
}
size = options.getWebSocketMaxTextMessageSize();
if (size > 0) {
- policy.setMaxTextMessageSize(size);
- }
- size = options.getWebSocketMaxTextMessageBufferSize();
- if (size > 0) {
- policy.setMaxTextMessageBufferSize(size);
+ client_.setMaxTextMessageSize(size);
}
+
}
/**
@@ -117,16 +112,8 @@ public void start() throws Exception {
@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);
- }
- });
+ listener_.onWebSocketConnecting();
+ client_.connect(adapterImpl_, url);
}
}
@@ -134,36 +121,26 @@ public void connect(final URI url) throws Exception {
* {@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");
- }
+ public void sendText(final String message) throws IOException {
+ session_.sendText(message, Callback.from(session_::demand, adapterImpl_));
}
/**
* {@inheritDoc}
*/
@Override
- public void closeIncommingSession() {
- if (incomingSession_ != null) {
- incomingSession_.close();
- }
+ public void sendBinary(final ByteBuffer buffer) throws IOException {
+ session_.sendBinary(buffer, Callback.from(session_::demand, adapterImpl_));
}
/**
* {@inheritDoc}
*/
@Override
- public void closeOutgoingSession() {
- if (outgoingSession_ != null) {
- outgoingSession_.close();
+ public void closeSession() {
+ if (session_ != null) {
+ session_.close();
+ session_ = null;
}
}
@@ -183,7 +160,10 @@ public void closeClient() throws Exception {
}
}
- private class JettyWebSocketAdapterImpl extends org.htmlunit.jetty.websocket.api.WebSocketAdapter {
+ /**
+ * Session.Listener.
+ */
+ public class JettyWebSocketAdapterImpl implements Session.Listener, Consumer {
/**
* Ctor.
@@ -196,53 +176,106 @@ private class JettyWebSocketAdapterImpl extends org.htmlunit.jetty.websocket.api
* {@inheritDoc}
*/
@Override
- public void onWebSocketConnect(final Session session) {
- super.onWebSocketConnect(session);
- outgoingSession_ = session;
+ public void onWebSocketOpen(final Session session) {
+ session_ = session;
- listener_.onWebSocketConnect();
+ listener_.onWebSocketOpen();
}
/**
* {@inheritDoc}
*/
@Override
- public void onWebSocketClose(final int statusCode, final String reason) {
- super.onWebSocketClose(statusCode, reason);
- outgoingSession_ = null;
+ public void onWebSocketText(final String message) {
+ if (session_ == null) {
+ return;
+ }
+ session_.demand();
- listener_.onWebSocketClose(statusCode, reason);
+ try {
+ listener_.onWebSocketText(message);
+ }
+ catch (final Exception e) {
+ // TODO: handle exception
+ }
}
/**
* {@inheritDoc}
*/
@Override
- public void onWebSocketText(final String message) {
- super.onWebSocketText(message);
+ public void onWebSocketBinary(final ByteBuffer buffer, final Callback callback) {
+ if (session_ == null) {
+ return;
+ }
+
+ byte[] arr = new byte[0];
+ try {
+ arr = new byte[buffer.remaining()];
+ buffer.get(arr);
+
+ callback.succeed();
+ }
+ catch (final Exception e) {
+ callback.fail(e);
+ return;
+ }
- listener_.onWebSocketText(message);
+ session_.demand();
+
+ try {
+ listener_.onWebSocketBinary(arr);
+ }
+ catch (final Exception e) {
+ // TODO: handle exception
+ }
}
/**
* {@inheritDoc}
*/
@Override
- public void onWebSocketBinary(final byte[] data, final int offset, final int length) {
- super.onWebSocketBinary(data, offset, length);
+ public void onWebSocketError(final Throwable cause) {
- listener_.onWebSocketBinary(data, offset, length);
+ final String className = cause.getClass().getName();
+ if ("java.nio.channels.ClosedChannelException".equals(className)
+ || "java.io.EOFException".equals(className)
+ || "java.net.SocketException".equals(className)
+ || "ClosedChannelException".contains(className)
+ || (cause.getMessage() != null
+ && (cause.getMessage().contains("Connection reset")
+ || cause.getMessage().contains("Broken pipe")
+ || cause.getMessage().contains("Connection closed")))) {
+ // TODO
+ return;
+ }
+
+ accept(cause);
}
/**
* {@inheritDoc}
*/
@Override
- public void onWebSocketError(final Throwable cause) {
- super.onWebSocketError(cause);
- outgoingSession_ = null;
+ public void onWebSocketClose(final int statusCode, final String reason) {
+ session_ = null;
+
+ try {
+ listener_.onWebSocketClose(statusCode, reason);
+ }
+ catch (final Exception e) {
+ // TODO: handle exception
+ }
+ }
- listener_.onWebSocketError(cause);
+ @Override
+ public void accept(final Throwable cause) {
+ try {
+ listener_.onWebSocketError(cause);
+ }
+ catch (final Exception e) {
+ // TODO: handle exception
+ }
}
}
}
diff --git a/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java b/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java
index 4442162856..af37b5b15e 100644
--- a/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java
+++ b/src/main/java/org/htmlunit/websocket/WebSocketAdapter.java
@@ -16,10 +16,11 @@
import java.io.IOException;
import java.net.URI;
+import java.nio.ByteBuffer;
/**
* Helper to have no direct dependency to the WebSockt client
- * implementation used by HtmlUnit.
+ * implementation used by HtmlUnit. This is used from the js code.
*
* @author Ronald Brill
*/
@@ -43,24 +44,25 @@ public interface WebSocketAdapter {
/**
* Sends the provided content.
*
- * @param content the content to be sent
+ * @param message the message to be sent
* @throws IOException in case of error
*/
- void send(Object content) throws IOException;
+ void sendText(String message) throws IOException;
/**
- * Close the incomming session.
+ * Sends the provided content.
*
- * @throws Exception in case of error
+ * @param buffer the bytes to be sent
+ * @throws IOException in case of error
*/
- void closeIncommingSession() throws Exception;
+ void sendBinary(ByteBuffer buffer) throws IOException;
/**
- * Close the outgoing session.
+ * Close the session.
*
* @throws Exception in case of error
*/
- void closeOutgoingSession() throws Exception;
+ void closeSession() throws Exception;
/**
* Close the client.
diff --git a/src/main/java/org/htmlunit/websocket/WebSocketAdapterFactory.java b/src/main/java/org/htmlunit/websocket/WebSocketAdapterFactory.java
index 29d816676c..8e42e3a4e6 100644
--- a/src/main/java/org/htmlunit/websocket/WebSocketAdapterFactory.java
+++ b/src/main/java/org/htmlunit/websocket/WebSocketAdapterFactory.java
@@ -32,6 +32,7 @@ public interface WebSocketAdapterFactory extends Serializable {
* @param webClient the {@link WebClient}
* @param webSocketListener the {@link WebSocketListener}
* @return a new {@link WebSocketAdapter}
+ * @throws Exception in case of error
*/
- WebSocketAdapter buildWebSocketAdapter(WebClient webClient, WebSocketListener webSocketListener);
+ WebSocketAdapter buildWebSocketAdapter(WebClient webClient, WebSocketListener webSocketListener) throws Exception;
}
diff --git a/src/main/java/org/htmlunit/websocket/WebSocketCookieStore.java b/src/main/java/org/htmlunit/websocket/WebSocketCookieStore.java
index 7fcde84ae7..049ec1d6a3 100644
--- a/src/main/java/org/htmlunit/websocket/WebSocketCookieStore.java
+++ b/src/main/java/org/htmlunit/websocket/WebSocketCookieStore.java
@@ -14,23 +14,22 @@
*/
package org.htmlunit.websocket;
-import java.net.CookieStore;
-import java.net.HttpCookie;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpCookieStore;
import org.htmlunit.WebClient;
-import org.htmlunit.javascript.host.WebSocket;
import org.htmlunit.http.Cookie;
+import org.htmlunit.javascript.host.WebSocket;
/**
* A helper class for {@link WebSocket}.
*
- * @author Ahmed Ashour
* @author Ronald Brill
*/
-class WebSocketCookieStore implements CookieStore {
+class WebSocketCookieStore implements HttpCookieStore {
private final WebClient webClient_;
@@ -38,27 +37,17 @@ class WebSocketCookieStore implements CookieStore {
webClient_ = webClient;
}
- /**
- * {@inheritDoc}
- */
@Override
- public void add(final URI uri, final HttpCookie cookie) {
- throw new UnsupportedOperationException();
+ public boolean add(final URI uri, final HttpCookie cookie) {
+ return false;
}
- /**
- * {@inheritDoc}
- */
@Override
- public List get(final URI uri) {
+ public List all() {
final List cookies = new ArrayList<>();
try {
- final String urlString = uri.toString().replace("ws://", "http://").replace("wss://", "https://");
- final java.net.URL url = new java.net.URL(urlString);
- for (final Cookie cookie : webClient_.getCookies(url)) {
- final HttpCookie httpCookie = new HttpCookie(cookie.getName(), cookie.getValue());
- httpCookie.setVersion(0);
- cookies.add(httpCookie);
+ for (final Cookie htmlUnitCookie : webClient_.getCookieManager().getCookies()) {
+ cookies.add(buildHttpCookie(htmlUnitCookie));
}
}
catch (final Exception e) {
@@ -67,35 +56,59 @@ public List get(final URI uri) {
return cookies;
}
- /**
- * {@inheritDoc}
- */
@Override
- public List getCookies() {
- throw new UnsupportedOperationException();
- }
+ public List match(final URI uri) {
+ final List cookies = new ArrayList<>();
+ try {
+ final String urlString = uri.toString()
+ .replace("ws://", "http://")
+ .replace("wss://", "https://");
+ final java.net.URL url = new java.net.URL(urlString);
- /**
- * {@inheritDoc}
- */
- @Override
- public List getURIs() {
- throw new UnsupportedOperationException();
+ for (final Cookie htmlUnitCookie : webClient_.getCookies(url)) {
+ cookies.add(buildHttpCookie(htmlUnitCookie));
+ }
+ }
+ catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+ return cookies;
}
- /**
- * {@inheritDoc}
- */
@Override
public boolean remove(final URI uri, final HttpCookie cookie) {
- throw new UnsupportedOperationException();
+ return false;
}
- /**
- * {@inheritDoc}
- */
@Override
- public boolean removeAll() {
+ public boolean clear() {
return false;
}
+
+ private static HttpCookie buildHttpCookie(final Cookie htmlUnitCookie) {
+ final HttpCookie.Builder builder = HttpCookie.build(
+ htmlUnitCookie.getName(),
+ htmlUnitCookie.getValue());
+ if (htmlUnitCookie.getDomain() != null) {
+ builder.domain(htmlUnitCookie.getDomain());
+ }
+
+ if (htmlUnitCookie.getPath() != null) {
+ builder.path(htmlUnitCookie.getPath());
+ }
+//
+// if (htmlUnitCookie.getMaxAge() > -1) {
+// builder.maxAge(htmlUnitCookie.getMaxAge());
+// }
+
+ if (htmlUnitCookie.isSecure()) {
+ builder.secure(true);
+ }
+
+ if (htmlUnitCookie.isHttpOnly()) {
+ builder.httpOnly(true);
+ }
+
+ return builder.build();
+ }
}
diff --git a/src/main/java/org/htmlunit/websocket/WebSocketListener.java b/src/main/java/org/htmlunit/websocket/WebSocketListener.java
index 327d96e3fe..dca06604e0 100644
--- a/src/main/java/org/htmlunit/websocket/WebSocketListener.java
+++ b/src/main/java/org/htmlunit/websocket/WebSocketListener.java
@@ -28,9 +28,9 @@ public interface WebSocketListener {
void onWebSocketConnecting();
/**
- * Callback to be called when connected.
+ * Callback to be called when opened.
*/
- void onWebSocketConnect();
+ void onWebSocketOpen();
/**
* Callback to be called when closed.
@@ -50,11 +50,9 @@ public interface WebSocketListener {
/**
* Callback to be called when binary data retrieved.
*
- * @param data the bytes
- * @param offset start offset
- * @param length the length
+ * @param buffer the bytes
*/
- void onWebSocketBinary(byte[] data, int offset, int length);
+ void onWebSocketBinary(byte[] buffer);
/**
* Callback to be called on connect error.
diff --git a/src/test/java/org/htmlunit/javascript/host/WebSocketTest.java b/src/test/java/org/htmlunit/javascript/host/WebSocketTest.java
index e8dc0bbed6..d06c2fa470 100644
--- a/src/test/java/org/htmlunit/javascript/host/WebSocketTest.java
+++ b/src/test/java/org/htmlunit/javascript/host/WebSocketTest.java
@@ -273,6 +273,7 @@ public void onWebSocketText(final String data) {
@Override
public void onWebSocketClose(final int closeCode, final String message) {
+System.out.println("Server close");
webSockets_.remove(this);
}
}