diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc index 153cbb27169..531a7c828b0 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/websocket/client-websocket.adoc @@ -35,7 +35,7 @@ The Maven artifact coordinates are the following: [[pg-client-websocket-start]] ==== Starting WebSocketClient -The main class is `org.eclipse.jetty.ee9.websocket.client.WebSocketClient`; you instantiate it, configure it, and then start it like may other Jetty components. +The main class is `org.eclipse.jetty.websocket.client.WebSocketClient`; you instantiate it, configure it, and then start it like may other Jetty components. This is a minimal example: [source,java,indent=0] @@ -124,7 +124,7 @@ In code: include::../../{doc_code}/org/eclipse/jetty/docs/programming/client/websocket/WebSocketClientDocs.java[tags=connectHTTP11] ---- -`WebSocketClient.connect()` links the client-side WebSocket _endpoint_ to a specific server URI, and returns a `CompletableFuture` of an `org.eclipse.jetty.ee9.websocket.api.Session`. +`WebSocketClient.connect()` links the client-side WebSocket _endpoint_ to a specific server URI, and returns a `CompletableFuture` of an `org.eclipse.jetty.websocket.api.Session`. The endpoint offers APIs to _receive_ WebSocket data (or errors) from the server, while the session offers APIs to _send_ WebSocket data to the server. @@ -133,7 +133,7 @@ The endpoint offers APIs to _receive_ WebSocket data (or errors) from the server Initiating a WebSocket communication with a server using HTTP/1.1 is detailed in link:https://tools.ietf.org/html/rfc8441[RFC 8441]. -A WebSocket client establishes a TCP connection to the server or reuses an existing one currently used for HTTP/2, then sends an HTTP/2 _connect_ request over an HTTP/2 stream. +A WebSocket client establishes a TCP connection to the server or reuses an existing one currently used for HTTP/2, then sends an HTTP/2 _CONNECT_ request over an HTTP/2 stream. If the server supports upgrading to WebSocket, it responds with HTTP status code `200`, then switches the communication over that stream, either incoming or outgoing, to happen using HTTP/2 `DATA` frames wrapping WebSocket frames. diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc index 09b140a57ad..40ce4f6bfc7 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/websocket.adoc @@ -35,44 +35,48 @@ A WebSocket endpoint is the entity that receives WebSocket events. The WebSocket events are the following: -* The _connect_ event. +* The _open_ event. This event is emitted when the WebSocket communication has been successfully established. -Applications interested in the connect event receive the WebSocket _session_ so that they can use it to send data to the remote peer. +Applications interested in the open event receive the WebSocket _session_ so that they can use it to send data to the remote peer. * The _close_ event. This event is emitted when the WebSocket communication has been closed. Applications interested in the close event receive a WebSocket status code and an optional close reason message. * The _error_ event. This event is emitted when the WebSocket communication encounters a fatal error, such as an I/O error (for example, the network connection has been broken), or a protocol error (for example, the remote peer sends an invalid WebSocket frame). Applications interested in the error event receive a `Throwable` that represent the error. -* The _message_ event. -The message event is emitted when a WebSocket message is received. -Only one thread at a time will be delivering a message event to the `onMessage` method; the next message event will not be delivered until the previous call to the `onMessage` method has exited. -Endpoints will always be notified of message events in the same order they were received over the network. +* The _frame_ events. +The frame events are emitted when a WebSocket frame is received, either a control frame such as PING, PONG or CLOSE, or a data frame such as BINARY or TEXT. +One or more data frames of the same type define a _message_. +* The _message_ events. +The message event are emitted when a WebSocket message is received. The message event can be of two types: -** Textual message event. +** TEXT. Applications interested in this type of messages receive a `String` representing the UTF-8 bytes received. -** Binary message event. -Applications interested in this type of messages receive a `byte[]` representing the raw bytes received. +** BINARY. +Applications interested in this type of messages receive a `ByteBuffer` representing the raw bytes received. + +Only one thread at a time will be delivering frame or message events to the corresponding methods; the next frame or message event will not be delivered until the previous call to the corresponding method has exited, and if there is demand for it. +Endpoints will always be notified of message events in the same order they were received over the network. [[pg-websocket-endpoints-listener]] ===== Listener Endpoints -A WebSocket endpoint may implement the `org.eclipse.jetty.ee9.websocket.api.WebSocketListener` interface to receive WebSocket events: +A WebSocket endpoint may implement the `org.eclipse.jetty.websocket.api.Session.Listener` interface to receive WebSocket events: [source,java,indent=0] ---- include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=listenerEndpoint] ---- -<1> Your listener class implements `WebSocketListener`. +<1> Your listener class implements `Session.Listener`. ====== Message Streaming Reads If you need to deal with large WebSocket messages, you may reduce the memory usage by streaming the message content. For large WebSocket messages, the memory usage may be large due to the fact that the text or the bytes must be accumulated until the message is complete before delivering the message event. -To stream textual or binary messages, you must implement interface `org.eclipse.jetty.ee9.websocket.api.WebSocketPartialListener` instead of `WebSocketListener`. +To stream textual or binary messages, you override either `org.eclipse.jetty.websocket.api.Session.Listener.onWebSocketPartialText(\...)` or `org.eclipse.jetty.websocket.api.Session.Listener.onWebSocketPartialBinary(\...)`. -Interface `WebSocketPartialListener` exposes one method for textual messages, and one method to binary messages that receive _chunks_ of, respectively, text and bytes that form the whole WebSocket message. +These methods that receive _chunks_ of, respectively, text and bytes that form the whole WebSocket message. You may accumulate the chunks yourself, or process each chunk as it arrives, or stream the chunks elsewhere, for example: @@ -84,7 +88,7 @@ include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=s [[pg-websocket-endpoints-annotated]] ===== Annotated Endpoints -A WebSocket endpoint may annotate methods with `org.eclipse.jetty.ee9.websocket.api.annotations.*` annotations to receive WebSocket events. +A WebSocket endpoint may annotate methods with `org.eclipse.jetty.websocket.api.annotations.*` annotations to receive WebSocket events. Each annotated method may take an optional `Session` argument as its first parameter: [source,java,indent=0] @@ -92,7 +96,7 @@ Each annotated method may take an optional `Session` argument as its first param include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=annotatedEndpoint] ---- <1> Use the `@WebSocket` annotation at the class level to make it a WebSocket endpoint. -<2> Use the `@OnWebSocketConnect` annotation for the _connect_ event. +<2> Use the `@OnWebSocketOpen` annotation for the _open_ event. As this is the first event notified to the endpoint, you can configure the `Session` object. <3> Use the `@OnWebSocketClose` annotation for the _close_ event. The method may take an optional `Session` as first parameter. @@ -103,20 +107,12 @@ The method may take an optional `Session` as first parameter. [NOTE] ==== -For binary messages, you may declare the annotated method with either or these two signatures: +For binary messages, you may declare the annotated method with this signature: [source,java] ---- @OnWebSocketMessage -public void methodName(byte[] bytes, int offset, int length) { ... } ----- - -or - -[source,java] ----- -@OnWebSocketMessage -public void methodName(ByteBuffer buffer) { ... } +public void methodName(ByteBuffer buffer, Callback callback) { ... } ---- ==== @@ -144,8 +140,8 @@ A WebSocket session is the entity that offers an API to send data to the remote [[pg-websocket-session-configure]] ===== Configuring the Session -You may configure the WebSocket session behavior using the `org.eclipse.jetty.ee9.websocket.api.Session` APIs. -You want to do this as soon as you have access to the `Session` object, typically from the xref:pg-websocket-endpoints[_connect_ event] handler: +You may configure the WebSocket session behavior using the `org.eclipse.jetty.websocket.api.Session` APIs. +You want to do this as soon as you have access to the `Session` object, typically from the xref:pg-websocket-endpoints[_open_ event] handler: [source,java,indent=0] ---- @@ -180,44 +176,17 @@ Please refer to the `Session` link:{javadoc-url}/org/eclipse/jetty/websocket/api [[pg-websocket-session-send]] ===== Sending Data -To send data to the remote peer, you need to obtain the `RemoteEndpoint` object from the `Session`, and then use its API to send data. - -`RemoteEndpoint` offers two styles of APIs to send data: - -* Blocking APIs, where the call returns when the data has been sent, or throws an `IOException` if the data cannot be sent. -* Non-blocking APIs, where a callback object is notified when the data has been sent, or when there was a failure sending the data. - -[[pg-websocket-session-send-blocking]] -====== Blocking APIs - -`RemoteEndpoint` blocking APIs throw `IOException`: - -[source,java,indent=0] ----- -include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sendBlocking] ----- - -Blocking APIs are simpler to use since they can be invoked one after the other sequentially. - -[CAUTION] -==== -Sending large messages to the remote peer may cause the sending thread to block, possibly exhausting the thread pool. -Consider using non-blocking APIs for large messages. -==== - -[[pg-websocket-session-send-non-blocking]] -====== Non-Blocking APIs - -`RemoteEndpoint` non-blocking APIs have an additional callback parameter: +To send data to the remote peer, you can use the non-blocking APIs offered by `Session`. [source,java,indent=0] ---- include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=sendNonBlocking] ---- -<1> Non-blocking APIs require a `WriteCallback` parameter. +<1> Non-blocking APIs require a `Callback` parameter. <2> Note how the second send must be performed from inside the callback. <3> Sequential sends may throw `WritePendingException`. +// TODO: rewrite this section in light of maxOutgoingFrames. Non-blocking APIs are more difficult to use since you are required to meet the following condition: [IMPORTANT] @@ -248,49 +217,42 @@ While non-blocking APIs are more difficult to use, they don't block the sender t If you need to send large WebSocket messages, you may reduce the memory usage by streaming the message content. -Both blocking and non-blocking APIs offer `sendPartial*(...)` methods that allow you to send a chunk of the whole message at a time, therefore reducing the memory usage since it is not necessary to have the whole message `String` or `byte[]` in memory to send it. +The APIs offer `sendPartial*(\...)` methods that allow you to send a chunk of the whole message at a time, therefore reducing the memory usage since it is not necessary to have the whole message `String` or `ByteBuffer` in memory to send it. -Streaming sends using blocking APIs is quite simple: +The APIs for streaming the message content are non-blocking and therefore slightly more complicated to use, as you should wait (without blocking!) for the callbacks to complete. -[source,java,indent=0] ----- -include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamSendBlocking] ----- - -Streaming sends using non-blocking APIs is more complicated, as you should wait (without blocking!) for the callbacks to complete. - -Fortunately, Jetty provides you with the `IteratingCallback` utility class (described in more details xref:pg-arch-io-echo[in this section]) which greatly simplify the use of non-blocking APIs: +Fortunately, Jetty provides the `IteratingCallback` utility class (described in more details xref:pg-arch-io-echo[in this section]) which greatly simplify the use of non-blocking APIs: [source,java,indent=0] ---- include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamSendNonBlocking] ---- -<1> Implementing `WriteCallback` allows to pass `this` to `sendPartialBytes(...)`. -<2> The `process()` method is called iteratively when each `sendPartialBytes(...)` is completed. -<3> Send the message chunks. +<1> Implementing `Callback` allows to pass `this` to `sendPartialBinary(...)`. +<2> The `process()` method is called iteratively when each `sendPartialBinary(...)` is completed. +<3> Sends the message chunks. [[pg-websocket-session-ping]] ===== Sending Ping/Pong -The WebSocket protocol defines two special frame, named `Ping` and `Pong` that may be interesting to applications for these use cases: +The WebSocket protocol defines two special frame, named `PING` and `PONG` that may be interesting to applications for these use cases: * Calculate the round-trip time with the remote peer. * Keep the connection from being closed due to idle timeout -- a heartbeat-like mechanism. -To handle `Ping`/`Pong` events, you may implement interface `org.eclipse.jetty.ee9.websocket.api.WebSocketPingPongListener`. +To handle `PING`/`PONG` events, you may implement methods `Session.Listener.onWebSocketPing(ByteBuffer)` and/or `Session.Listener.onWebSocketPong(ByteBuffer)`. [NOTE] ==== -`Ping`/`Pong` events are not supported when using annotations. +`PING`/`PONG` events are also supported when using annotations via the `OnWebSocketFrame` annotation. ==== -`Ping` frames may contain opaque application bytes, and the WebSocket implementation replies to them with a `Pong` frame containing the same bytes: +`PING` frames may contain opaque application bytes, and the WebSocket implementation replies to them with a `PONG` frame containing the same bytes: [source,java,indent=0] ---- include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=pingPongListener] ---- -<1> The WebSocket endpoint class must implement `WebSocketPingPongListener` +<1> The WebSocket endpoint class must implement `Session.Listener` [[pg-websocket-session-close]] ===== Closing the Session diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java index 9433a1d4919..3065ffb1886 100644 --- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java +++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/WebSocketDocs.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.docs.programming; -import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; @@ -22,17 +21,13 @@ import java.time.Duration; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.NanoTime; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; -import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @SuppressWarnings("unused") @@ -40,14 +35,14 @@ public class WebSocketDocs { @SuppressWarnings("InnerClassMayBeStatic") // tag::listenerEndpoint[] - public class ListenerEndPoint implements WebSocketListener // <1> + public class ListenerEndPoint implements Session.Listener // <1> { private Session session; @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - // The WebSocket connection is established. + // The WebSocket endpoint has been opened. // Store the session to be able to send data to the remote peer. this.session = session; @@ -56,13 +51,13 @@ public class WebSocketDocs session.setMaxTextMessageSize(16 * 1024); // You may immediately send a message to the remote peer. - session.getRemote().sendString("connected", WriteCallback.NOOP); + session.sendText("connected", Callback.NOOP); } @Override public void onWebSocketClose(int statusCode, String reason) { - // The WebSocket connection is closed. + // The WebSocket endpoint has been closed. // You may dispose resources. disposeResources(); @@ -71,7 +66,7 @@ public class WebSocketDocs @Override public void onWebSocketError(Throwable cause) { - // The WebSocket connection failed. + // The WebSocket endpoint failed. // You may log the error. cause.printStackTrace(); @@ -87,11 +82,11 @@ public class WebSocketDocs // You may echo it back if it matches certain criteria. if (message.startsWith("echo:")) - session.getRemote().sendString(message.substring("echo:".length()), WriteCallback.NOOP); + session.sendText(message.substring("echo:".length()), Callback.NOOP); } @Override - public void onWebSocketBinary(byte[] payload, int offset, int length) + public void onWebSocketBinary(ByteBuffer payload, Callback callback) { // A WebSocket binary message is received. @@ -99,17 +94,18 @@ public class WebSocketDocs byte[] pngBytes = new byte[]{(byte)0x89, 'P', 'N', 'G'}; for (int i = 0; i < pngBytes.length; ++i) { - if (pngBytes[i] != payload[offset + i]) + if (pngBytes[i] != payload.get(i)) return; } - savePNGImage(payload, offset, length); + savePNGImage(payload); + callback.succeed(); } } // end::listenerEndpoint[] @SuppressWarnings("InnerClassMayBeStatic") // tag::streamingListenerEndpoint[] - public class StreamingListenerEndpoint implements WebSocketPartialListener + public class StreamingListenerEndpoint implements Session.Listener { private Path textPath; @@ -121,10 +117,12 @@ public class WebSocketDocs } @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { // Save chunks to file. appendToFile(payload, fin); + // Complete the callback. + callback.succeed(); } } // end::streamingListenerEndpoint[] @@ -136,10 +134,10 @@ public class WebSocketDocs { private Session session; - @OnWebSocketConnect // <2> - public void onConnect(Session session) + @OnWebSocketOpen // <2> + public void onOpen(Session session) { - // The WebSocket connection is established. + // The WebSocket endpoint has been opened. // Store the session to be able to send data to the remote peer. this.session = session; @@ -148,13 +146,13 @@ public class WebSocketDocs session.setMaxTextMessageSize(16 * 1024); // You may immediately send a message to the remote peer. - session.getRemote().sendString("connected", WriteCallback.NOOP); + session.sendText("connected", Callback.NOOP); } @OnWebSocketClose // <3> public void onClose(int statusCode, String reason) { - // The WebSocket connection is closed. + // The WebSocket endpoint has been closed. // You may dispose resources. disposeResources(); @@ -163,7 +161,7 @@ public class WebSocketDocs @OnWebSocketError // <4> public void onError(Throwable cause) { - // The WebSocket connection failed. + // The WebSocket endpoint failed. // You may log the error. cause.printStackTrace(); @@ -179,11 +177,11 @@ public class WebSocketDocs // You may echo it back if it matches certain criteria. if (message.startsWith("echo:")) - session.getRemote().sendString(message.substring("echo:".length()), WriteCallback.NOOP); + session.sendText(message.substring("echo:".length()), Callback.NOOP); } @OnWebSocketMessage // <5> - public void onBinaryMessage(byte[] payload, int offset, int length) + public void onBinaryMessage(ByteBuffer payload, Callback callback) { // A WebSocket binary message is received. @@ -191,10 +189,10 @@ public class WebSocketDocs byte[] pngBytes = new byte[]{(byte)0x89, 'P', 'N', 'G'}; for (int i = 0; i < pngBytes.length; ++i) { - if (pngBytes[i] != payload[offset + i]) + if (pngBytes[i] != payload.get(i)) return; } - savePNGImage(payload, offset, length); + savePNGImage(payload); } } // end::annotatedEndpoint[] @@ -222,10 +220,10 @@ public class WebSocketDocs @SuppressWarnings("InnerClassMayBeStatic") // tag::sessionConfigure[] - public class ConfigureEndpoint implements WebSocketListener + public class ConfigureEndpoint implements Session.Listener { @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { // Configure the max length of incoming messages. session.setMaxTextMessageSize(16 * 1024); @@ -236,38 +234,6 @@ public class WebSocketDocs } // end::sessionConfigure[] - @SuppressWarnings("InnerClassMayBeStatic") - // tag::sendBlocking[] - @WebSocket - public class BlockingSendEndpoint - { - @OnWebSocketMessage - public void onText(Session session, String text) - { - // Obtain the RemoteEndpoint APIs. - RemoteEndpoint remote = session.getRemote(); - - try - { - // Send textual data to the remote peer. - remote.sendString("data"); - - // Send binary data to the remote peer. - ByteBuffer bytes = readImageFromFile(); - remote.sendBytes(bytes); - - // Send a PING frame to the remote peer. - remote.sendPing(ByteBuffer.allocate(8).putLong(NanoTime.now()).flip()); - } - catch (IOException x) - { - // No need to rethrow or close the session. - System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send data", x); - } - } - } - // end::sendBlocking[] - @SuppressWarnings("InnerClassMayBeStatic") // tag::sendNonBlocking[] @WebSocket @@ -276,27 +242,24 @@ public class WebSocketDocs @OnWebSocketMessage public void onText(Session session, String text) { - // Obtain the RemoteEndpoint APIs. - RemoteEndpoint remote = session.getRemote(); - // Send textual data to the remote peer. - remote.sendString("data", new WriteCallback() // <1> + session.sendText("data", new Callback() // <1> { @Override - public void writeSuccess() + public void succeed() { // Send binary data to the remote peer. ByteBuffer bytes = readImageFromFile(); - remote.sendBytes(bytes, new WriteCallback() // <2> + session.sendBinary(bytes, new Callback() // <2> { @Override - public void writeSuccess() + public void succeed() { // Both sends succeeded. } @Override - public void writeFailed(Throwable x) + public void fail(Throwable x) { System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send binary data", x); } @@ -304,53 +267,18 @@ public class WebSocketDocs } @Override - public void writeFailed(Throwable x) + public void fail(Throwable x) { // No need to rethrow or close the session. System.getLogger("websocket").log(System.Logger.Level.WARNING, "could not send textual data", x); } }); - // remote.sendString("wrong", WriteCallback.NOOP); // May throw WritePendingException! <3> + // remote.sendString("wrong", Callback.NOOP); // May throw WritePendingException! <3> } } // end::sendNonBlocking[] - @SuppressWarnings("InnerClassMayBeStatic") - // tag::streamSendBlocking[] - @WebSocket - public class StreamSendBlockingEndpoint - { - @OnWebSocketMessage - public void onText(Session session, String text) - { - try - { - RemoteEndpoint remote = session.getRemote(); - while (true) - { - ByteBuffer chunk = readChunkToSend(); - if (chunk == null) - { - // No more bytes, finish the WebSocket message. - remote.sendPartialBytes(ByteBuffer.allocate(0), true); - break; - } - else - { - // Send the chunk. - remote.sendPartialBytes(chunk, false); - } - } - } - catch (IOException x) - { - x.printStackTrace(); - } - } - } - // end::streamSendBlocking[] - @SuppressWarnings("InnerClassMayBeStatic") // tag::streamSendNonBlocking[] @WebSocket @@ -359,18 +287,17 @@ public class WebSocketDocs @OnWebSocketMessage public void onText(Session session, String text) { - RemoteEndpoint remote = session.getRemote(); - new Sender(remote).iterate(); + new Sender(session).iterate(); } - private class Sender extends IteratingCallback implements WriteCallback // <1> + private class Sender extends IteratingCallback implements Callback // <1> { - private final RemoteEndpoint remote; + private final Session session; private boolean finished; - private Sender(RemoteEndpoint remote) + private Sender(Session session) { - this.remote = remote; + this.session = session; } @Override @@ -383,27 +310,27 @@ public class WebSocketDocs if (chunk == null) { // No more bytes, finish the WebSocket message. - remote.sendPartialBytes(ByteBuffer.allocate(0), true, this); // <3> + session.sendPartialBinary(ByteBuffer.allocate(0), true, this); // <3> finished = true; return Action.SCHEDULED; } else { // Send the chunk. - remote.sendPartialBytes(ByteBuffer.allocate(0), false, this); // <3> + session.sendPartialBinary(ByteBuffer.allocate(0), false, this); // <3> return Action.SCHEDULED; } } @Override - public void writeSuccess() + public void succeed() { // When the send succeeds, succeed this IteratingCallback. succeeded(); } @Override - public void writeFailed(Throwable x) + public void fail(Throwable x) { // When the send fails, fail this IteratingCallback. failed(x); @@ -420,14 +347,14 @@ public class WebSocketDocs @SuppressWarnings("InnerClassMayBeStatic") // tag::pingPongListener[] - public class RoundTripListenerEndpoint implements WebSocketPingPongListener // <1> + public class RoundTripListenerEndpoint implements Session.Listener // <1> { @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { // Send to the remote peer the local nanoTime. ByteBuffer buffer = ByteBuffer.allocate(8).putLong(NanoTime.now()).flip(); - session.getRemote().sendPing(buffer, WriteCallback.NOOP); + session.sendPing(buffer, Callback.NOOP); } @Override @@ -451,7 +378,7 @@ public class WebSocketDocs public void onText(Session session, String text) { if ("close".equalsIgnoreCase(text)) - session.close(StatusCode.NORMAL, "bye"); + session.close(StatusCode.NORMAL, "bye", Callback.NOOP); } } // end::sessionClose[] @@ -476,7 +403,7 @@ public class WebSocketDocs { } - private static void savePNGImage(byte[] payload, int offset, int length) + private static void savePNGImage(ByteBuffer byteBuffer) { } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java index 0b02c2e7e08..51ebf73455e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java @@ -152,13 +152,12 @@ public interface CoreSession extends OutgoingFrames, IncomingFrames, Configurati void abort(); /** - * Manage flow control by indicating demand for handling Frames. A call to - * {@link FrameHandler#onFrame(Frame, Callback)} will only be made if a - * corresponding demand has been signaled. It is an error to call this method - * if {@link FrameHandler#isAutoDemanding()} returns true. + *

Manages flow control by indicating demand for WebSocket frames.

+ *

A call to {@link FrameHandler#onFrame(Frame, Callback)} will only + * be made if there is demand.

* - * @param n The number of frames that can be handled (in sequential calls to - * {@link FrameHandler#onFrame(Frame, Callback)}). May not be negative. + * @param n the number of frames that can be handled in sequential calls to + * {@link FrameHandler#onFrame(Frame, Callback)}, must be positive. */ void demand(long n); @@ -235,7 +234,8 @@ public interface CoreSession extends OutgoingFrames, IncomingFrames, Configurati @Override public ByteBufferPool getByteBufferPool() { - return null; + WebSocketComponents components = getWebSocketComponents(); + return components != null ? components.getByteBufferPool() : null; } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java index 545fcb6f268..3db3a5b5b3b 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/FrameHandler.java @@ -16,102 +16,113 @@ package org.eclipse.jetty.websocket.core; import org.eclipse.jetty.util.Callback; /** - * Interface for local WebSocket Endpoint Frame handling. - * - *

- * This is the receiver of Parsed Frames. It is implemented by the Application (or Application API layer or Framework) - * as the primary API to/from the Core websocket implementation. The instance to be used for each websocket connection - * is instantiated by the application, either: - *

+ *

Handles incoming WebSocket frames for a given endpoint.

+ *

FrameHandler is the receiver of parsed WebSocket frames. + * It is implemented by application code as the primary API to + * interact with the WebSocket implementation.

+ *

The FrameHandler instance to be used for each WebSocket + * connection is instantiated by the application, either:

* - *

- * Once instantiated the FrameHandler follows is used as follows: - *

+ *

Once instantiated the FrameHandler is used as follows:

* + *

FrameHandler is responsible to manage the demand for more + * WebSocket frames, either directly by calling {@link CoreSession#demand(long)} + * or by delegating the demand management to other components.

*/ public interface FrameHandler extends IncomingFrames { /** - * Async notification that Connection is being opened. - *

- * FrameHandler can write during this call, but can not receive frames until the callback is succeeded. - *

- *

- * If the FrameHandler succeeds the callback we transition to OPEN state and can now receive frames if auto-demanding, - * or can now call {@link CoreSession#demand(long)} to receive frames if it is not auto-demanding. - * If the FrameHandler fails the callback a close frame will be sent with {@link CloseStatus#SERVER_ERROR} and - * the connection will be closed.
- *

+ *

Invoked when the WebSocket connection is opened.

+ *

It is allowed to send WebSocket frames via + * {@link CoreSession#sendFrame(Frame, Callback, boolean)}. + *

WebSocket frames cannot be received until a call to + * {@link CoreSession#demand(long)} is made.

+ *

If the callback argument is failed, the implementation + * sends a CLOSE frame with {@link CloseStatus#SERVER_ERROR}, + * and the connection will be closed.

* * @param coreSession the session associated with this connection. - * @param callback the callback to indicate success in processing (or failure) + * @param callback the callback to indicate success or failure of + * the processing of this event. */ void onOpen(CoreSession coreSession, Callback callback); /** - * Receiver of all Frames. - * This method will never be called in parallel for the same session and will be called - * sequentially to satisfy all outstanding demand signaled by calls to - * {@link CoreSession#demand(long)}. - * Control and Data frames are passed to this method. - * Close frames may be responded to by the handler, but if an appropriate close response is not - * sent once the callback is succeeded, then a response close will be generated and sent. + *

Invoked when a WebSocket frame is received.

+ *

This method will never be called concurrently for the + * same session; will be called sequentially to satisfy the + * outstanding demand signaled by calls to + * {@link CoreSession#demand(long)}.

+ *

Both control and data frames are passed to this method.

+ *

CLOSE frames may be responded from this method, but if + * they are not responded, then the implementation will respond + * when the callback is completed.

+ *

The callback argument must be completed to indicate + * that the buffers associated with the frame can be recycled.

+ *

Additional WebSocket frames (of any type, including CLOSE + * frames) cannot be received until a call to + * {@link CoreSession#demand(long)} is made.

* - * @param frame the raw frame - * @param callback the callback to indicate success in processing frame (or failure) + * @param frame the WebSocket frame. + * @param callback the callback to indicate success or failure of + * the processing of this event. */ void onFrame(Frame frame, Callback callback); /** - * An error has occurred or been detected in websocket-core and being reported to FrameHandler. - * A call to onError will be followed by a call to {@link #onClosed(CloseStatus, Callback)} giving the close status - * derived from the error. This will not be called more than once, {@link #onClosed(CloseStatus, Callback)} + *

Invoked when an error has occurred or has been detected.

+ *

A call to this method will be followed by a call to + * {@link #onClosed(CloseStatus, Callback)} with the close status + * derived from the error.

+ *

This method will not be called more than once, {@link #onClosed(CloseStatus, Callback)} * will be called on the callback completion. * - * @param cause the reason for the error - * @param callback the callback to indicate success in processing (or failure) + * @param cause the error cause + * @param callback the callback to indicate success or failure of + * the processing of this event. */ void onError(Throwable cause, Callback callback); /** - * This is the Close Handshake Complete event. - *

- * The connection is now closed, no reading or writing is possible anymore. - * Implementations of FrameHandler can cleanup their resources for this connection now. - * This method will be called only once. - *

+ *

Invoked when a WebSocket close event happened.

+ *

The WebSocket connection is closed, no reading or writing + * is possible anymore.

+ *

Implementations of this method may cleanup resources + * that have been allocated.

+ *

This method will not be called more than once.

* - * @param closeStatus the close status received from remote, or in the case of abnormal closure from local. - * @param callback the callback to indicate success in processing (or failure) + * @param closeStatus the close status received from the remote peer, + * or generated locally in the case of abnormal closures. + * @param callback the callback to indicate success or failure of + * the processing of this event. */ void onClosed(CloseStatus closeStatus, Callback callback); - - /** - * Does the FrameHandler manage it's own demand? - * - * @return true if demand will be managed by an automatic call to demand(1) after every succeeded callback passed to - * {@link #onFrame(Frame, Callback)}. If false the FrameHandler will need to manage its own demand by calling - * {@link CoreSession#demand(long)} when it is willing to receive new Frames. - */ - default boolean isAutoDemanding() - { - return true; - } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java index 8d3eef06753..2b70c78aec8 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/OutgoingFrames.java @@ -27,7 +27,7 @@ public interface OutgoingFrames * layers and extensions present in the implementation. *

* If you are implementing a mutation, you are obliged to handle - * the incoming WriteCallback appropriately. + * the incoming Callback appropriately. * * @param frame the frame to eventually write to the network layer. * @param callback the callback to notify when the frame is written. diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketCoreSession.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketCoreSession.java index 4715542f774..da5738982ae 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketCoreSession.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketCoreSession.java @@ -55,7 +55,6 @@ public class WebSocketCoreSession implements CoreSession, Dumpable private final WebSocketSessionState sessionState = new WebSocketSessionState(); private final FrameHandler handler; private final Negotiated negotiated; - private final boolean autoDemanding; private final Flusher flusher = new Flusher(this); private final ExtensionStack extensionStack; @@ -80,7 +79,6 @@ public class WebSocketCoreSession implements CoreSession, Dumpable this.handler = handler; this.behavior = behavior; this.negotiated = negotiated; - this.autoDemanding = handler.isAutoDemanding(); extensionStack = negotiated.getExtensions(); extensionStack.initialize(new IncomingAdaptor(), new OutgoingAdaptor(), this); } @@ -113,14 +111,6 @@ public class WebSocketCoreSession implements CoreSession, Dumpable } } - /** - * @return True if the sessions handling is demanding. - */ - public boolean isAutoDemanding() - { - return autoDemanding; - } - public ExtensionStack getExtensionStack() { return negotiated.getExtensions(); @@ -382,21 +372,18 @@ public class WebSocketCoreSession implements CoreSession, Dumpable if (LOG.isDebugEnabled()) LOG.debug("ConnectionState: Transition to CONNECTED"); - Callback openCallback = Callback.from( - () -> - { - sessionState.onOpen(); - if (LOG.isDebugEnabled()) - LOG.debug("ConnectionState: Transition to OPEN"); - if (autoDemanding) - autoDemand(); - }, - x -> - { - if (LOG.isDebugEnabled()) - LOG.debug("Error during OPEN", x); - processHandlerError(new CloseException(CloseStatus.SERVER_ERROR, x), NOOP); - }); + Callback openCallback = Callback.from(() -> + { + sessionState.onOpen(); + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: Transition to OPEN"); + }, + x -> + { + if (LOG.isDebugEnabled()) + LOG.debug("Error during OPEN", x); + processHandlerError(new CloseException(CloseStatus.SERVER_ERROR, x), NOOP); + }); try { @@ -417,16 +404,9 @@ public class WebSocketCoreSession implements CoreSession, Dumpable @Override public void demand(long n) { - if (autoDemanding) - throw new IllegalStateException("FrameHandler is not demanding: " + this); getExtensionStack().demand(n); } - public void autoDemand() - { - getExtensionStack().demand(1); - } - @Override public boolean isRsv1Used() { @@ -655,13 +635,7 @@ public class WebSocketCoreSession implements CoreSession, Dumpable // Handle inbound frame if (frame.getOpCode() != OpCode.CLOSE) { - Callback handlerCallback = !isAutoDemanding() ? callback : Callback.from(() -> - { - callback.succeeded(); - autoDemand(); - }, callback::failed); - - handle(() -> handler.onFrame(frame, handlerCallback)); + handle(() -> handler.onFrame(frame, callback)); return; } @@ -677,22 +651,21 @@ public class WebSocketCoreSession implements CoreSession, Dumpable } else { - closeCallback = Callback.from( - () -> + closeCallback = Callback.from(() -> + { + if (sessionState.isOutputOpen()) { - if (sessionState.isOutputOpen()) - { - CloseStatus closeStatus = CloseStatus.getCloseStatus(frame); - if (LOG.isDebugEnabled()) - LOG.debug("ConnectionState: sending close response {}", closeStatus); - close(closeStatus == null ? CloseStatus.NO_CODE_STATUS : closeStatus, callback); - } - else - { - callback.succeeded(); - } - }, - x -> processHandlerError(x, callback)); + CloseStatus closeStatus = CloseStatus.getCloseStatus(frame); + if (LOG.isDebugEnabled()) + LOG.debug("ConnectionState: sending close response {}", closeStatus); + close(closeStatus == null ? CloseStatus.NO_CODE_STATUS : closeStatus, callback); + } + else + { + callback.succeeded(); + } + }, + x -> processHandlerError(x, callback)); } handler.onFrame(frame, closeCallback); @@ -783,7 +756,7 @@ public class WebSocketCoreSession implements CoreSession, Dumpable @Override public String toString() { - return String.format("WSCoreSession@%x{%s,%s,%s,af=%b,i/o=%d/%d,fs=%d}->%s", + return String.format("WebSocketCoreSession@%x{%s,%s,%s,af=%b,i/o=%d/%d,fs=%d}->%s", hashCode(), behavior, sessionState, diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/MessageHandler.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/MessageHandler.java index 326a6c1dadc..81bf7f7585c 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/MessageHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/MessageHandler.java @@ -32,11 +32,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A utility implementation of FrameHandler that defragments - * text frames into a String message before calling {@link #onText(String, Callback)}. - * Flow control is by default automatic, but an implementation - * may extend {@link #isAutoDemanding()} to return false and then explicitly control - * demand with calls to {@link CoreSession#demand(long)}. + *

A utility implementation of FrameHandler that aggregates TEXT frames + * into a String message before calling {@link #onText(String, Callback)}. */ public class MessageHandler implements FrameHandler { @@ -120,6 +117,7 @@ public class MessageHandler implements FrameHandler this.coreSession = coreSession; callback.succeeded(); + coreSession.demand(1); } @Override @@ -130,30 +128,26 @@ public class MessageHandler implements FrameHandler switch (frame.getOpCode()) { - case OpCode.CLOSE: - onCloseFrame(frame, callback); - break; - case OpCode.PING: - onPingFrame(frame, callback); - break; - case OpCode.PONG: - onPongFrame(frame, callback); - break; - case OpCode.TEXT: + case OpCode.CLOSE -> onCloseFrame(frame, callback); + case OpCode.PING -> onPingFrame(frame, callback); + case OpCode.PONG -> onPongFrame(frame, callback); + case OpCode.TEXT -> + { dataType = OpCode.TEXT; onTextFrame(frame, callback); - break; - case OpCode.BINARY: + } + case OpCode.BINARY -> + { dataType = OpCode.BINARY; onBinaryFrame(frame, callback); - break; - case OpCode.CONTINUATION: + } + case OpCode.CONTINUATION -> + { onContinuationFrame(frame, callback); if (frame.isFin()) dataType = OpCode.UNDEFINED; - break; - default: - callback.failed(new IllegalStateException()); + } + default -> callback.failed(new IllegalStateException()); } } @@ -199,7 +193,8 @@ public class MessageHandler implements FrameHandler long currentSize = frame.getPayload().remaining() + textBuffer.length(); if (currentSize > maxSize) throw new MessageTooLargeException("Message larger than " + maxSize + " bytes"); - textBuffer.append(frame.getPayload()); + else + textBuffer.append(frame.getPayload()); } if (frame.isFin()) @@ -211,8 +206,11 @@ public class MessageHandler implements FrameHandler { if (textBuffer.hasCodingErrors()) throw new BadPayloadException("Invalid UTF-8"); - callback.succeeded(); + else + callback.succeeded(); } + + coreSession.demand(1); } catch (Throwable t) { @@ -232,8 +230,8 @@ public class MessageHandler implements FrameHandler long currentSize = frame.getPayload().remaining() + binaryBuffer.size(); if (currentSize > maxSize) throw new MessageTooLargeException("Message larger than " + maxSize + " bytes"); - - BufferUtil.writeTo(frame.getPayload(), binaryBuffer); + else + BufferUtil.writeTo(frame.getPayload(), binaryBuffer); } if (frame.isFin()) @@ -245,6 +243,8 @@ public class MessageHandler implements FrameHandler { callback.succeeded(); } + + coreSession.demand(1); } catch (Throwable t) { @@ -256,27 +256,21 @@ public class MessageHandler implements FrameHandler { switch (dataType) { - case OpCode.BINARY: - onBinaryFrame(frame, callback); - break; - - case OpCode.TEXT: - onTextFrame(frame, callback); - break; - - default: - throw new IllegalStateException(); + case OpCode.BINARY -> onBinaryFrame(frame, callback); + case OpCode.TEXT -> onTextFrame(frame, callback); + default -> throw new IllegalStateException(); } } protected void onPingFrame(Frame frame, Callback callback) { - coreSession.sendFrame(new Frame(OpCode.PONG, true, frame.getPayload()), callback, false); + coreSession.sendFrame(new Frame(OpCode.PONG, true, frame.getPayload()), Callback.from(() -> coreSession.demand(1), callback), false); } protected void onPongFrame(Frame frame, Callback callback) { callback.succeeded(); + coreSession.demand(1); } protected void onCloseFrame(Frame frame, Callback callback) @@ -346,7 +340,7 @@ public class MessageHandler implements FrameHandler int i = 0; @Override - protected Action process() throws Throwable + protected Action process() { if (i + 1 > parts.length) return Action.SUCCEEDED; @@ -398,7 +392,7 @@ public class MessageHandler implements FrameHandler int i = 0; @Override - protected Action process() throws Throwable + protected Action process() { if (i + 1 > parts.length) return Action.SUCCEEDED; diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/AbstractMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/AbstractMessageSink.java index 3fbed69422b..819b1a6154a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/AbstractMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/AbstractMessageSink.java @@ -18,14 +18,81 @@ import java.util.Objects; import org.eclipse.jetty.websocket.core.CoreSession; +/** + *

Abstract implementation of {@link MessageSink}.

+ *

Management of demand for WebSocket frames may either be entirely managed + * by the {@link MessageSink} implementation ({@code autoDemand==true}); or + * it may be managed collaboratively between the application and the + * {@link MessageSink} implementation ({@code autoDemand==true}).

+ *

{@link MessageSink} implementations must handle the demand for WebSocket + * frames in this way:

+ * + *

Method {@link #autoDemand()} helps to manage the demand after the + * invocation of the application function returns successfully.

+ */ public abstract class AbstractMessageSink implements MessageSink { - protected final CoreSession session; - protected final MethodHandle methodHandle; + private final CoreSession session; + private final MethodHandle methodHandle; + private final boolean autoDemand; - public AbstractMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link MessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke + * @param autoDemand whether this {@link MessageSink} manages demand automatically + * as explained in {@link AbstractMessageSink} + */ + public AbstractMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { this.session = Objects.requireNonNull(session, "CoreSession"); this.methodHandle = Objects.requireNonNull(methodHandle, "MethodHandle"); + this.autoDemand = autoDemand; + } + + /** + * @return the WebSocket session + */ + public CoreSession getCoreSession() + { + return session; + } + + /** + * @return the application function + */ + public MethodHandle getMethodHandle() + { + return methodHandle; + } + + /** + * @return whether this {@link MessageSink} automatically demands for more + * WebSocket frames after the invocation of the application function has returned. + */ + public boolean isAutoDemand() + { + return autoDemand; + } + + /** + *

If {@link #isAutoDemand()} then demands for one more WebSocket frame + * via {@link CoreSession#demand(long)}; otherwise it is a no-operation, + * because the demand is explicitly managed by the application function.

+ */ + protected void autoDemand() + { + if (isAutoDemand()) + getCoreSession().demand(1); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteArrayMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteArrayMessageSink.java index 6724c856ed3..aec69940e33 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteArrayMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteArrayMessageSink.java @@ -25,22 +25,31 @@ import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.MessageTooLargeException; +/** + *

A {@link MessageSink} implementation that accumulates BINARY frames + * into a message that is then delivered to the application function + * passed to the constructor in the form of a {@code byte[]}.

+ */ public class ByteArrayMessageSink extends AbstractMessageSink { - private static final byte[] EMPTY_BUFFER = new byte[0]; - private ByteBufferCallbackAccumulator out; + private ByteBufferCallbackAccumulator accumulator; - public ByteArrayMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link ByteArrayMessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke when a new message has been assembled + * @param autoDemand whether this {@link MessageSink} manages demand automatically + */ + public ByteArrayMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); // This uses the offset length byte array signature not supported by jakarta websocket. // The jakarta layer instead uses decoders for whole byte array messages instead of this message sink. MethodType onMessageType = MethodType.methodType(Void.TYPE, byte[].class, int.class, int.class); if (methodHandle.type().changeReturnType(void.class) != onMessageType.changeReturnType(void.class)) - { throw InvalidSignatureException.build(onMessageType, methodHandle.type()); - } } @Override @@ -48,62 +57,59 @@ public class ByteArrayMessageSink extends AbstractMessageSink { try { - long size = (out == null ? 0 : out.getLength()) + frame.getPayloadLength(); - long maxBinaryMessageSize = session.getMaxBinaryMessageSize(); - if (maxBinaryMessageSize > 0 && size > maxBinaryMessageSize) + long size = (accumulator == null ? 0 : accumulator.getLength()) + frame.getPayloadLength(); + long maxSize = getCoreSession().getMaxBinaryMessageSize(); + if (maxSize > 0 && size > maxSize) { - throw new MessageTooLargeException( - String.format("Binary message too large: (actual) %,d > (configured max binary message size) %,d", size, maxBinaryMessageSize)); - } - - // If we are fin and no OutputStream has been created we don't need to aggregate. - if (frame.isFin() && (out == null)) - { - if (frame.hasPayload()) - { - byte[] buf = BufferUtil.toArray(frame.getPayload()); - methodHandle.invoke(buf, 0, buf.length); - } - else - methodHandle.invoke(EMPTY_BUFFER, 0, 0); - - callback.succeeded(); - session.demand(1); + callback.failed(new MessageTooLargeException(String.format("Binary message too large: %,d > %,d", size, maxSize))); return; } - // Aggregate the frame payload. - if (frame.hasPayload()) + ByteBuffer payload = frame.getPayload(); + if (frame.isFin() && accumulator == null) { - ByteBuffer payload = frame.getPayload(); - if (out == null) - out = new ByteBufferCallbackAccumulator(); - out.addEntry(payload, callback); + byte[] buf = BufferUtil.toArray(payload); + getMethodHandle().invoke(buf, 0, buf.length); + callback.succeeded(); + autoDemand(); + return; } - // If the methodHandle throws we don't want to fail callback twice. - callback = Callback.NOOP; + if (!frame.isFin() && !frame.hasPayload()) + { + callback.succeeded(); + getCoreSession().demand(1); + return; + } + + if (accumulator == null) + accumulator = new ByteBufferCallbackAccumulator(); + accumulator.addEntry(payload, callback); + if (frame.isFin()) { - byte[] buf = out.takeByteArray(); - methodHandle.invoke(buf, 0, buf.length); + // Do not complete twice the callback if the invocation fails. + callback = Callback.NOOP; + byte[] buf = accumulator.takeByteArray(); + getMethodHandle().invoke(buf, 0, buf.length); + autoDemand(); + } + else + { + getCoreSession().demand(1); } - session.demand(1); } catch (Throwable t) { - if (out != null) - out.fail(t); + if (accumulator != null) + accumulator.fail(t); callback.failed(t); } finally { if (frame.isFin()) - { - // reset - out = null; - } + accumulator = null; } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteBufferMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteBufferMessageSink.java index 1da91ad09c2..1fa44b8c924 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteBufferMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ByteBufferMessageSink.java @@ -16,32 +16,46 @@ package org.eclipse.jetty.websocket.core.messages; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteBuffer; -import java.util.Objects; import org.eclipse.jetty.io.ByteBufferCallbackAccumulator; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.RetainableByteBuffer; -import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.MessageTooLargeException; +/** + *

A {@link MessageSink} implementation that accumulates BINARY frames + * into a message that is then delivered to the application function + * passed to the constructor in the form of a {@link ByteBuffer}.

+ */ public class ByteBufferMessageSink extends AbstractMessageSink { - private ByteBufferCallbackAccumulator out; + private ByteBufferCallbackAccumulator accumulator; - public ByteBufferMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link ByteBufferMessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke when a new message has been assembled + * @param autoDemand whether this {@link MessageSink} manages demand automatically + */ + public ByteBufferMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + this(session, methodHandle, autoDemand, true); + } - // Validate onMessageMethod - Objects.requireNonNull(methodHandle, "MethodHandle"); - MethodType onMessageType = MethodType.methodType(Void.TYPE, ByteBuffer.class); - if (methodHandle.type() != onMessageType) + protected ByteBufferMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand, boolean validateSignature) + { + super(session, methodHandle, autoDemand); + + if (validateSignature) { - throw InvalidSignatureException.build(onMessageType, methodHandle.type()); + MethodType onMessageType = MethodType.methodType(Void.TYPE, ByteBuffer.class); + if (methodHandle.type() != onMessageType) + throw InvalidSignatureException.build(onMessageType, methodHandle.type()); } } @@ -50,69 +64,64 @@ public class ByteBufferMessageSink extends AbstractMessageSink { try { - long size = (out == null ? 0 : out.getLength()) + frame.getPayloadLength(); - long maxBinaryMessageSize = session.getMaxBinaryMessageSize(); - if (maxBinaryMessageSize > 0 && size > maxBinaryMessageSize) + long size = (accumulator == null ? 0 : accumulator.getLength()) + frame.getPayloadLength(); + long maxSize = getCoreSession().getMaxBinaryMessageSize(); + if (maxSize > 0 && size > maxSize) { - throw new MessageTooLargeException(String.format("Binary message too large: (actual) %,d > (configured max binary message size) %,d", - size, maxBinaryMessageSize)); - } - - // If we are fin and no OutputStream has been created we don't need to aggregate. - if (frame.isFin() && (out == null)) - { - if (frame.hasPayload()) - methodHandle.invoke(frame.getPayload()); - else - methodHandle.invoke(BufferUtil.EMPTY_BUFFER); - - callback.succeeded(); - session.demand(1); + callback.failed(new MessageTooLargeException(String.format("Binary message too large: %,d > %,d", size, maxSize))); return; } - // Aggregate the frame payload. - if (frame.hasPayload()) + if (frame.isFin() && accumulator == null) { - ByteBuffer payload = frame.getPayload(); - if (out == null) - out = new ByteBufferCallbackAccumulator(); - out.addEntry(payload, callback); + invoke(getMethodHandle(), frame.getPayload(), callback); + autoDemand(); + return; } - // If the methodHandle throws we don't want to fail callback twice. - callback = Callback.NOOP; + if (!frame.isFin() && !frame.hasPayload()) + { + callback.succeeded(); + getCoreSession().demand(1); + return; + } + + if (accumulator == null) + accumulator = new ByteBufferCallbackAccumulator(); + accumulator.addEntry(frame.getPayload(), callback); + if (frame.isFin()) { - ByteBufferPool bufferPool = session.getByteBufferPool(); - RetainableByteBuffer buffer = bufferPool.acquire(out.getLength(), false); + ByteBufferPool bufferPool = getCoreSession().getByteBufferPool(); + RetainableByteBuffer buffer = bufferPool.acquire(accumulator.getLength(), false); ByteBuffer byteBuffer = buffer.getByteBuffer(); - out.writeTo(byteBuffer); - - try - { - methodHandle.invoke(byteBuffer); - } - finally - { - buffer.release(); - } + accumulator.writeTo(byteBuffer); + callback = Callback.from(buffer::release); + invoke(getMethodHandle(), byteBuffer, callback); + autoDemand(); + } + else + { + // Did not call the application so must explicitly demand here. + getCoreSession().demand(1); } - - session.demand(1); } catch (Throwable t) { - if (out != null) - out.fail(t); + if (accumulator != null) + accumulator.fail(t); callback.failed(t); } finally { if (frame.isFin()) - { - out = null; - } + accumulator = null; } } + + protected void invoke(MethodHandle methodHandle, ByteBuffer byteBuffer, Callback callback) throws Throwable + { + methodHandle.invoke(byteBuffer); + callback.succeeded(); + } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/DispatchedMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/DispatchedMessageSink.java index ec382cd4553..57c0006f57e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/DispatchedMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/DispatchedMessageSink.java @@ -14,6 +14,8 @@ package org.eclipse.jetty.websocket.core.messages; import java.io.Closeable; +import java.io.InputStream; +import java.io.Reader; import java.lang.invoke.MethodHandle; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -24,144 +26,89 @@ import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; /** - * Centralized logic for Dispatched Message Handling. - *

- * A Dispatched MessageSink can consist of 1 or more {@link #accept(Frame, Callback)} calls. - *

- * The first {@link #accept(Frame, Callback)} in a message will trigger a dispatch to the - * function specified in the constructor. - *

- * The completion of the dispatched function call is the sign that the next message is suitable - * for processing from the network. (The connection fillAndParse should remain idle for the - * NEXT message until such time as the dispatched function call has completed) - *

- *

- * There are a few use cases we need to handle. - *

- *

- * 1. Normal Processing - *

- *
- *     Connection Thread | DispatchedMessageSink | Thread 2
- *     TEXT                accept()
- *                          - dispatch -           function.read(stream)
- *     CONT                accept()                stream.read()
- *     CONT                accept()                stream.read()
- *     CONT=fin            accept()                stream.read()
- *                           EOF                   stream.read EOF
- *     IDLE
- *                                                 exit method
- *     RESUME(NEXT MSG)
- * 
- *

- * 2. Early Exit (with no activity) - *

- *
- *     Connection Thread | DispatchedMessageSink | Thread 2
- *     TEXT                accept()
- *                          - dispatch -           function.read(stream)
- *     CONT                accept()                exit method (normal return)
- *     IDLE
- *     TIMEOUT
- * 
- *

- * 3. Early Exit (due to exception) - *

- *
- *     Connection Thread | DispatchedMessageSink | Thread 2
- *     TEXT                accept()
- *                          - dispatch -           function.read(stream)
- *     CONT                accept()                exit method (throwable)
- *     callback.fail()
- *     endpoint.onError()
- *     close(error)
- * 
- *

- * 4. Early Exit (with Custom Threading) - *

- *
- *     Connection Thread | DispatchedMessageSink | Thread 2              | Thread 3
- *     TEXT                accept()
- *                          - dispatch -           function.read(stream)
- *                                                 thread.new(stream)      stream.read()
- *                                                 exit method
- *     CONT                accept()                                        stream.read()
- *     CONT                accept()                                        stream.read()
- *     CONT=fin            accept()                                        stream.read()
- *                           EOF                                           stream.read EOF
- *     RESUME(NEXT MSG)
- * 
+ *

A partial implementation of {@link MessageSink} for methods that consume WebSocket + * messages using blocking stream APIs, typically via {@link InputStream} or {@link Reader}.

+ *

The first call to {@link #accept(Frame, Callback)} triggers the application function + * specified in the constructor to be invoked in a different thread.

+ *

Subsequent calls to {@link #accept(Frame, Callback)} feed a nested {@link MessageSink} + * that in turns feeds the {@link InputStream} or {@link Reader} stream.

+ *

Implementations of this class must manage the demand for WebSocket frames, and + * therefore must always be auto-demanding.

+ *

Upon return from the application function, the stream is closed. + * This means that the stream must be consumed synchronously within the invocation of the + * application function.

+ *

The demand for the next WebSocket message is performed when both the application + * function has returned and the last frame has been consumed (signaled by completing the + * callback associated with the frame).

+ *

Throwing from the application function results in the WebSocket connection to be + * closed.

*/ public abstract class DispatchedMessageSink extends AbstractMessageSink { - private CompletableFuture dispatchComplete; - private MessageSink typeSink; private final Executor executor; + private volatile CompletableFuture dispatchComplete; + private MessageSink typeSink; - public DispatchedMessageSink(CoreSession session, MethodHandle methodHandle) + public DispatchedMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); + if (!autoDemand) + throw new IllegalArgumentException("%s must be auto-demanding".formatted(getClass().getSimpleName())); executor = session.getWebSocketComponents().getExecutor(); } - public abstract MessageSink newSink(Frame frame); + public abstract MessageSink newMessageSink(); public void accept(Frame frame, final Callback callback) { if (typeSink == null) { - typeSink = newSink(frame); + typeSink = newMessageSink(); dispatchComplete = new CompletableFuture<>(); - // Dispatch to end user function (will likely start with blocking for data/accept). - // If the MessageSink can be closed do this after invoking and before completing the CompletableFuture. + // Call the endpoint method in a different + // thread, since it will use blocking APIs. executor.execute(() -> { try { - methodHandle.invoke(typeSink); - if (typeSink instanceof Closeable) - IO.close((Closeable)typeSink); - + getMethodHandle().invoke(typeSink); + if (typeSink instanceof Closeable closeable) + IO.close(closeable); dispatchComplete.complete(null); } catch (Throwable throwable) { - if (typeSink instanceof Closeable) - IO.close((Closeable)typeSink); - + typeSink.fail(throwable); dispatchComplete.completeExceptionally(throwable); } }); } - Callback frameCallback; + Callback frameCallback = callback; if (frame.isFin()) { - // This is the final frame we should wait for the frame callback and the dispatched thread. - Callback.Completable finComplete = Callback.Completable.from(callback); - frameCallback = finComplete; - CompletableFuture.allOf(dispatchComplete, finComplete).whenComplete((aVoid, throwable) -> + // Wait for both the frame callback and the dispatched thread. + Callback.Completable frameComplete = Callback.Completable.from(callback); + frameCallback = frameComplete; + CompletableFuture.allOf(dispatchComplete, frameComplete).whenComplete((result, failure) -> { typeSink = null; dispatchComplete = null; - if (throwable == null) - session.demand(1); + + // The nested MessageSink manages the demand until the last + // frame, while this MessageSink manages the demand when both + // the last frame and the dispatched thread are completed. + if (failure == null) + autoDemand(); }); } - else - { - frameCallback = new Callback.Nested(callback) - { - @Override - public void succeeded() - { - super.succeeded(); - session.demand(1); - } - }; - } typeSink.accept(frame, frameCallback); } + + public boolean isDispatched() + { + return dispatchComplete != null; + } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/InputStreamMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/InputStreamMessageSink.java index 0bcf0dab225..72c65802387 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/InputStreamMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/InputStreamMessageSink.java @@ -16,18 +16,17 @@ package org.eclipse.jetty.websocket.core.messages; import java.lang.invoke.MethodHandle; import org.eclipse.jetty.websocket.core.CoreSession; -import org.eclipse.jetty.websocket.core.Frame; public class InputStreamMessageSink extends DispatchedMessageSink { - public InputStreamMessageSink(CoreSession session, MethodHandle methodHandle) + public InputStreamMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); } @Override - public MessageSink newSink(Frame frame) + public MessageSink newMessageSink() { - return new MessageInputStream(); + return new MessageInputStream(getCoreSession()); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageInputStream.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageInputStream.java index 6024c22b337..afd93bde983 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageInputStream.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageInputStream.java @@ -17,14 +17,15 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Objects; import java.util.concurrent.TimeUnit; -import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.thread.AutoLock; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,43 +39,59 @@ import org.slf4j.LoggerFactory; public class MessageInputStream extends InputStream implements MessageSink { private static final Logger LOG = LoggerFactory.getLogger(MessageInputStream.class); - private static final Entry EOF = new Entry(BufferUtil.EMPTY_BUFFER, Callback.NOOP); - private static final Entry CLOSED = new Entry(BufferUtil.EMPTY_BUFFER, Callback.NOOP); + private static final Entry EOF = new Entry(null, Callback.NOOP); + private static final Entry CLOSED = new Entry(null, Callback.NOOP); + private static final Entry FAILED = new Entry(null, Callback.NOOP); - private final AutoLock lock = new AutoLock(); - private final BlockingArrayQueue buffers = new BlockingArrayQueue<>(); - private boolean closed = false; + private final AutoLock.WithCondition lock = new AutoLock.WithCondition(); + private final ArrayDeque buffers = new ArrayDeque<>(); + private final CoreSession session; private Entry currentEntry; + private Throwable failure; + private boolean closed; private long timeoutMs = -1; + public MessageInputStream(CoreSession session) + { + this.session = session; + } + @Override public void accept(Frame frame, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("accepting {}", frame); - boolean succeed = false; - try (AutoLock l = lock.lock()) + if (!frame.isFin() && !frame.hasPayload()) { - // If closed or we have no payload, request the next frame. - if (closed || (!frame.hasPayload() && !frame.isFin())) + callback.succeeded(); + session.demand(1); + return; + } + + Runnable action = null; + try (AutoLock.WithCondition l = lock.lock()) + { + if (failure != null) { - succeed = true; + Throwable cause = failure; + action = () -> callback.failed(cause); + } + else if (closed) + { + action = callback::succeeded; } else { - if (frame.hasPayload()) - buffers.add(new Entry(frame.getPayload(), callback)); - else - succeed = true; - + buffers.offer(new Entry(frame, callback)); if (frame.isFin()) - buffers.add(EOF); + buffers.offer(EOF); } + l.signal(); } - if (succeed) - callback.succeeded(); + if (action != null) + action.run(); } @Override @@ -93,7 +110,7 @@ public class MessageInputStream extends InputStream implements MessageSink } @Override - public int read(final byte[] b, final int off, final int len) throws IOException + public int read(byte[] b, int off, int len) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(b, off, len).slice(); BufferUtil.clear(buffer); @@ -106,6 +123,9 @@ public class MessageInputStream extends InputStream implements MessageSink if (LOG.isDebugEnabled()) LOG.debug("currentEntry = {}", currentEntry); + if (currentEntry == FAILED) + throw IO.rethrow(getFailure()); + if (currentEntry == CLOSED) throw new IOException("Closed"); @@ -116,49 +136,79 @@ public class MessageInputStream extends InputStream implements MessageSink return -1; } - // We have content. - int fillLen = BufferUtil.append(buffer, currentEntry.buffer); - if (!currentEntry.buffer.hasRemaining()) + ByteBuffer payload = currentEntry.frame.getPayload(); + if (currentEntry.frame.isFin() && !payload.hasRemaining()) + { + succeedCurrentEntry(); + // Recurse to avoid returning 0, as now EOF will be found. + return read(buffer); + } + + int length = BufferUtil.append(buffer, payload); + if (!payload.hasRemaining()) succeedCurrentEntry(); - // Return number of bytes actually copied into buffer. + // Return number of bytes copied into the buffer. if (LOG.isDebugEnabled()) - LOG.debug("filled {} bytes from {}", fillLen, currentEntry); - return fillLen; + LOG.debug("filled {} bytes from {}", length, currentEntry); + return length; } @Override - public void close() throws IOException + public void fail(Throwable failure) + { + if (LOG.isDebugEnabled()) + LOG.debug("fail()", failure); + + ArrayList entries = new ArrayList<>(); + try (AutoLock.WithCondition l = lock.lock()) + { + if (this.failure != null) + return; + this.failure = failure; + + drainInto(entries); + buffers.offer(FAILED); + l.signal(); + } + + entries.forEach(e -> e.callback.failed(failure)); + } + + @Override + public void close() { if (LOG.isDebugEnabled()) LOG.debug("close()"); ArrayList entries = new ArrayList<>(); - try (AutoLock l = lock.lock()) + try (AutoLock.WithCondition l = lock.lock()) { if (closed) return; closed = true; - if (currentEntry != null) - { - entries.add(currentEntry); - currentEntry = null; - } - - // Clear queue and fail all entries. - entries.addAll(buffers); - buffers.clear(); + drainInto(entries); buffers.offer(CLOSED); + l.signal(); } - // Succeed all entries as we don't need them anymore (failing would close the connection). - for (Entry e : entries) + entries.forEach(e -> e.callback.succeeded()); + } + + private void drainInto(ArrayList entries) + { + assert lock.isHeldByCurrentThread(); + + if (currentEntry != null) { - e.callback.succeeded(); + entries.add(currentEntry); + currentEntry = null; } - super.close(); + // Drain the queue. + entries.addAll(buffers); + buffers.clear(); } public void setTimeout(long timeoutMs) @@ -169,47 +219,44 @@ public class MessageInputStream extends InputStream implements MessageSink private void succeedCurrentEntry() { Entry current; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { current = currentEntry; currentEntry = null; } if (current != null) + { current.callback.succeeded(); + if (!current.frame.isFin()) + session.demand(1); + } } private Entry getCurrentEntry() throws IOException { - try (AutoLock l = lock.lock()) + try (AutoLock.WithCondition l = lock.lock()) { if (currentEntry != null) return currentEntry; - } - try - { + long timeout = timeoutMs; if (LOG.isDebugEnabled()) - LOG.debug("Waiting {} ms to read", timeoutMs); + LOG.debug("Waiting {} ms to read", timeout); Entry result; - if (timeoutMs < 0) + while (true) { - // Wait forever until a buffer is available. - result = buffers.take(); - } - else - { - // Wait at most for the given timeout. - result = buffers.poll(timeoutMs, TimeUnit.MILLISECONDS); - if (result == null) - throw new IOException(String.format("Read timeout: %,dms expired", timeoutMs)); + result = buffers.poll(); + if (result != null) + break; + + if (timeout < 0) + l.await(); + else if (!l.await(timeout, TimeUnit.MILLISECONDS)) + throw new IOException(String.format("Read timeout: %,dms expired", timeout)); } - try (AutoLock l = lock.lock()) - { - currentEntry = result; - return currentEntry; - } + return currentEntry = result; } catch (InterruptedException e) { @@ -218,21 +265,15 @@ public class MessageInputStream extends InputStream implements MessageSink } } - private static class Entry + private Throwable getFailure() { - public ByteBuffer buffer; - public Callback callback; - - public Entry(ByteBuffer buffer, Callback callback) + try (AutoLock ignored = lock.lock()) { - this.buffer = Objects.requireNonNull(buffer); - this.callback = callback; - } - - @Override - public String toString() - { - return String.format("Entry[%s,%s]", BufferUtil.toDetailString(buffer), callback.getClass().getSimpleName()); + return failure; } } + + private record Entry(Frame frame, Callback callback) + { + } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageReader.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageReader.java index f258473ed17..c84c0fc1312 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageReader.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageReader.java @@ -24,8 +24,8 @@ import java.nio.charset.StandardCharsets; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; -import org.eclipse.jetty.websocket.core.WebSocketConstants; import static java.nio.charset.StandardCharsets.UTF_8; @@ -36,49 +36,45 @@ import static java.nio.charset.StandardCharsets.UTF_8; */ public class MessageReader extends Reader implements MessageSink { - private static final int BUFFER_SIZE = WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE; - private final ByteBuffer buffer; private final MessageInputStream stream; private final CharsetDecoder utf8Decoder = UTF_8.newDecoder() .onUnmappableCharacter(CodingErrorAction.REPORT) .onMalformedInput(CodingErrorAction.REPORT); - public MessageReader() + public MessageReader(CoreSession coreSession) { - this(BUFFER_SIZE); - } - - public MessageReader(int bufferSize) - { - this.stream = new MessageInputStream(); - this.buffer = BufferUtil.allocate(bufferSize); + this.stream = new MessageInputStream(coreSession); + this.buffer = BufferUtil.allocate(coreSession.getInputBufferSize()); } @Override - public int read(char[] cbuf, int off, int len) throws IOException + public int read(char[] chars, int off, int len) throws IOException { - CharBuffer charBuffer = CharBuffer.wrap(cbuf, off, len); - boolean endOfInput = false; + CharBuffer charBuffer = CharBuffer.wrap(chars, off, len); + boolean eof; while (true) { int read = stream.read(buffer); - if (read == 0) + eof = read < 0; + if (eof || read == 0) break; - if (read < 0) - { - endOfInput = true; - break; - } } - CoderResult result = utf8Decoder.decode(buffer, charBuffer, endOfInput); + CoderResult result = utf8Decoder.decode(buffer, charBuffer, eof); if (result.isError()) result.throwException(); - if (endOfInput && (charBuffer.position() == 0)) + if (eof && charBuffer.position() == off) return -1; - return charBuffer.position(); + + return charBuffer.position() - off; + } + + @Override + public void fail(Throwable failure) + { + stream.fail(failure); } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageSink.java index bf648bc5fad..896ae3baad2 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/MessageSink.java @@ -14,19 +14,38 @@ package org.eclipse.jetty.websocket.core.messages; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; +import org.eclipse.jetty.websocket.core.FrameHandler; /** - * Sink consumer for messages (used for multiple frames with continuations, - * and also to allow for streaming APIs) + *

A consumer of WebSocket data frames (either BINARY or TEXT).

+ *

{@link FrameHandler} delegates the processing of data frames + * to {@link MessageSink}, including the processing of the demand + * for the next frames.

*/ public interface MessageSink { /** - * Consume the frame payload to the message. + *

Consumes the WebSocket frame, possibly asynchronously + * when this method has returned.

+ *

The callback argument must be completed when the frame + * payload is consumed.

+ *

The demand for more frames must be explicitly invoked, + * or arranged to be invoked asynchronously, by the implementation + * of this method, by calling {@link CoreSession#demand(long)}.

* - * @param frame the frame, its payload (and fin state) to append - * @param callback the callback for how the frame was consumed + * @param frame the frame to consume + * @param callback the callback to complete when the frame is consumed */ void accept(Frame frame, Callback callback); + + /** + *

Fails this {@link MessageSink} with the given cause.

+ * + * @param failure the cause of the failure + */ + default void fail(Throwable failure) + { + } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteArrayMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteArrayMessageSink.java index 32f47717b52..fd34a4b7c68 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteArrayMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteArrayMessageSink.java @@ -20,13 +20,23 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; +/** + *

A {@link MessageSink} implementation that delivers BINARY frames + * to the application function passed to the constructor in the form + * of a {@code byte[]}.

+ */ public class PartialByteArrayMessageSink extends AbstractMessageSink { - private static byte[] EMPTY_BUFFER = new byte[0]; - - public PartialByteArrayMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link PartialByteArrayMessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke when a new frame has arrived + * @param autoDemand whether this {@link MessageSink} manages demand automatically + */ + public PartialByteArrayMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); } @Override @@ -36,12 +46,16 @@ public class PartialByteArrayMessageSink extends AbstractMessageSink { if (frame.hasPayload() || frame.isFin()) { - byte[] buffer = frame.hasPayload() ? BufferUtil.toArray(frame.getPayload()) : EMPTY_BUFFER; - methodHandle.invoke(buffer, frame.isFin()); + byte[] buffer = BufferUtil.toArray(frame.getPayload()); + getMethodHandle().invoke(buffer, frame.isFin()); + callback.succeeded(); + autoDemand(); + } + else + { + callback.succeeded(); + getCoreSession().demand(1); } - - callback.succeeded(); - session.demand(1); } catch (Throwable t) { diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteBufferMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteBufferMessageSink.java index 215640637a7..cde238b5656 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteBufferMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialByteBufferMessageSink.java @@ -14,16 +14,29 @@ package org.eclipse.jetty.websocket.core.messages; import java.lang.invoke.MethodHandle; +import java.nio.ByteBuffer; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; +/** + *

A {@link MessageSink} implementation that delivers BINARY frames + * to the application function passed to the constructor in the form + * of a {@link ByteBuffer}.

+ */ public class PartialByteBufferMessageSink extends AbstractMessageSink { - public PartialByteBufferMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link PartialByteBufferMessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke when a new frame has arrived + * @param autoDemand whether this {@link MessageSink} manages demand automatically + */ + public PartialByteBufferMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); } @Override @@ -32,14 +45,25 @@ public class PartialByteBufferMessageSink extends AbstractMessageSink try { if (frame.hasPayload() || frame.isFin()) - methodHandle.invoke(frame.getPayload(), frame.isFin()); - - callback.succeeded(); - session.demand(1); + { + invoke(getMethodHandle(), frame.getPayload(), frame.isFin(), callback); + autoDemand(); + } + else + { + callback.succeeded(); + getCoreSession().demand(1); + } } catch (Throwable t) { callback.failed(t); } } + + protected void invoke(MethodHandle methodHandle, ByteBuffer byteBuffer, boolean fin, Callback callback) throws Throwable + { + methodHandle.invoke(byteBuffer, fin); + callback.succeeded(); + } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialStringMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialStringMessageSink.java index 37f34fd751a..9b37e15cf45 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialStringMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/PartialStringMessageSink.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.websocket.core.messages; import java.lang.invoke.MethodHandle; -import java.util.Objects; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Utf8StringBuilder; @@ -22,14 +21,25 @@ import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.exception.BadPayloadException; +/** + *

A {@link MessageSink} implementation that delivers TEXT frames + * to the application function passed to the constructor in the form + * of a {@link String}.

+ */ public class PartialStringMessageSink extends AbstractMessageSink { - private Utf8StringBuilder out; + private Utf8StringBuilder accumulator; - public PartialStringMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link PartialStringMessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke when a new frame has arrived + * @param autoDemand whether this {@link MessageSink} manages demand automatically + */ + public PartialStringMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); - Objects.requireNonNull(methodHandle, "MethodHandle"); + super(session, methodHandle, autoDemand); } @Override @@ -37,28 +47,34 @@ public class PartialStringMessageSink extends AbstractMessageSink { try { - if (out == null) - out = new Utf8StringBuilder(session.getInputBufferSize()); + if (accumulator == null) + accumulator = new Utf8StringBuilder(getCoreSession().getInputBufferSize()); + + accumulator.append(frame.getPayload()); - out.append(frame.getPayload()); if (frame.isFin()) { - String complete = out.takeCompleteString(() -> new BadPayloadException("Invalid UTF-8")); - methodHandle.invoke(complete, true); - out = null; + String complete = accumulator.takeCompleteString(() -> new BadPayloadException("Invalid UTF-8")); + getMethodHandle().invoke(complete, true); } else { - String partial = out.takePartialString(() -> new BadPayloadException("Invalid UTF-8")); - methodHandle.invoke(partial, false); + String partial = accumulator.takePartialString(() -> new BadPayloadException("Invalid UTF-8")); + getMethodHandle().invoke(partial, false); } callback.succeeded(); - session.demand(1); + + autoDemand(); } catch (Throwable t) { callback.failed(t); } + finally + { + if (frame.isFin()) + accumulator = null; + } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ReaderMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ReaderMessageSink.java index c3350549a76..8f8e4f1759a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ReaderMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/ReaderMessageSink.java @@ -16,18 +16,17 @@ package org.eclipse.jetty.websocket.core.messages; import java.lang.invoke.MethodHandle; import org.eclipse.jetty.websocket.core.CoreSession; -import org.eclipse.jetty.websocket.core.Frame; public class ReaderMessageSink extends DispatchedMessageSink { - public ReaderMessageSink(CoreSession session, MethodHandle methodHandle) + public ReaderMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); } @Override - public MessageReader newSink(Frame frame) + public MessageReader newMessageSink() { - return new MessageReader(session.getInputBufferSize()); + return new MessageReader(getCoreSession()); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/StringMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/StringMessageSink.java index 40ab9d4978c..87595d20bf4 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/StringMessageSink.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/messages/StringMessageSink.java @@ -22,14 +22,26 @@ import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.exception.BadPayloadException; import org.eclipse.jetty.websocket.core.exception.MessageTooLargeException; +/** + *

A {@link MessageSink} implementation that accumulates TEXT frames + * into a message that is then delivered to the application function + * passed to the constructor in the form of a {@link String}.

+ */ public class StringMessageSink extends AbstractMessageSink { private Utf8StringBuilder out; private int size; - public StringMessageSink(CoreSession session, MethodHandle methodHandle) + /** + * Creates a new {@link StringMessageSink}. + * + * @param session the WebSocket session + * @param methodHandle the application function to invoke when a new message has been assembled + * @param autoDemand whether this {@link MessageSink} manages demand automatically + */ + public StringMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) { - super(session, methodHandle); + super(session, methodHandle, autoDemand); this.size = 0; } @@ -39,23 +51,29 @@ public class StringMessageSink extends AbstractMessageSink try { size += frame.getPayloadLength(); - long maxTextMessageSize = session.getMaxTextMessageSize(); - if (maxTextMessageSize > 0 && size > maxTextMessageSize) + long maxSize = getCoreSession().getMaxTextMessageSize(); + if (maxSize > 0 && size > maxSize) { - throw new MessageTooLargeException(String.format("Text message too large: (actual) %,d > (configured max text message size) %,d", - size, maxTextMessageSize)); + callback.failed(new MessageTooLargeException(String.format("Text message too large: %,d > %,d", size, maxSize))); + return; } if (out == null) - out = new Utf8StringBuilder(session.getInputBufferSize()); + out = new Utf8StringBuilder(getCoreSession().getInputBufferSize()); out.append(frame.getPayload()); + if (frame.isFin()) { - methodHandle.invoke(out.takeCompleteString(() -> new BadPayloadException("Invalid UTF-8"))); + getMethodHandle().invoke(out.takeCompleteString(() -> new BadPayloadException("Invalid UTF-8"))); + callback.succeeded(); + autoDemand(); + } + else + { + callback.succeeded(); + getCoreSession().demand(1); } - callback.succeeded(); - session.demand(1); } catch (Throwable t) { @@ -65,7 +83,6 @@ public class StringMessageSink extends AbstractMessageSink { if (frame.isFin()) { - // reset size = 0; out = null; } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java index 94563582433..cd587a435de 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/AutoFragmentTest.java @@ -74,13 +74,13 @@ public class AutoFragmentTest // Turn off fragmentation on the server. assertTrue(serverHandler.open.await(5, TimeUnit.SECONDS)); - serverHandler.coreSession.setMaxFrameSize(0); - serverHandler.coreSession.setAutoFragment(false); + serverHandler.getCoreSession().setMaxFrameSize(0); + serverHandler.getCoreSession().setAutoFragment(false); // Set the client to fragment to the maxFrameSize. int maxFrameSize = 30; - clientHandler.coreSession.setMaxFrameSize(maxFrameSize); - clientHandler.coreSession.setAutoFragment(true); + clientHandler.getCoreSession().setMaxFrameSize(maxFrameSize); + clientHandler.getCoreSession().setAutoFragment(true); // Send a message which is too large. int size = maxFrameSize * 2; @@ -88,7 +88,7 @@ public class AutoFragmentTest Arrays.fill(array, 0, size, (byte)'X'); ByteBuffer message = BufferUtil.toBuffer(array); Frame sentFrame = new Frame(OpCode.BINARY, BufferUtil.copy(message)); - clientHandler.coreSession.sendFrame(sentFrame, Callback.NOOP, false); + clientHandler.getCoreSession().sendFrame(sentFrame, Callback.NOOP, false); // We should not receive any frames larger than the max frame size. // So our message should be split into two frames. @@ -121,20 +121,20 @@ public class AutoFragmentTest connect.get(5, TimeUnit.SECONDS); // Turn off fragmentation on the client. - clientHandler.coreSession.setMaxFrameSize(0); - clientHandler.coreSession.setAutoFragment(false); + clientHandler.getCoreSession().setMaxFrameSize(0); + clientHandler.getCoreSession().setAutoFragment(false); // Set the server should fragment to the maxFrameSize. int maxFrameSize = 30; assertTrue(serverHandler.open.await(5, TimeUnit.SECONDS)); - serverHandler.coreSession.setMaxFrameSize(maxFrameSize); - serverHandler.coreSession.setAutoFragment(true); + serverHandler.getCoreSession().setMaxFrameSize(maxFrameSize); + serverHandler.getCoreSession().setAutoFragment(true); // Send a message which is too large. int size = maxFrameSize * 2; byte[] message = new byte[size]; Arrays.fill(message, 0, size, (byte)'X'); - clientHandler.coreSession.sendFrame(new Frame(OpCode.BINARY, BufferUtil.toBuffer(message)), Callback.NOOP, false); + clientHandler.getCoreSession().sendFrame(new Frame(OpCode.BINARY, BufferUtil.toBuffer(message)), Callback.NOOP, false); // We should not receive any frames larger than the max frame size. // So our message should be split into two frames. @@ -166,14 +166,14 @@ public class AutoFragmentTest connect.get(5, TimeUnit.SECONDS); // Turn off fragmentation on the client. - clientHandler.coreSession.setMaxFrameSize(0); - clientHandler.coreSession.setAutoFragment(false); + clientHandler.getCoreSession().setMaxFrameSize(0); + clientHandler.getCoreSession().setAutoFragment(false); // Set a small maxFrameSize on the server. int maxFrameSize = 10; assertTrue(serverHandler.open.await(5, TimeUnit.SECONDS)); - serverHandler.coreSession.setMaxFrameSize(maxFrameSize); - serverHandler.coreSession.setAutoFragment(true); + serverHandler.getCoreSession().setMaxFrameSize(maxFrameSize); + serverHandler.getCoreSession().setAutoFragment(true); // Generate a large random payload. int payloadSize = 1000; @@ -187,7 +187,7 @@ public class AutoFragmentTest BufferUtil.flipToFlush(payload, 0); // Send the large random payload which should be fragmented on the server. - clientHandler.coreSession.sendFrame(new Frame(OpCode.BINARY, BufferUtil.copy(payload)), Callback.NOOP, false); + clientHandler.getCoreSession().sendFrame(new Frame(OpCode.BINARY, BufferUtil.copy(payload)), Callback.NOOP, false); // Assemble the message from the fragmented frames. ByteBuffer message = BufferUtil.allocate(payloadSize * 2); @@ -219,14 +219,14 @@ public class AutoFragmentTest connect.get(5, TimeUnit.SECONDS); // Turn off fragmentation on the client. - clientHandler.coreSession.setMaxFrameSize(0); - clientHandler.coreSession.setAutoFragment(false); + clientHandler.getCoreSession().setMaxFrameSize(0); + clientHandler.getCoreSession().setAutoFragment(false); // Set a small maxFrameSize on the server. int maxFrameSize = 1024; assertTrue(serverHandler.open.await(5, TimeUnit.SECONDS)); - serverHandler.coreSession.setMaxFrameSize(maxFrameSize); - serverHandler.coreSession.setAutoFragment(true); + serverHandler.getCoreSession().setMaxFrameSize(maxFrameSize); + serverHandler.getCoreSession().setAutoFragment(true); // Highly compressible payload. byte[] data = new byte[512 * 1024]; @@ -234,7 +234,7 @@ public class AutoFragmentTest ByteBuffer payload = ByteBuffer.wrap(data); // Send the payload which should be fragmented on the server. - clientHandler.coreSession.sendFrame(new Frame(OpCode.BINARY, BufferUtil.copy(payload)), Callback.NOOP, false); + clientHandler.getCoreSession().sendFrame(new Frame(OpCode.BINARY, BufferUtil.copy(payload)), Callback.NOOP, false); // Assemble the message from the fragmented frames. ByteBuffer message = BufferUtil.allocate(payload.remaining() * 2); @@ -282,13 +282,13 @@ public class AutoFragmentTest connect.get(5, TimeUnit.SECONDS); // Turn off fragmentation on the client. - clientHandler.coreSession.setMaxFrameSize(0); - clientHandler.coreSession.setAutoFragment(false); + clientHandler.getCoreSession().setMaxFrameSize(0); + clientHandler.getCoreSession().setAutoFragment(false); // Set maxFrameSize and autoFragment on the server. assertTrue(serverHandler.open.await(5, TimeUnit.SECONDS)); - serverHandler.coreSession.setMaxFrameSize(maxFrameSize); - serverHandler.coreSession.setAutoFragment(true); + serverHandler.getCoreSession().setMaxFrameSize(maxFrameSize); + serverHandler.getCoreSession().setAutoFragment(true); // Send the payload which should be fragmented by the server permessage-deflate. ByteBuffer sendPayload = BufferUtil.copy(payload); diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandTest.java index 597876cd0ba..bdf95686984 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandTest.java @@ -83,7 +83,6 @@ public class DemandTest public void onError(Throwable cause, Callback callback) { callback.succeeded(); - _coreSession.demand(1); } @Override @@ -91,12 +90,6 @@ public class DemandTest { callback.succeeded(); } - - @Override - public boolean isAutoDemanding() - { - return false; - } } @Test diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandingIncomingFramesCapture.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandingIncomingFramesCapture.java index 112ed3df2c8..365021d011f 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandingIncomingFramesCapture.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/DemandingIncomingFramesCapture.java @@ -33,8 +33,7 @@ public class DemandingIncomingFramesCapture extends IncomingFramesCapture } finally { - if (_coreSession.isAutoDemanding()) - _coreSession.autoDemand(); + _coreSession.demand(1); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java index abeb5bebb19..0f3ff967f13 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/EchoFrameHandler.java @@ -55,5 +55,7 @@ public class EchoFrameHandler extends TestAsyncFrameHandler { callback.succeeded(); } + + coreSession.demand(1); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java index 01023ee7269..5c22ddb2fff 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/FrameBufferTest.java @@ -85,8 +85,8 @@ public class FrameBufferTest extends WebSocketTester TestFrameHandler clientHandler = new TestFrameHandler(); client.connect(clientHandler, server.getUri()).get(5, TimeUnit.SECONDS); serverHandler.open.await(5, TimeUnit.SECONDS); - clientHandler.coreSession.setAutoFragment(false); - serverHandler.coreSession.setAutoFragment(false); + clientHandler.getCoreSession().setAutoFragment(false); + serverHandler.getCoreSession().setAutoFragment(false); int payloadLen = 32 * 1024; byte[] array = new byte[payloadLen]; diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java index 44243ba0dce..5bf8b4f9e2b 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/MessageHandlerTest.java @@ -46,7 +46,6 @@ public class MessageHandlerTest static byte[] fourByteUtf8Bytes = fourByteUtf8String.getBytes(StandardCharsets.UTF_8); static byte[] nonUtf8Bytes = {0x7F, (byte)0xFF, (byte)0xFF}; - boolean autoDemanding; CoreSession coreSession; List textMessages = new ArrayList<>(); List binaryMessages = new ArrayList<>(); @@ -55,10 +54,8 @@ public class MessageHandlerTest MessageHandler handler; @BeforeEach - public void beforeEach() throws Exception + public void beforeEach() { - autoDemanding = true; - coreSession = new CoreSession.Empty() { private final ByteBufferPool bufferPool = new ArrayByteBufferPool(); @@ -92,12 +89,6 @@ public class MessageHandlerTest binaryMessages.add(message); callbacks.add(callback); } - - @Override - public boolean isAutoDemanding() - { - return autoDemanding; - } }; handler.onOpen(coreSession, NOOP); diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java index b97a0c0011e..5715268773d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestAsyncFrameHandler.java @@ -51,6 +51,7 @@ public class TestAsyncFrameHandler implements FrameHandler LOG.debug("[{}] onOpen {}", name, coreSession); this.coreSession = coreSession; callback.succeeded(); + coreSession.demand(1); openLatch.countDown(); } @@ -61,6 +62,7 @@ public class TestAsyncFrameHandler implements FrameHandler LOG.debug("[{}] onFrame {}", name, frame); receivedFrames.offer(Frame.copy(frame)); callback.succeeded(); + coreSession.demand(1); } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java index 12d31e6b7f0..7fb8b865b06 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestFrameHandler.java @@ -56,6 +56,7 @@ public class TestFrameHandler implements SynchronousFrameHandler if (LOG.isDebugEnabled()) LOG.debug("onOpen {}", coreSession); this.coreSession = coreSession; + demand(); open.countDown(); } @@ -65,6 +66,12 @@ public class TestFrameHandler implements SynchronousFrameHandler if (LOG.isDebugEnabled()) LOG.debug("onFrame: " + OpCode.name(frame.getOpCode()) + ":" + BufferUtil.toDetailString(frame.getPayload())); receivedFrames.offer(Frame.copy(frame)); + demand(); + } + + protected void demand() + { + coreSession.demand(1); } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java index 7e5d6c1ed4c..7b131a3b901 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/TestMessageHandler.java @@ -27,7 +27,6 @@ public class TestMessageHandler extends MessageHandler { protected static final Logger LOG = LoggerFactory.getLogger(TestMessageHandler.class); - public CoreSession coreSession; public BlockingQueue textMessages = new BlockingArrayQueue<>(); public BlockingQueue binaryMessages = new BlockingArrayQueue<>(); public CloseStatus closeStatus; @@ -40,7 +39,6 @@ public class TestMessageHandler extends MessageHandler public void onOpen(CoreSession coreSession, Callback callback) { super.onOpen(coreSession, callback); - this.coreSession = coreSession; openLatch.countDown(); } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java index 9f7420c5bf8..8d4d7c95426 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketCloseTest.java @@ -546,8 +546,8 @@ public class WebSocketCloseTest extends WebSocketTester LOG.debug("onOpen {}", coreSession); this.coreSession = coreSession; state = this.coreSession.toString(); - opened.countDown(); callback.succeeded(); + opened.countDown(); } @Override @@ -582,11 +582,5 @@ public class WebSocketCloseTest extends WebSocketTester state = coreSession.toString(); callback.succeeded(); } - - @Override - public boolean isAutoDemanding() - { - return false; - } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java index 847bd5212c5..ad0a4716e7a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketNegotiationTest.java @@ -484,7 +484,7 @@ public class WebSocketNegotiationTest extends WebSocketTester // The list of Extensions on the client contains the internal Extensions. StringBuilder negotiatedExtensions = new StringBuilder(); - List extensions = ((WebSocketCoreSession)clientHandler.coreSession).getExtensionStack().getExtensions(); + List extensions = ((WebSocketCoreSession)clientHandler.getCoreSession()).getExtensionStack().getExtensions(); for (int i = 0; i < extensions.size(); i++) { negotiatedExtensions.append(extensions.get(i).getConfig().getParameterizedName()); diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java index 32d8363ebd7..8012efe24db 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/WebSocketOpenTest.java @@ -16,14 +16,12 @@ package org.eclipse.jetty.websocket.core; import java.net.Socket; import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; +import java.util.function.BiConsumer; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import static org.eclipse.jetty.util.Callback.NOOP; import static org.hamcrest.MatcherAssert.assertThat; @@ -40,10 +38,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; */ public class WebSocketOpenTest extends WebSocketTester { - private static final Logger LOG = LoggerFactory.getLogger(WebSocketOpenTest.class); - private WebSocketServer server; - private DemandingAsyncFrameHandler serverHandler; + private NonDemandingAsyncFrameHandler serverHandler; private Socket client; @AfterEach @@ -53,9 +49,9 @@ public class WebSocketOpenTest extends WebSocketTester server.stop(); } - public void setup(BiFunction onOpen) throws Exception + public void setup(BiConsumer onOpen) throws Exception { - serverHandler = new DemandingAsyncFrameHandler(onOpen); + serverHandler = new NonDemandingAsyncFrameHandler(onOpen); server = new WebSocketServer(serverHandler); server.start(); client = newClient(server.getLocalPort()); @@ -70,7 +66,6 @@ public class WebSocketOpenTest extends WebSocketTester s.sendFrame(new Frame(OpCode.TEXT, "Hello"), NOOP, false); c.succeeded(); s.demand(1); - return null; }); Frame.Parsed frame = receiveFrame(client.getInputStream()); assertThat(frame.getPayloadAsUTF8(), is("Hello")); @@ -87,13 +82,12 @@ public class WebSocketOpenTest extends WebSocketTester @Test public void testFailureInOnOpen() throws Exception { - try (StacklessLogging stackless = new StacklessLogging(WebSocketCoreSession.class)) + try (StacklessLogging ignored = new StacklessLogging(WebSocketCoreSession.class)) { setup((s, c) -> { assertThat(s.toString(), containsString("CONNECTED")); c.failed(new Exception("Test Exception in onOpen")); - return null; }); assertTrue(serverHandler.closeLatch.await(5, TimeUnit.SECONDS)); @@ -115,7 +109,6 @@ public class WebSocketOpenTest extends WebSocketTester { assertThat(s.toString(), containsString("CONNECTED")); s.close(CloseStatus.SHUTDOWN, "Test close in onOpen", c); - return null; }); Frame.Parsed frame = receiveFrame(client.getInputStream()); @@ -144,7 +137,6 @@ public class WebSocketOpenTest extends WebSocketTester { throw new RuntimeException(e); } - return null; }); CoreSession coreSession = sx.exchange(null); @@ -182,11 +174,11 @@ public class WebSocketOpenTest extends WebSocketTester assertThat(new CloseStatus(frame).getCode(), is(CloseStatus.NORMAL)); } - static class DemandingAsyncFrameHandler extends TestAsyncFrameHandler + static class NonDemandingAsyncFrameHandler extends TestAsyncFrameHandler { - private final BiFunction onOpen; + private final BiConsumer onOpen; - DemandingAsyncFrameHandler(BiFunction onOpen) + NonDemandingAsyncFrameHandler(BiConsumer onOpen) { this.onOpen = onOpen; } @@ -197,14 +189,17 @@ public class WebSocketOpenTest extends WebSocketTester if (LOG.isDebugEnabled()) LOG.debug("[{}] onOpen {}", name, coreSession); this.coreSession = coreSession; - onOpen.apply(coreSession, callback); + onOpen.accept(coreSession, callback); openLatch.countDown(); } @Override - public boolean isAutoDemanding() + public void onFrame(Frame frame, Callback callback) { - return false; + if (LOG.isDebugEnabled()) + LOG.debug("[{}] onFrame {}", name, frame); + receivedFrames.offer(Frame.copy(frame)); + callback.succeeded(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java index 77033032473..bb2882f64c3 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/autobahn/CoreAutobahnClient.java @@ -177,7 +177,7 @@ public class CoreAutobahnClient if (!echoHandler.closeLatch.await(5, TimeUnit.MINUTES)) { LOG.warn("could not close {}, aborting session", echoHandler); - echoHandler.coreSession.abort(); + echoHandler.getCoreSession().abort(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java index 4453a7ca25f..0638c6e000a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/client/WebSocketClientServerTest.java @@ -104,6 +104,7 @@ public class WebSocketClientServerTest else { callback.succeeded(); + getCoreSession().demand(1); } } }; diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java index a632794069f..e3359d513f8 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/ExtensionTool.java @@ -82,7 +82,7 @@ public class ExtensionTool byte[] net; // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); for (int i = 0; i < parts; i++) { @@ -102,8 +102,7 @@ public class ExtensionTool public void succeeded() { super.succeeded(); - if (coreSession.isAutoDemanding()) - coreSession.autoDemand(); + coreSession.demand(1); } }; ext.onFrame(frame, callback); diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java index 804ae990e42..15110d91c04 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/FragmentExtensionTest.java @@ -59,7 +59,7 @@ public class FragmentExtensionTest extends AbstractExtensionTest ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); // Quote List quote = new ArrayList<>(); @@ -131,7 +131,7 @@ public class FragmentExtensionTest extends AbstractExtensionTest ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); String payload = "Are you there?"; Frame ping = new Frame(OpCode.PING).setPayload(payload); diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java index 635f012ce70..6db995869b9 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PerMessageDeflateExtensionTest.java @@ -295,7 +295,7 @@ public class PerMessageDeflateExtensionTest extends AbstractExtensionTest ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); String payload = "Are you there?"; Frame ping = new Frame(OpCode.PING).setPayload(payload); @@ -333,7 +333,7 @@ public class PerMessageDeflateExtensionTest extends AbstractExtensionTest ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); // Quote List quote = new ArrayList<>(); @@ -386,7 +386,7 @@ public class PerMessageDeflateExtensionTest extends AbstractExtensionTest ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); Frame ping = new Frame(OpCode.TEXT); ping.setRsv1(true); diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PermessageDeflateDemandTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PermessageDeflateDemandTest.java index 7dfd5d1dfe1..5f3284ff0eb 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PermessageDeflateDemandTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/extensions/PermessageDeflateDemandTest.java @@ -185,11 +185,5 @@ public class PermessageDeflateDemandTest { callback.succeeded(); } - - @Override - public boolean isAutoDemanding() - { - return false; - } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java index 531d89b7cd2..a5484a9c1d1 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxy.java @@ -21,6 +21,7 @@ import java.util.concurrent.CountDownLatch; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; @@ -45,22 +46,22 @@ class WebSocketProxy FAILED } - private final Object lock = new Object(); - private final WebSocketCoreClient client; - private final URI serverUri; + private final WebSocketCoreClient proxyClient; + private final URI serverURI; public Client2Proxy client2Proxy = new Client2Proxy(); - public Server2Proxy server2Proxy = new Server2Proxy(); + public Proxy2Server proxy2Server = new Proxy2Server(); - public WebSocketProxy(WebSocketCoreClient client, URI serverUri) + public WebSocketProxy(WebSocketCoreClient proxyClient, URI serverURI) { - this.client = client; - this.serverUri = serverUri; + this.proxyClient = proxyClient; + this.serverURI = serverURI; } class Client2Proxy implements FrameHandler { - private CoreSession client; + private final AutoLock lock = new AutoLock(); + private CoreSession client2ProxySession; private State state = State.NOT_OPEN; private Callback closeCallback; @@ -71,7 +72,7 @@ class WebSocketProxy public State getState() { - synchronized (this) + try (AutoLock ignored = lock.lock()) { return state; } @@ -81,16 +82,16 @@ class WebSocketProxy public void onOpen(CoreSession coreSession, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onOpen {}", toString(), coreSession); + LOG.debug("[{}] onOpen {}", this, coreSession); Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { case NOT_OPEN: state = State.CONNECTING; - client = coreSession; + client2ProxySession = coreSession; break; default: @@ -102,16 +103,16 @@ class WebSocketProxy if (failure != null) callback.failed(failure); else - server2Proxy.connect(Callback.from(() -> onOpenSuccess(callback), (t) -> onOpenFail(callback, t))); + proxy2Server.connect(Callback.from(() -> onOpenSuccess(callback), (t) -> onOpenFail(callback, t))); } private void onOpenSuccess(Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onOpenSuccess", toString()); + LOG.debug("[{}] onOpenSuccess", this); Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -130,18 +131,23 @@ class WebSocketProxy } if (failure != null) - server2Proxy.fail(failure, callback); + { + proxy2Server.fail(failure, callback); + } else + { callback.succeeded(); + client2ProxySession.demand(1); + } } private void onOpenFail(Callback callback, Throwable t) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onOpenFail {}", toString(), t); + LOG.debug("[{}] onOpenFail", this, t); Throwable failure = t; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -168,12 +174,13 @@ class WebSocketProxy public void onFrame(Frame frame, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onFrame {}", toString(), frame); + LOG.debug("[{}] onFrame {}", this, frame); receivedFrames.offer(Frame.copy(frame)); + boolean demand = false; Callback sendCallback = callback; Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -181,11 +188,14 @@ class WebSocketProxy if (frame.getOpCode() == OpCode.CLOSE) { state = State.ISHUT; - // the callback is saved until a close response comes in sendFrame from Server2Proxy + // the callback is saved until a close response comes in sendFrame from Proxy2Server // if the callback was completed here then core would send its own close response closeCallback = callback; - sendCallback = Callback.from(() -> - {}, callback::failed); + sendCallback = Callback.from(() -> {}, callback::failed); + } + else + { + demand = true; } break; @@ -205,19 +215,35 @@ class WebSocketProxy } if (failure != null) + { callback.failed(failure); + } else - server2Proxy.send(frame, sendCallback); + { + if (demand) + { + Callback c = sendCallback; + proxy2Server.send(frame, Callback.from(() -> + { + c.succeeded(); + client2ProxySession.demand(1); + }, c::failed)); + } + else + { + proxy2Server.send(frame, sendCallback); + } + } } @Override public void onError(Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onError {}", toString(), failure); + LOG.debug("[{}] onError", this, failure); boolean failServer2Proxy; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -235,7 +261,7 @@ class WebSocketProxy } if (failServer2Proxy) - server2Proxy.fail(failure, callback); + proxy2Server.fail(failure, callback); else callback.failed(failure); } @@ -243,10 +269,10 @@ class WebSocketProxy public void fail(Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] fail {}", toString(), failure); + LOG.debug("[{}] fail", this, failure); Callback sendCallback = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -268,7 +294,7 @@ class WebSocketProxy } if (sendCallback != null) - client.close(CloseStatus.SHUTDOWN, failure.getMessage(), sendCallback); + client2ProxySession.close(CloseStatus.SHUTDOWN, failure.getMessage(), sendCallback); else callback.failed(failure); } @@ -277,10 +303,10 @@ class WebSocketProxy public void onClosed(CloseStatus closeStatus, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onClosed {}", toString(), closeStatus); + LOG.debug("[{}] onClosed {}", this, closeStatus); boolean abnormalClose = false; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -294,7 +320,7 @@ class WebSocketProxy } if (abnormalClose) - server2Proxy.fail(new ClosedChannelException(), Callback.NOOP); + proxy2Server.fail(new ClosedChannelException(), Callback.NOOP); closed.countDown(); callback.succeeded(); @@ -303,11 +329,11 @@ class WebSocketProxy public void send(Frame frame, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] send {}", toString(), frame); + LOG.debug("[{}] send {}", this, frame); Callback sendCallback = callback; Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -337,7 +363,7 @@ class WebSocketProxy if (failure != null) callback.failed(failure); else - client.sendFrame(frame, sendCallback, false); + client2ProxySession.sendFrame(frame, sendCallback, false); } @Override @@ -347,9 +373,10 @@ class WebSocketProxy } } - class Server2Proxy implements FrameHandler + class Proxy2Server implements FrameHandler { - private CoreSession server; + private final AutoLock lock = new AutoLock(); + private CoreSession proxy2ServerSession; private State state = State.NOT_OPEN; private Callback closeCallback; @@ -360,7 +387,7 @@ class WebSocketProxy public State getState() { - synchronized (this) + try (AutoLock ignored = lock.lock()) { return state; } @@ -369,10 +396,10 @@ class WebSocketProxy public void connect(Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] connect", toString()); + LOG.debug("[{}] connect", this); Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -380,7 +407,7 @@ class WebSocketProxy try { state = State.CONNECTING; - client.connect(this, serverUri).whenComplete((s, t) -> + proxyClient.connect(this, serverURI).whenComplete((s, t) -> { if (t != null) onConnectFailure(t, callback); @@ -415,10 +442,10 @@ class WebSocketProxy private void onConnectSuccess(CoreSession coreSession, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onConnectSuccess {}", toString(), coreSession); + LOG.debug("[{}] onConnectSuccess {}", this, coreSession); Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -438,18 +465,23 @@ class WebSocketProxy } if (failure != null) + { coreSession.close(CloseStatus.SHUTDOWN, failure.getMessage(), Callback.from(callback, failure)); + } else + { callback.succeeded(); + coreSession.demand(1); + } } private void onConnectFailure(Throwable t, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onConnectFailure {}", toString(), t); + LOG.debug("[{}] onConnectFailure", this, t); Throwable failure = t; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -477,16 +509,16 @@ class WebSocketProxy public void onOpen(CoreSession coreSession, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onOpen {}", toString(), coreSession); + LOG.debug("[{}] onOpen {}", this, coreSession); Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { case CONNECTING: state = State.OPEN; - server = coreSession; + proxy2ServerSession = coreSession; break; case FAILED: @@ -509,12 +541,13 @@ class WebSocketProxy public void onFrame(Frame frame, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onFrame {}", toString(), frame); + LOG.debug("[{}] onFrame {}", this, frame); receivedFrames.offer(Frame.copy(frame)); + boolean demand = false; Callback sendCallback = callback; Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -523,8 +556,11 @@ class WebSocketProxy { state = State.ISHUT; closeCallback = callback; - sendCallback = Callback.from(() -> - {}, callback::failed); + sendCallback = Callback.from(() -> {}, callback::failed); + } + else + { + demand = true; } break; @@ -544,19 +580,35 @@ class WebSocketProxy } if (failure != null) + { callback.failed(failure); + } else - client2Proxy.send(frame, sendCallback); + { + if (demand) + { + Callback c = sendCallback; + client2Proxy.send(frame, Callback.from(() -> + { + c.succeeded(); + proxy2ServerSession.demand(1); + }, c::failed)); + } + else + { + client2Proxy.send(frame, sendCallback); + } + } } @Override public void onError(Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onError {}", toString(), failure); + LOG.debug("[{}] onError", this, failure); boolean failClient2Proxy = false; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -582,10 +634,10 @@ class WebSocketProxy public void onClosed(CloseStatus closeStatus, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] onClosed {}", toString(), closeStatus); + LOG.debug("[{}] onClosed {}", this, closeStatus); boolean abnormalClose = false; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -608,10 +660,10 @@ class WebSocketProxy public void fail(Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] fail {}", toString(), failure); + LOG.debug("[{}] fail", this, failure); Callback sendCallback = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -633,7 +685,7 @@ class WebSocketProxy } if (sendCallback != null) - server.close(CloseStatus.SHUTDOWN, failure.getMessage(), sendCallback); + proxy2ServerSession.close(CloseStatus.SHUTDOWN, failure.getMessage(), sendCallback); else callback.failed(failure); } @@ -641,11 +693,11 @@ class WebSocketProxy public void send(Frame frame, Callback callback) { if (LOG.isDebugEnabled()) - LOG.debug("[{}] send {}", toString(), frame); + LOG.debug("[{}] send {}", this, frame); Callback sendCallback = callback; Throwable failure = null; - synchronized (lock) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -675,13 +727,13 @@ class WebSocketProxy if (failure != null) callback.failed(failure); else - server.sendFrame(frame, sendCallback, false); + proxy2ServerSession.sendFrame(frame, sendCallback, false); } @Override public String toString() { - return "Server2Proxy:" + getState(); + return "Proxy2Server:" + getState(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java index 780c54ae531..f17998bdaf1 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/proxy/WebSocketProxyTest.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.websocket.core.proxy; import java.net.URI; import java.time.Duration; -import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -146,7 +145,7 @@ public class WebSocketProxyTest _server.stop(); } - public void awaitProxyClose(WebSocketProxy.Client2Proxy client2Proxy, WebSocketProxy.Server2Proxy server2Proxy) throws Exception + public void awaitProxyClose(WebSocketProxy.Client2Proxy client2Proxy, WebSocketProxy.Proxy2Server server2Proxy) throws Exception { if (client2Proxy != null && !client2Proxy.closed.await(5, TimeUnit.SECONDS)) throw new TimeoutException("client2Proxy close timeout"); @@ -160,7 +159,7 @@ public class WebSocketProxyTest { TestAsyncFrameHandler clientFrameHandler = new TestAsyncFrameHandler("CLIENT"); WebSocketProxy.Client2Proxy proxyClientSide = proxy.client2Proxy; - WebSocketProxy.Server2Proxy proxyServerSide = proxy.server2Proxy; + WebSocketProxy.Proxy2Server proxyServerSide = proxy.proxy2Server; CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(_client, proxyUri, clientFrameHandler); upgradeRequest.setConfiguration(defaultCustomizer); @@ -168,6 +167,20 @@ public class WebSocketProxyTest response.get(5, TimeUnit.SECONDS); clientFrameHandler.sendText("hello world"); + + Frame frame = proxyClientSide.receivedFrames.poll(5, TimeUnit.SECONDS); + assertNotNull(frame); + assertThat(frame.getPayloadAsUTF8(), is("hello world")); + frame = serverFrameHandler.receivedFrames.poll(5, TimeUnit.SECONDS); + assertNotNull(frame); + assertThat(frame.getPayloadAsUTF8(), is("hello world")); + frame = proxyServerSide.receivedFrames.poll(5, TimeUnit.SECONDS); + assertNotNull(frame); + assertThat(frame.getPayloadAsUTF8(), is("hello world")); + frame = clientFrameHandler.receivedFrames.poll(5, TimeUnit.SECONDS); + assertNotNull(frame); + assertThat(frame.getPayloadAsUTF8(), is("hello world")); + clientFrameHandler.close(CloseStatus.NORMAL, "standard close"); assertTrue(clientFrameHandler.closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(serverFrameHandler.closeLatch.await(5, TimeUnit.SECONDS)); @@ -176,11 +189,6 @@ public class WebSocketProxyTest assertThat(proxyClientSide.getState(), is(WebSocketProxy.State.CLOSED)); assertThat(proxyServerSide.getState(), is(WebSocketProxy.State.CLOSED)); - assertThat(Objects.requireNonNull(proxyClientSide.receivedFrames.poll()).getPayloadAsUTF8(), is("hello world")); - assertThat(Objects.requireNonNull(serverFrameHandler.receivedFrames.poll()).getPayloadAsUTF8(), is("hello world")); - assertThat(Objects.requireNonNull(proxyServerSide.receivedFrames.poll()).getPayloadAsUTF8(), is("hello world")); - assertThat(Objects.requireNonNull(clientFrameHandler.receivedFrames.poll()).getPayloadAsUTF8(), is("hello world")); - assertThat(CloseStatus.getCloseStatus(proxyClientSide.receivedFrames.poll()).getReason(), is("standard close")); assertThat(CloseStatus.getCloseStatus(serverFrameHandler.receivedFrames.poll()).getReason(), is("standard close")); assertThat(CloseStatus.getCloseStatus(proxyServerSide.receivedFrames.poll()).getReason(), is("standard close")); @@ -197,7 +205,7 @@ public class WebSocketProxyTest { testHandler.blockServerUpgradeRequests(); WebSocketProxy.Client2Proxy proxyClientSide = proxy.client2Proxy; - WebSocketProxy.Server2Proxy proxyServerSide = proxy.server2Proxy; + WebSocketProxy.Proxy2Server proxyServerSide = proxy.proxy2Server; TestAsyncFrameHandler clientFrameHandler = new TestAsyncFrameHandler("CLIENT"); try (StacklessLogging ignored = new StacklessLogging(WebSocketCoreSession.class)) @@ -237,7 +245,7 @@ public class WebSocketProxyTest } }; WebSocketProxy.Client2Proxy proxyClientSide = proxy.client2Proxy; - WebSocketProxy.Server2Proxy proxyServerSide = proxy.server2Proxy; + WebSocketProxy.Proxy2Server proxyServerSide = proxy.proxy2Server; try (StacklessLogging ignored = new StacklessLogging(WebSocketCoreSession.class)) { @@ -269,7 +277,7 @@ public class WebSocketProxyTest { serverFrameHandler.throwOnFrame(); WebSocketProxy.Client2Proxy proxyClientSide = proxy.client2Proxy; - WebSocketProxy.Server2Proxy proxyServerSide = proxy.server2Proxy; + WebSocketProxy.Proxy2Server proxyServerSide = proxy.proxy2Server; TestAsyncFrameHandler clientFrameHandler = new TestAsyncFrameHandler("CLIENT"); CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(_client, proxyUri, clientFrameHandler); @@ -299,7 +307,7 @@ public class WebSocketProxyTest frame = serverFrameHandler.receivedFrames.poll(); assertNull(frame); - // Server2Proxy + // Proxy2Server frame = proxyServerSide.receivedFrames.poll(); assertNotNull(frame); closeStatus = CloseStatus.getCloseStatus(frame); @@ -328,7 +336,7 @@ public class WebSocketProxyTest { serverFrameHandler.throwOnFrame(); WebSocketProxy.Client2Proxy proxyClientSide = proxy.client2Proxy; - WebSocketProxy.Server2Proxy proxyServerSide = proxy.server2Proxy; + WebSocketProxy.Proxy2Server proxyServerSide = proxy.proxy2Server; TestAsyncFrameHandler clientFrameHandler = new TestAsyncFrameHandler("CLIENT") { @@ -365,7 +373,7 @@ public class WebSocketProxyTest assertThat(frame.getPayloadAsUTF8(), is("hello world")); assertNull(serverFrameHandler.receivedFrames.poll()); - // Server2Proxy + // Proxy2Server frame = proxyServerSide.receivedFrames.poll(); closeStatus = CloseStatus.getCloseStatus(frame); assertThat(closeStatus.getCode(), is(CloseStatus.SERVER_ERROR)); @@ -382,7 +390,7 @@ public class WebSocketProxyTest assertNull(proxyClientSide.receivedFrames.poll()); assertThat(proxyClientSide.getState(), is(WebSocketProxy.State.FAILED)); - // Server2Proxy is failed by the Client2Proxy + // Proxy2Server is failed by the Client2Proxy assertNull(proxyServerSide.receivedFrames.poll()); assertThat(proxyServerSide.getState(), is(WebSocketProxy.State.FAILED)); } diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java index 2ee6fa2d972..d8e9483ade8 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/server/WebSocketServerTest.java @@ -94,9 +94,9 @@ public class WebSocketServerTest extends WebSocketTester TestFrameHandler serverHandler = new TestFrameHandler() { @Override - public boolean isAutoDemanding() + protected void demand() { - return false; + // Demand is explicitly performed by the test code. } }; @@ -147,17 +147,10 @@ public class WebSocketServerTest extends WebSocketTester @Override public void onOpen(CoreSession coreSession, Callback callback) { - super.onOpen(coreSession); - callback.succeeded(); + super.onOpen(coreSession, callback); coreSession.demand(1); } - @Override - public boolean isAutoDemanding() - { - return false; - } - @Override public void onFrame(Frame frame, Callback callback) { @@ -254,17 +247,10 @@ public class WebSocketServerTest extends WebSocketTester @Override public void onOpen(CoreSession coreSession, Callback callback) { - super.onOpen(coreSession); - callback.succeeded(); + super.onOpen(coreSession, callback); coreSession.demand(3); } - @Override - public boolean isAutoDemanding() - { - return false; - } - @Override public void onFrame(Frame frame, Callback callback) { @@ -272,6 +258,12 @@ public class WebSocketServerTest extends WebSocketTester receivedFrames.offer(frame); receivedCallbacks.offer(callback); } + + @Override + protected void demand() + { + // Demand is explicitly performed by the test code. + } }; server = new WebSocketServer(serverHandler); @@ -325,12 +317,6 @@ public class WebSocketServerTest extends WebSocketTester super.onClosed(closeStatus); } - @Override - public boolean isAutoDemanding() - { - return false; - } - @Override public void onFrame(Frame frame, Callback callback) { @@ -392,12 +378,6 @@ public class WebSocketServerTest extends WebSocketTester coreSession.demand(2); } - @Override - public boolean isAutoDemanding() - { - return false; - } - @Override public void onFrame(Frame frame, Callback callback) { diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/MessageReaderTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/MessageReaderTest.java index 2cc0366bd2f..6a24fc653bb 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/MessageReaderTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/MessageReaderTest.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.messages.MessageReader; @@ -37,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class MessageReaderTest { - private final MessageReader reader = new MessageReader(); + private final MessageReader reader = new MessageReader(new CoreSession.Empty()); private final CompletableFuture message = new CompletableFuture<>(); private boolean first = true; diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/PartialStringMessageSinkTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/PartialStringMessageSinkTest.java index 4d0985d9159..22fed88f870 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/PartialStringMessageSinkTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/PartialStringMessageSinkTest.java @@ -51,7 +51,7 @@ public class PartialStringMessageSinkTest @BeforeEach public void before() throws Exception { - messageSink = new PartialStringMessageSink(coreSession, endpoint.getMethodHandle()); + messageSink = new PartialStringMessageSink(coreSession, endpoint.getMethodHandle(), true); } @Test diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/StringMessageSinkTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/StringMessageSinkTest.java index 3feffda0e53..a5448b74b45 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/StringMessageSinkTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/util/StringMessageSinkTest.java @@ -44,7 +44,7 @@ public class StringMessageSinkTest @Test public void testMaxMessageSize() throws Exception { - StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle()); + StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle(), true); ByteBuffer utf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0xF0, (byte)0x90, (byte)0x8D, (byte)0x88}); FutureCallback callback = new FutureCallback(); @@ -60,7 +60,7 @@ public class StringMessageSinkTest @Test public void testValidUtf8() throws Exception { - StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle()); + StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle(), true); ByteBuffer utf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0xF0, (byte)0x90, (byte)0x8D, (byte)0x88}); FutureCallback callback = new FutureCallback(); @@ -73,7 +73,7 @@ public class StringMessageSinkTest @Test public void testUtf8Continuation() throws Exception { - StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle()); + StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle(), true); ByteBuffer firstUtf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0xF0, (byte)0x90}); ByteBuffer continuationUtf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0x8D, (byte)0x88}); @@ -91,7 +91,7 @@ public class StringMessageSinkTest @Test public void testInvalidSingleFrameUtf8() throws Exception { - StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle()); + StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle(), true); ByteBuffer invalidUtf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0xF0, (byte)0x90, (byte)0x8D}); FutureCallback callback = new FutureCallback(); @@ -106,7 +106,7 @@ public class StringMessageSinkTest @Test public void testInvalidMultiFrameUtf8() throws Exception { - StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle()); + StringMessageSink messageSink = new StringMessageSink(coreSession, endpoint.getMethodHandle(), true); ByteBuffer firstUtf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0xF0, (byte)0x90}); ByteBuffer continuationUtf8Payload = BufferUtil.toBuffer(new byte[]{(byte)0x8D}); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java deleted file mode 100644 index c5607e8651f..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java +++ /dev/null @@ -1,42 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * The possible batch modes when enqueuing outgoing frames. - */ -public enum BatchMode -{ - /** - * Implementers are free to decide whether to send or not frames - * to the network layer. - */ - AUTO, - - /** - * Implementers must batch frames. - */ - ON, - - /** - * Implementers must send frames to the network layer. - */ - OFF; - - public static BatchMode max(BatchMode one, BatchMode two) - { - // Return the BatchMode that has the higher priority, where AUTO < ON < OFF. - return one.ordinal() < two.ordinal() ? two : one; - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Callback.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Callback.java new file mode 100644 index 00000000000..74e90854ecf --- /dev/null +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Callback.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.api; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +/** + * Callback for Write events. + *

+ * NOTE: We don't expose org.eclipse.jetty.util.Callback here as that would complicate matters with the WebAppContext's classloader isolation. + */ +public interface Callback +{ + Callback NOOP = new Callback() + { + }; + + /** + * Creates a callback from the given success and failure lambdas. + * + * @param success called when the callback succeeds + * @param failure called when the callback fails + * @return a new callback + */ + static Callback from(Runnable success, Consumer failure) + { + return new Callback() + { + @Override + public void succeed() + { + success.run(); + } + + @Override + public void fail(Throwable x) + { + failure.accept(x); + } + }; + } + + /** + *

+ * Callback invoked when the write succeeds. + *

+ * + * @see #fail(Throwable) + */ + default void succeed() + { + } + + /** + *

+ * Callback invoked when the write fails. + *

+ * + * @param x the reason for the write failure + */ + default void fail(Throwable x) + { + } + + class Completable extends CompletableFuture implements Callback + { + public static Completable with(Consumer consumer) + { + Completable completable = new Completable(); + consumer.accept(completable); + return completable; + } + + @Override + public void succeed() + { + complete(null); + } + + @Override + public void fail(Throwable x) + { + completeExceptionally(x); + } + + public Completable compose(Consumer consumer) + { + Completable completable = new Completable(); + thenAccept(ignored -> consumer.accept(completable)); + return completable; + } + } +} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java deleted file mode 100644 index 4027e2751ce..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java +++ /dev/null @@ -1,50 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -public class CloseStatus -{ - private static final int MAX_CONTROL_PAYLOAD = 125; - public static final int MAX_REASON_PHRASE = MAX_CONTROL_PAYLOAD - 2; - - private final int code; - private final String phrase; - - /** - * Creates a reason for closing a web socket connection with the given code and reason phrase. - * - * @param closeCode the close code - * @param reasonPhrase the reason phrase - * @see StatusCode - */ - public CloseStatus(int closeCode, String reasonPhrase) - { - this.code = closeCode; - this.phrase = reasonPhrase; - if (reasonPhrase.length() > MAX_REASON_PHRASE) - { - throw new IllegalArgumentException("Phrase exceeds maximum length of " + MAX_REASON_PHRASE); - } - } - - public int getCode() - { - return code; - } - - public String getPhrase() - { - return phrase; - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Configurable.java similarity index 80% rename from jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java rename to jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Configurable.java index ec343d76f02..acaf306ea50 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Configurable.java @@ -16,12 +16,10 @@ package org.eclipse.jetty.websocket.api; import java.time.Duration; /** - * Settings for WebSocket operations. + *

Implementations allow to configure WebSocket parameters.

*/ -public interface WebSocketPolicy +public interface Configurable { - WebSocketBehavior getBehavior(); - /** * The duration that a websocket may be idle before being closed by the implementation * @@ -29,6 +27,13 @@ public interface WebSocketPolicy */ Duration getIdleTimeout(); + /** + * The duration that a websocket may be idle before being closed by the implementation + * + * @param duration the timeout duration (may not be null or negative) + */ + void setIdleTimeout(Duration duration); + /** * The input (read from network layer) buffer size. *

@@ -39,6 +44,13 @@ public interface WebSocketPolicy */ int getInputBufferSize(); + /** + * The input (read from network layer) buffer size. + * + * @param size the size in bytes + */ + void setInputBufferSize(int size); + /** * The output (write to network layer) buffer size. *

@@ -49,6 +61,13 @@ public interface WebSocketPolicy */ int getOutputBufferSize(); + /** + * The output (write to network layer) buffer size. + * + * @param size the size in bytes + */ + void setOutputBufferSize(int size); + /** * Get the maximum size of a binary message during parsing. *

@@ -64,6 +83,16 @@ public interface WebSocketPolicy */ long getMaxBinaryMessageSize(); + /** + * The maximum size of a binary message during parsing/generating. + *

+ * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

+ * + * @param size the maximum allowed size of a binary message. + */ + void setMaxBinaryMessageSize(long size); + /** * Get the maximum size of a text message during parsing. *

@@ -79,51 +108,6 @@ public interface WebSocketPolicy */ long getMaxTextMessageSize(); - /** - * The maximum payload size of any WebSocket Frame which can be received. - * - * @return the maximum size of a WebSocket Frame. - */ - long getMaxFrameSize(); - - /** - * If true, frames are automatically fragmented to respect the maximum frame size. - * - * @return whether to automatically fragment incoming WebSocket Frames. - */ - boolean isAutoFragment(); - - /** - * The duration that a websocket may be idle before being closed by the implementation - * - * @param duration the timeout duration (may not be null or negative) - */ - void setIdleTimeout(Duration duration); - - /** - * The input (read from network layer) buffer size. - * - * @param size the size in bytes - */ - void setInputBufferSize(int size); - - /** - * The output (write to network layer) buffer size. - * - * @param size the size in bytes - */ - void setOutputBufferSize(int size); - - /** - * The maximum size of a binary message during parsing/generating. - *

- * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - *

- * - * @param size the maximum allowed size of a binary message. - */ - void setMaxBinaryMessageSize(long size); - /** * The maximum size of a text message during parsing/generating. *

@@ -133,6 +117,13 @@ public interface WebSocketPolicy */ void setMaxTextMessageSize(long size); + /** + * The maximum payload size of any WebSocket Frame which can be received. + * + * @return the maximum size of a WebSocket Frame. + */ + long getMaxFrameSize(); + /** * The maximum payload size of any WebSocket Frame which can be received. *

@@ -143,10 +134,39 @@ public interface WebSocketPolicy */ void setMaxFrameSize(long maxFrameSize); + /** + * If true, frames are automatically fragmented to respect the maximum frame size. + * + * @return whether to automatically fragment incoming WebSocket Frames. + */ + boolean isAutoFragment(); + /** * If set to true, frames are automatically fragmented to respect the maximum frame size. * * @param autoFragment whether to automatically fragment incoming WebSocket Frames. */ void setAutoFragment(boolean autoFragment); + + /** + * Get the maximum number of data frames allowed to be waiting to be sent at any one time. + * The default value is -1, this indicates there is no limit on how many frames can be + * queued to be sent by the implementation. If the limit is exceeded, subsequent frames + * sent are failed with a {@link java.nio.channels.WritePendingException} but + * the connection is not failed and will remain open. + * + * @return the max number of frames. + */ + int getMaxOutgoingFrames(); + + /** + * Set the maximum number of data frames allowed to be waiting to be sent at any one time. + * The default value is -1, this indicates there is no limit on how many frames can be + * queued to be sent by the implementation. If the limit is exceeded, subsequent frames + * sent are failed with a {@link java.nio.channels.WritePendingException} but + * the connection is not failed and will remain open. + * + * @param maxOutgoingFrames the max number of frames. + */ + void setMaxOutgoingFrames(int maxOutgoingFrames); } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java deleted file mode 100644 index 5800bc57892..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java +++ /dev/null @@ -1,196 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -import java.io.IOException; -import java.net.SocketAddress; -import java.nio.ByteBuffer; - -public interface RemoteEndpoint -{ - /** - * Send a binary message, returning when all bytes of the message has been transmitted. - *

- * Note: this is a blocking call - * - * @param data the message to be sent - * @throws IOException if unable to send the bytes - */ - void sendBytes(ByteBuffer data) throws IOException; - - /** - * Initiates the asynchronous transmission of a binary message. This method returns before the message is transmitted. - * Developers may provide a callback to be notified when the message has been transmitted or resulted in an error. - * - * @param data the data being sent - * @param callback callback to notify of success or failure of the write operation - */ - void sendBytes(ByteBuffer data, WriteCallback callback); - - /** - * Send a binary message in pieces, blocking until all of the message has been transmitted. - * The runtime reads the message in order. Non-final pieces are - * sent with isLast set to false. The final piece must be sent with isLast set to true. - * - * @param fragment the piece of the message being sent - * @param isLast true if this is the last piece of the partial bytes - * @throws IOException if unable to send the partial bytes - */ - void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException; - - /** - * Initiates the asynchronous transmission of a partial binary message. This method returns before the message is - * transmitted. - * The runtime reads the message in order. Non-final pieces are sent with isLast - * set to false. The final piece must be sent with isLast set to true. - * Developers may provide a callback to be notified when the message has been transmitted or resulted in an error. - * - * @param fragment the data being sent - * @param isLast true if this is the last piece of the partial bytes - * @param callback callback to notify of success or failure of the write operation - */ - void sendPartialBytes(ByteBuffer fragment, boolean isLast, WriteCallback callback); - - /** - * Send a text message, blocking until all bytes of the message has been transmitted. - *

- * Note: this is a blocking call - * - * @param text the message to be sent - * @throws IOException if unable to send the text message - */ - void sendString(String text) throws IOException; - - /** - * Initiates the asynchronous transmission of a text message. This method may return before the message is - * transmitted. Developers may provide a callback to - * be notified when the message has been transmitted or resulted in an error. - * - * @param text the text being sent - * @param callback callback to notify of success or failure of the write operation - */ - void sendString(String text, WriteCallback callback); - - /** - * Send a text message in pieces, blocking until all of the message has been transmitted. The runtime reads the - * message in order. Non-final pieces are sent - * with isLast set to false. The final piece must be sent with isLast set to true. - * - * @param fragment the piece of the message being sent - * @param isLast true if this is the last piece of the partial bytes - * @throws IOException if unable to send the partial bytes - */ - void sendPartialString(String fragment, boolean isLast) throws IOException; - - /** - * Initiates the asynchronous transmission of a partial text message. - * This method may return before the message is transmitted. - * The runtime reads the message in order. Non-final pieces are sent with isLast - * set to false. The final piece must be sent with isLast set to true. - * Developers may provide a callback to be notified when the message has been transmitted or resulted in an error. - * - * @param fragment the text being sent - * @param isLast true if this is the last piece of the partial bytes - * @param callback callback to notify of success or failure of the write operation - */ - void sendPartialString(String fragment, boolean isLast, WriteCallback callback); - - /** - * Send a Ping message containing the given application data to the remote endpoint, blocking until all of the - * message has been transmitted. - * The corresponding Pong message may be picked up using the MessageHandler.Pong handler. - * - * @param applicationData the data to be carried in the ping request - * @throws IOException if unable to send the ping - */ - void sendPing(ByteBuffer applicationData) throws IOException; - - /** - * Asynchronously send a Ping message containing the given application data to the remote endpoint. - * The corresponding Pong message may be picked up using the MessageHandler.Pong handler. - * - * @param applicationData the data to be carried in the ping request - * @param callback callback to notify of success or failure of the write operation - */ - void sendPing(ByteBuffer applicationData, WriteCallback callback); - - /** - * Allows the developer to send an unsolicited Pong message containing the given application data - * in order to serve as a unidirectional heartbeat for the session, this will block until - * all of the message has been transmitted. - * - * @param applicationData the application data to be carried in the pong response. - * @throws IOException if unable to send the pong - */ - void sendPong(ByteBuffer applicationData) throws IOException; - - /** - * Allows the developer to asynchronously send an unsolicited Pong message containing the given application data - * in order to serve as a unidirectional heartbeat for the session. - * - * @param applicationData the application data to be carried in the pong response. - * @param callback callback to notify of success or failure of the write operation - */ - void sendPong(ByteBuffer applicationData, WriteCallback callback); - - /** - * @return the batch mode with which messages are sent. - * @see #flush() - */ - BatchMode getBatchMode(); - - /** - * Set the batch mode with which messages are sent. - * - * @param mode the batch mode to use - * @see #flush() - */ - void setBatchMode(BatchMode mode); - - /** - * Get the maximum number of data frames allowed to be waiting to be sent at any one time. - * The default value is -1, this indicates there is no limit on how many frames can be - * queued to be sent by the implementation. If the limit is exceeded, subsequent frames - * sent are failed with a {@link java.nio.channels.WritePendingException} but - * the connection is not failed and will remain open. - * - * @return the max number of frames. - */ - int getMaxOutgoingFrames(); - - /** - * Set the maximum number of data frames allowed to be waiting to be sent at any one time. - * The default value is -1, this indicates there is no limit on how many frames can be - * queued to be sent by the implementation. If the limit is exceeded, subsequent frames - * sent are failed with a {@link java.nio.channels.WritePendingException} but - * the connection is not failed and will remain open. - * - * @param maxOutgoingFrames the max number of frames. - */ - void setMaxOutgoingFrames(int maxOutgoingFrames); - - /** - * Get the SocketAddress for the established connection. - * - * @return the SocketAddress for the established connection. - */ - SocketAddress getRemoteAddress(); - - /** - * Flushes messages that may have been batched by the implementation. - * - * @throws IOException if the flush fails - */ - void flush() throws IOException; -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java index 3215c13577b..aba060cb4a3 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Session.java @@ -14,184 +14,349 @@ package org.eclipse.jetty.websocket.api; import java.io.Closeable; -import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.ByteBuffer; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** - * Session represents an active link of communications with a Remote WebSocket Endpoint. + *

{@link Session} represents an active link of + * communication with a remote WebSocket endpoint.

+ *

{@link Session} APIs can be used to configure + * the various parameters that control the behavior + * of the WebSocket communication, such as + * {@link #setMaxTextMessageSize(long)}, and to send + * WebSocket frames or messages to the other peer.

+ *

The passive link of communication that receives + * WebSocket events is {@link Listener}.

*/ -public interface Session extends WebSocketPolicy, Closeable +public interface Session extends Configurable, Closeable { /** - * Request a close of the current conversation with a normal status code and no reason phrase. - *

- * This will enqueue a graceful close to the remote endpoint. + *

Explicitly demands for WebSocket events.

+ *

This method should be called only when the WebSocket endpoint is not + * demanding automatically, as defined by {@link WebSocket#autoDemand()} + * and {@link Listener.AutoDemanding}.

+ *

In general, invoking this method results in a listener method or + * an annotated method to be called when the corresponding event is + * ready to be delivered.

+ *

For WebSocket endpoints that wants to receive frame events + * (for example by overriding {@link Listener#onWebSocketFrame(Frame, Callback)}), + * invoking this method will result in a frame event being delivered to + * the listener/annotated method when a new frame is received.

+ *

For WebSocket endpoints that want to receive whole message + * events (for example by overriding {@link Listener#onWebSocketText(String)}), + * invoking this method will result in a message event being delivered to + * the listener/annotated method when a new message is received. + * The implementation will automatically demand for more frames until a + * whole message is assembled and then deliver the whole message as event.

+ *

Note that even when the WebSocket endpoint is interested in whole + * messages, calling this method is necessary not only to possibly receive + * the next whole message, but also to receive control frames (such as + * PING or CLOSE frames). + * Failing to call this method after receiving a whole message results + * in the CLOSE frame event to not be processed, and therefore for the + * endpoint to not notice when the other peer closed the WebSocket + * communication.

* - * @see #close(CloseStatus) - * @see #close(int, String) + * @throws IllegalStateException if the WebSocket endpoint is auto-demanding + */ + void demand(); + + /** + *

Initiates the asynchronous send of a BINARY message, notifying + * the given callback when the message send is completed, either + * successfully or with a failure.

+ * + * @param buffer the message bytes to send + * @param callback callback to notify when the send operation is complete + */ + void sendBinary(ByteBuffer buffer, Callback callback); + + /** + *

Initiates the asynchronous send of a BINARY frame, possibly part + * of a larger binary message, notifying the given callback when the frame + * send is completed, either successfully or with a failure.

+ *

Non-final frames must be sent with the parameter {@code last=false}. + * The final frame must be sent with {@code last=true}.

+ * + * @param buffer the frame bytes to send + * @param last whether this is the last frame of the message + * @param callback callback to notify when the send operation is complete + */ + void sendPartialBinary(ByteBuffer buffer, boolean last, Callback callback); + + /** + *

Initiates the asynchronous send of a TEXT message, notifying + * the given callback when the message send is completed, either + * successfully or with a failure.

+ * + * @param text the message text to send + * @param callback callback to notify when the send operation is complete + */ + void sendText(String text, Callback callback); + + /** + *

Initiates the asynchronous send of a TEXT frame, possibly part + * of a larger binary message, notifying the given callback when the frame + * send is completed, either successfully or with a failure.

+ *

Non-final frames must be sent with the parameter {@code last=false}. + * The final frame must be sent with {@code last=true}.

+ * + * @param text the frame text to send + * @param last whether this is the last frame of the message + * @param callback callback to notify when the send operation is complete + */ + void sendPartialText(String text, boolean last, Callback callback); + + /** + *

Initiates the asynchronous send of a PING frame, notifying the given + * callback when the frame send is completed, either successfully or with + * a failure.

+ * + * @param applicationData the data to be carried in the PING frame + * @param callback callback to notify when the send operation is complete + */ + void sendPing(ByteBuffer applicationData, Callback callback); + + /** + *

Initiates the asynchronous send of a PONG frame, notifying the given + * callback when the frame send is completed, either successfully or with + * a failure.

+ * + * @param applicationData the data to be carried in the PONG frame + * @param callback callback to notify when the send operation is complete + */ + void sendPong(ByteBuffer applicationData, Callback callback); + + /** + *

Equivalent to {@code close(StatusCode.NORMAL, null, Callback.NOOP)}.

+ * + * @see #close(int, String, Callback) * @see #disconnect() */ @Override - void close(); - - /** - * Request Close the current conversation, giving a reason for the closure. Note the websocket spec defines the acceptable uses of status codes and reason - * phrases. - *

- * This will enqueue a graceful close to the remote endpoint. - * - * @param closeStatus the reason for the closure - * @see #close() - * @see #close(int, String) - * @see #disconnect() - */ - void close(CloseStatus closeStatus); - - /** - * Send a websocket Close frame, with status code. - *

- * This will enqueue a graceful close to the remote endpoint. - * - * @param statusCode the status code - * @param reason the (optional) reason. (can be null for no reason) - * @see StatusCode - * @see #close() - * @see #close(CloseStatus) - * @see #disconnect() - */ - void close(int statusCode, String reason); - - /** - * Send a websocket Close frame, with status code. - *

- * This will enqueue a graceful close to the remote endpoint. - * - * @param statusCode the status code - * @param reason the (optional) reason. (can be null for no reason) - * @param callback the callback to track close frame sent (or failed) - * @see StatusCode - * @see #close() - * @see #close(CloseStatus) - * @see #disconnect() - */ - default void close(int statusCode, String reason, WriteCallback callback) + default void close() { - try - { - close(statusCode, reason); - callback.writeSuccess(); - } - catch (Throwable t) - { - callback.writeFailed(t); - } + close(StatusCode.NORMAL, null, Callback.NOOP); } /** - * Issue a harsh disconnect of the underlying connection. - *

- * This will terminate the connection, without sending a websocket close frame. - *

- * Once called, any read/write activity on the websocket from this point will be indeterminate. - *

- * Once the underlying connection has been determined to be closed, the various onClose() events (either - * {@link WebSocketListener#onWebSocketClose(int, String)} or {@link OnWebSocketClose}) will be called on your - * websocket. + *

Sends a websocket CLOSE frame, with status code and reason, notifying + * the given callback when the frame send is completed, either successfully + * or with a failure.

* + * @param statusCode the status code + * @param reason the (optional) reason + * @param callback callback to notify when the send operation is complete + * @see StatusCode * @see #close() - * @see #close(CloseStatus) - * @see #close(int, String) + * @see #disconnect() + */ + void close(int statusCode, String reason, Callback callback); + + /** + *

Abruptly closes the WebSocket connection without sending a CLOSE frame.

+ * + * @see #close(int, String, Callback) */ void disconnect(); /** - * The Local Socket Address for the active Session - *

- * Do not assume that this will return a {@link InetSocketAddress} in all cases. - * Use of various proxies, and even UnixSockets can result a SocketAddress being returned - * without supporting {@link InetSocketAddress} - *

- * - * @return the SocketAddress for the local connection, or null if not supported by Session + * @return the local SocketAddress for the connection, if available */ - SocketAddress getLocalAddress(); + SocketAddress getLocalSocketAddress(); /** - * Access the (now read-only) {@link WebSocketPolicy} in use for this connection. - * - * @return the policy in use + * @return the remote SocketAddress for the connection, if available */ - default WebSocketPolicy getPolicy() - { - return this; - } + SocketAddress getRemoteSocketAddress(); /** - * Returns the version of the websocket protocol currently being used. This is taken as the value of the Sec-WebSocket-Version header used in the opening - * handshake. i.e. "13". + *

Returns the version of the WebSocket protocol currently being used.

+ *

This is taken as the value of the {@code Sec-WebSocket-Version} header + * used in the {@link #getUpgradeRequest() upgrade request}. * - * @return the protocol version + * @return the WebSocket protocol version */ String getProtocolVersion(); /** - * Return a reference to the RemoteEndpoint object representing the other end of this conversation. - * - * @return the remote endpoint - */ - RemoteEndpoint getRemote(); - - /** - * The Remote Socket Address for the active Session - *

- * Do not assume that this will return a {@link InetSocketAddress} in all cases. - * Use of various proxies, and even UnixSockets can result a SocketAddress being returned - * without supporting {@link InetSocketAddress} - *

- * - * @return the SocketAddress for the remote connection, or null if not supported by Session - */ - SocketAddress getRemoteAddress(); - - /** - * Get the UpgradeRequest used to create this session - * * @return the UpgradeRequest used to create this session */ UpgradeRequest getUpgradeRequest(); /** - * Get the UpgradeResponse used to create this session - * * @return the UpgradeResponse used to create this session */ UpgradeResponse getUpgradeResponse(); /** - * Return true if and only if the underlying socket is open. - * * @return whether the session is open */ boolean isOpen(); /** - * Return true if and only if the underlying socket is using a secure transport. - * - * @return whether its using a secure transport + * @return whether the underlying socket is using a secure transport */ boolean isSecure(); /** - * Suspend the delivery of incoming WebSocket frames. - *

- * If this is called from inside the scope of the message handler the suspend takes effect immediately. - * If suspend is called outside the scope of the message handler then the call may take effect - * after 1 more frame is delivered. - *

- * - * @return the suspend token suitable for resuming the reading of data on the connection. + *

The passive link of communication with a remote WebSocket endpoint.

+ *

Applications provide WebSocket endpoints that implement this interface + * to receive WebSocket events from the remote peer, and can use + * {@link Session} for configuration and to send WebSocket frames or messages + * to the other peer.

*/ - SuspendToken suspend(); + interface Listener + { + /** + *

A WebSocket {@link Session} has opened successfully and is ready to be used.

+ *

Applications can store the given {@link Session} as a field so it can be used + * to send messages back to the other peer.

+ * + * @param session the WebSocket session + */ + default void onWebSocketOpen(Session session) + { + } + + /** + *

A WebSocket frame has been received.

+ *

The received frames may be control frames such as PING, PONG or CLOSE, + * or data frames either BINARY or TEXT.

+ * + * @param frame the received frame + */ + default void onWebSocketFrame(Frame frame, Callback callback) + { + } + + /** + *

A WebSocket PING frame has been received.

+ * + * @param payload the PING payload + */ + default void onWebSocketPing(ByteBuffer payload) + { + } + + /** + *

A WebSocket PONG frame has been received.

+ * + * @param payload the PONG payload + */ + default void onWebSocketPong(ByteBuffer payload) + { + } + + /** + *

A WebSocket BINARY (or associated CONTINUATION) frame has been received.

+ *

The {@code ByteBuffer} is read-only, and will be recycled when the {@code callback} + * is completed.

+ * + * @param payload the BINARY frame payload + * @param last whether this is the last frame + * @param callback the callback to complete when the payload has been processed + */ + default void onWebSocketPartialBinary(ByteBuffer payload, boolean last, Callback callback) + { + } + + /** + *

A WebSocket TEXT (or associated CONTINUATION) frame has been received.

+ * + * @param payload the text message payload + *

+ * Note that due to framing, there is a above average chance of any UTF8 sequences being split on the + * border between two frames will result in either the previous frame, or the next frame having an + * invalid UTF8 sequence, but the combined frames having a valid UTF8 sequence. + *

+ * The String being provided here will not end in a split UTF8 sequence. Instead this partial sequence + * will be held over until the next frame is received. + * @param last whether this is the last frame + */ + default void onWebSocketPartialText(String payload, boolean last) + { + } + + /** + *

A WebSocket BINARY message has been received.

+ * + * @param payload the raw payload array received + */ + default void onWebSocketBinary(ByteBuffer payload, Callback callback) + { + } + + /** + *

A WebSocket TEXT message has been received.

+ * + * @param message the text payload + */ + default void onWebSocketText(String message) + { + } + + /** + *

A WebSocket error has occurred during the processing of WebSocket frames.

+ *

Usually errors occurs from bad or malformed incoming packets, for example + * text frames that do not contain UTF-8 bytes, frames that are too big, or other + * violations of the WebSocket specification.

+ *

The WebSocket {@link Session} will be closed, but applications may + * explicitly {@link Session#close(int, String, Callback) close} the + * {@link Session} providing a different status code or reason.

+ * + * @param cause the error that occurred + */ + default void onWebSocketError(Throwable cause) + { + } + + /** + *

The WebSocket {@link Session} has been closed.

+ * + * @param statusCode the close {@link StatusCode status code} + * @param reason the optional reason for the close + */ + default void onWebSocketClose(int statusCode, String reason) + { + } + + /** + *

Tag interface that signals that the WebSocket endpoint + * is demanding for WebSocket frames automatically.

+ * + * @see WebSocket#autoDemand() + */ + interface AutoDemanding extends Session.Listener + { + } + + abstract class Abstract implements Listener + { + private volatile Session session; + + @Override + public void onWebSocketOpen(Session session) + { + this.session = session; + } + + public Session getSession() + { + return session; + } + + public boolean isOpen() + { + Session session = this.session; + return session != null && session.isOpen(); + } + } + + abstract class AbstractAutoDemanding extends Abstract implements AutoDemanding + { + } + } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java deleted file mode 100644 index 95d953a888a..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java +++ /dev/null @@ -1,25 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * Connection suspend token - */ -public interface SuspendToken -{ - /** - * Resume a previously suspended connection. - */ - void resume(); -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java deleted file mode 100644 index 632b4970b90..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java +++ /dev/null @@ -1,77 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * Default implementation of the {@link WebSocketListener}. - *

- * Convenient abstract class to base standard WebSocket implementations off of. - */ -public class WebSocketAdapter implements WebSocketListener -{ - private volatile Session session; - private RemoteEndpoint remote; - - public RemoteEndpoint getRemote() - { - return remote; - } - - public Session getSession() - { - return session; - } - - public boolean isConnected() - { - Session sess = this.session; - return (sess != null) && (sess.isOpen()); - } - - public boolean isNotConnected() - { - return !isConnected(); - } - - @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) - { - /* do nothing */ - } - - @Override - public void onWebSocketClose(int statusCode, String reason) - { - /* do nothing */ - } - - @Override - public void onWebSocketConnect(Session sess) - { - this.session = sess; - this.remote = sess.getRemote(); - } - - @Override - public void onWebSocketError(Throwable cause) - { - /* do nothing */ - } - - @Override - public void onWebSocketText(String message) - { - /* do nothing */ - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java deleted file mode 100644 index cf6dd8b743f..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java +++ /dev/null @@ -1,26 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * Behavior for how the WebSocket should operate. - *

- * This dictated by the RFC 6455 spec in various places, where certain behavior must be performed depending on - * operation as a CLIENT vs a SERVER - */ -public enum WebSocketBehavior -{ - CLIENT, - SERVER -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java deleted file mode 100644 index 445900db064..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java +++ /dev/null @@ -1,58 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * Core WebSocket Connection Listener - */ -public interface WebSocketConnectionListener -{ - /** - * A Close Event was received. - *

- * The underlying Connection will be considered closed at this point. - * - * @param statusCode the close status code. (See {@link StatusCode}) - * @param reason the optional reason for the close. - */ - default void onWebSocketClose(int statusCode, String reason) - { - } - - /** - * A WebSocket {@link Session} has connected successfully and is ready to be used. - *

- * Note: It is a good idea to track this session as a field in your object so that you can write messages back via the {@link RemoteEndpoint} - * - * @param session the websocket session. - */ - default void onWebSocketConnect(Session session) - { - } - - /** - * A WebSocket exception has occurred. - *

- * This is a way for the internal implementation to notify of exceptions occured during the processing of websocket. - *

- * Usually this occurs from bad / malformed incoming packets. (example: bad UTF8 data, frames that are too big, violations of the spec) - *

- * This will result in the {@link Session} being closed by the implementing side. - * - * @param cause the error that occurred. - */ - default void onWebSocketError(Throwable cause) - { - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketFrameListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketFrameListener.java deleted file mode 100644 index d61643a2d08..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketFrameListener.java +++ /dev/null @@ -1,27 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * WebSocket Frame Listener interface for incoming WebSocket frames. - */ -public interface WebSocketFrameListener extends WebSocketConnectionListener -{ - /** - * A WebSocket frame has been received. - * - * @param frame the immutable frame received - */ - void onWebSocketFrame(Frame frame); -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java deleted file mode 100644 index 52869d0030d..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java +++ /dev/null @@ -1,40 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * Basic WebSocket Listener interface for incoming WebSocket message events. - */ -public interface WebSocketListener extends WebSocketConnectionListener -{ - /** - * A WebSocket binary frame has been received. - * - * @param payload the raw payload array received - * @param offset the offset in the payload array where the data starts - * @param len the length of bytes in the payload - */ - default void onWebSocketBinary(byte[] payload, int offset, int len) - { - } - - /** - * A WebSocket Text frame was received. - * - * @param message the message - */ - default void onWebSocketText(String message) - { - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java deleted file mode 100644 index cd317e5aa2a..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -import java.nio.ByteBuffer; - -/** - * WebSocket Partial Message Listener interface for incoming WebSocket TEXT/BINARY/CONTINUATION frames. - */ -public interface WebSocketPartialListener extends WebSocketConnectionListener -{ - /** - * A WebSocket BINARY (or associated CONTINUATION) frame has been received. - *

- * Important Note: The payload {@code ByteBuffer} cannot be modified, and the ByteBuffer object itself - * will be recycled on completion of this method call, make a copy of the data contained within if you want to - * retain it between calls. - * - * @param payload the binary message frame payload - * @param fin true if this is the final frame, false otherwise - */ - default void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) - { - } - - /** - * A WebSocket TEXT (or associated CONTINUATION) frame has been received. - * - * @param payload the text message payload - *

- * Note that due to framing, there is a above average chance of any UTF8 sequences being split on the - * border between two frames will result in either the previous frame, or the next frame having an - * invalid UTF8 sequence, but the combined frames having a valid UTF8 sequence. - *

- * The String being provided here will not end in a split UTF8 sequence. Instead this partial sequence - * will be held over until the next frame is received. - * @param fin true if this is the final frame, false otherwise - */ - default void onWebSocketPartialText(String payload, boolean fin) - { - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java deleted file mode 100644 index f238c736e1e..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java +++ /dev/null @@ -1,40 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -import java.nio.ByteBuffer; - -/** - * WebSocket PING/PONG Listener interface for incoming WebSocket PING/PONG frames. - */ -public interface WebSocketPingPongListener extends WebSocketConnectionListener -{ - /** - * A WebSocket PING has been received. - * - * @param payload the ping payload - */ - default void onWebSocketPing(ByteBuffer payload) - { - } - - /** - * A WebSocket PONG has been received. - * - * @param payload the pong payload - */ - default void onWebSocketPong(ByteBuffer payload) - { - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java deleted file mode 100644 index c66b40bdc21..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java +++ /dev/null @@ -1,62 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.api; - -/** - * Callback for Write events. - *

- * NOTE: We don't expose org.eclipse.jetty.util.Callback here as that would complicate matters with the WebAppContext's classloader isolation. - */ -public interface WriteCallback -{ - WriteCallback NOOP = new WriteCallback() - { - }; - - /** - *

- * Callback invoked when the write fails. - *

- * - * @param x the reason for the write failure - */ - default void writeFailed(Throwable x) - { - } - - /** - *

- * Callback invoked when the write succeeds. - *

- * - * @see #writeFailed(Throwable) - */ - default void writeSuccess() - { - } - - @Deprecated - class Adaptor implements WriteCallback - { - @Override - public void writeFailed(Throwable x) - { - } - - @Override - public void writeSuccess() - { - } - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java index 214889dee79..5ffb023ee4b 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketClose.java @@ -19,23 +19,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.eclipse.jetty.websocket.api.Session; - /** - * Annotation for tagging methods to receive connection close events. - *

- * Acceptable method patterns.
- * Note: {@code methodName} can be any name you want to use. + *

Annotation for methods to receive WebSocket close events.

+ *

Acceptable method patterns:

*
    - *
  1. {@code public void methodName(int statusCode, String reason)}
  2. - *
  3. public void methodName({@link Session} session, int statusCode, String reason)
  4. + *
  5. {@code public void (int statusCode, String reason)}
  6. + *
  7. {@code public void (Session session, int statusCode, String reason)}
  8. *
*/ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = - {ElementType.METHOD}) +@Target(ElementType.METHOD) public @interface OnWebSocketClose { - /* no config */ } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java index ab59ebd9638..838397db217 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketError.java @@ -19,23 +19,17 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.eclipse.jetty.websocket.api.Session; - /** - * Annotation for receiving websocket errors (exceptions) that have occurred internally in the websocket implementation. - *

- * Acceptable method patterns.
- * Note: {@code methodName} can be any name you want to use. + *

Annotation for methods to receive WebSocket errors.

+ *

Acceptable method patterns:

*
    - *
  1. public void methodName({@link Throwable} error)
  2. - *
  3. public void methodName({@link Session} session, {@link Throwable} error)
  4. + *
  5. {@code public void (Throwable cause)}
  6. + *
  7. {@code public void (Session session, Throwable cause)}
  8. *
*/ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = - {ElementType.METHOD}) +@Target(ElementType.METHOD) public @interface OnWebSocketError { - /* no config */ } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java index 711e0a40e49..17a28bd9620 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketFrame.java @@ -20,23 +20,20 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.eclipse.jetty.websocket.api.Frame; -import org.eclipse.jetty.websocket.api.Session; /** - * (ADVANCED) Annotation for tagging methods to receive frame events. - *

- * Acceptable method patterns.
- * Note: {@code methodName} can be any name you want to use. + *

Annotation for methods to receive WebSocket frame events.

+ *

Acceptable method patterns:

*
    - *
  1. public void methodName({@link Frame} frame)
  2. - *
  3. public void methodName({@link Session} session, {@link Frame} frame)
  4. + *
  5. {@code public void (Frame frame)}
  6. + *
  7. {@code public void (Session session, Frame frame)}
  8. *
+ * + * @see Frame */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = - {ElementType.METHOD}) +@Target(ElementType.METHOD) public @interface OnWebSocketFrame { - /* no config */ } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java index 71bb4a47232..2e7029aa779 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketMessage.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.websocket.api.annotations; +import java.io.InputStream; import java.io.Reader; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -20,15 +21,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; - /** - * Annotation for tagging methods to receive Binary or Text Message events. - *

- * Acceptable method patterns.
- * Note: {@code methodName} can be any name you want to use. - *

+ *

Annotation for methods to receive BINARY or TEXT WebSocket events.

+ *

Acceptable method patterns:

* Text Message Versions *
    *
  1. {@code public void methodName(String text)}
  2. @@ -36,32 +31,38 @@ import org.eclipse.jetty.websocket.api.WebSocketPartialListener; *
  3. {@code public void methodName(Reader reader)}
  4. *
  5. {@code public void methodName(Session session, Reader reader)}
  6. *
- *

Note: that the {@link Reader} in this case will always use UTF-8 encoding/charset (this is dictated by the RFC 6455 spec for Text Messages. If you need to - * use a non-UTF-8 encoding/charset, you are instructed to use the binary messaging techniques.

+ *

NOTE

+ *

Method that takes a {@link Reader} must have + * {@link WebSocket#autoDemand()} set to {@code true}.

+ *

NOTE

+ *

The {@link Reader} argument will always use the UTF-8 charset, + * (as dictated by RFC 6455). If you need to use a different charset, + * you must use BINARY messages.

* Binary Message Versions *
    - *
  1. {@code public void methodName(ByteBuffer message)}
  2. - *
  3. {@code public void methodName(Session session, ByteBuffer message)}
  4. - *
  5. {@code public void methodName(byte[] buf, int offset, int length)}
  6. - *
  7. {@code public void methodName(Session session, byte[] buf, int offset, int length)}
  8. + *
  9. {@code public void methodName(ByteBuffer message, Callback callback)}
  10. + *
  11. {@code public void methodName(Session session, ByteBuffer message, Callback callback)}
  12. *
  13. {@code public void methodName(InputStream stream)}
  14. *
  15. {@code public void methodName(Session session, InputStream stream)}
  16. *
+ *

NOTE

+ *

Method that takes a {@link InputStream} must have + * {@link WebSocket#autoDemand()} set to {@code true}.

* Partial Message Variations - *

These are used to receive partial messages without aggregating them into a complete WebSocket message. Instead the a boolean - * argument is supplied to indicate whether this is the last segment of data of the message. See {@link WebSocketPartialListener} - * interface for more details on partial messages.

+ *

These are used to receive individual frames (and therefore partial + * messages) without aggregating the frames into a complete WebSocket message. + * A {@code boolean} parameter is supplied to indicate whether the frame is + * the last segment of data of the message.

*
    - *
  1. {@code public void methodName(ByteBuffer payload, boolean last)}
  2. + *
  3. {@code public void methodName(ByteBuffer payload, boolean last, Callback callback)}
  4. + *
  5. {@code public void methodName(Session session, ByteBuffer payload, boolean last, Callback callback)}
  6. *
  7. {@code public void methodName(String payload, boolean last)}
  8. + *
  9. {@code public void methodName(Session session, String payload, boolean last)}
  10. *
- *

Note: Similar to the signatures above these can all be used with an optional first {@link Session} parameter.

*/ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = - {ElementType.METHOD}) +@Target(ElementType.METHOD) public @interface OnWebSocketMessage { - /* no config */ } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketOpen.java similarity index 68% rename from jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.java rename to jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketOpen.java index 216f9ba8a13..722d0a3e097 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketConnect.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/OnWebSocketOpen.java @@ -19,22 +19,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.eclipse.jetty.websocket.api.Session; - /** - * Annotation for tagging methods to receive connection open events. - *

- * Only 1 acceptable method pattern for this annotation.
- * Note: {@code methodName} can be any name you want to use. + *

Annotation for methods to receive WebSocket connect events.

+ *

Acceptable method patterns:

*
    - *
  1. public void methodName({@link Session} session)
  2. + *
  3. {@code public void (Session session)}
  4. *
*/ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = - {ElementType.METHOD}) -public @interface OnWebSocketConnect +@Target(ElementType.METHOD) +public @interface OnWebSocketOpen { - /* no config */ } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java index 89bf5af750c..29f54f0b341 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java @@ -15,50 +15,31 @@ package org.eclipse.jetty.websocket.api.annotations; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.eclipse.jetty.websocket.api.BatchMode; -import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.Session; /** - * Tags a POJO as being a WebSocket class. + *

Annotation for classes to be WebSocket endpoints.

*/ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target(value = - {ElementType.TYPE}) +@Inherited +@Target(value = ElementType.TYPE) public @interface WebSocket { /** - * The size of the buffer (in bytes) used to read from the network layer. + *

Returns whether demand for WebSocket frames is automatically performed + * upon successful return from methods annotated with {@link OnWebSocketOpen}, + * {@link OnWebSocketFrame} and {@link OnWebSocketMessage}.

+ *

If the demand is not automatic, then {@link Session#demand()} must be + * explicitly invoked to receive more WebSocket frames (both control and + * data frames, including CLOSE frames).

+ * + * @return whether demand for WebSocket frames is automatic */ - int inputBufferSize() default -1; - - /** - * The maximum size of a binary message (in bytes) during parsing/generating. - *

- * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - */ - int maxBinaryMessageSize() default -1; - - /** - * The time in ms (milliseconds) that a websocket may be idle before closing. - */ - int idleTimeout() default -1; - - /** - * The maximum size of a text message during parsing/generating. - *

- * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} - */ - int maxTextMessageSize() default -1; - - /** - * The output frame buffering mode. - *

- * Default: {@link BatchMode#AUTO} - */ - BatchMode batchMode() default BatchMode.AUTO; + boolean autoDemand() default true; } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java deleted file mode 100644 index bfd7fb65cde..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -/** - * Jetty WebSocket API : WebSocket POJO Annotations - */ -package org.eclipse.jetty.websocket.api.annotations; - diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/InvalidWebSocketException.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/InvalidWebSocketException.java index 247ed00f692..3d9680c4adb 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/InvalidWebSocketException.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/InvalidWebSocketException.java @@ -13,8 +13,7 @@ package org.eclipse.jetty.websocket.api.exceptions; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; -import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** @@ -22,12 +21,10 @@ import org.eclipse.jetty.websocket.api.annotations.WebSocket; *

* A valid WebSocket should do one of the following: *

    - *
  • Implement {@link WebSocketListener}
  • - *
  • Extend {@link WebSocketAdapter}
  • + *
  • Implement {@link Session.Listener}
  • *
  • Declare the {@link WebSocket @WebSocket} annotation on the type
  • *
*/ -@SuppressWarnings("serial") public class InvalidWebSocketException extends WebSocketException { public InvalidWebSocketException(String message) diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/package-info.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/package-info.java deleted file mode 100644 index 94b0379a261..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -/** - * Jetty WebSocket API : Exception Types - */ -package org.eclipse.jetty.websocket.api.exceptions; - diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java deleted file mode 100644 index 4579e7bf04f..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -/** - * Jetty WebSocket API - */ -package org.eclipse.jetty.websocket.api; - diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index 0b3071684c1..2d2105e94ac 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -35,10 +35,9 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ShutdownThread; +import org.eclipse.jetty.websocket.api.Configurable; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; import org.eclipse.jetty.websocket.client.internal.JettyClientUpgradeRequest; import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler; @@ -52,7 +51,7 @@ import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class WebSocketClient extends ContainerLifeCycle implements WebSocketPolicy, WebSocketContainer +public class WebSocketClient extends ContainerLifeCycle implements Configurable, WebSocketContainer { private static final Logger LOG = LoggerFactory.getLogger(WebSocketClient.class); private final WebSocketCoreClient coreClient; @@ -177,12 +176,6 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli dumpObjects(out, indent, getOpenSessions()); } - @Override - public WebSocketBehavior getBehavior() - { - return WebSocketBehavior.CLIENT; - } - @Override public void addSessionListener(WebSocketSessionListener listener) { @@ -217,42 +210,6 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli return configurationCustomizer.getIdleTimeout(); } - @Override - public int getInputBufferSize() - { - return configurationCustomizer.getInputBufferSize(); - } - - @Override - public int getOutputBufferSize() - { - return configurationCustomizer.getOutputBufferSize(); - } - - @Override - public long getMaxBinaryMessageSize() - { - return configurationCustomizer.getMaxBinaryMessageSize(); - } - - @Override - public long getMaxTextMessageSize() - { - return configurationCustomizer.getMaxTextMessageSize(); - } - - @Override - public long getMaxFrameSize() - { - return configurationCustomizer.getMaxFrameSize(); - } - - @Override - public boolean isAutoFragment() - { - return configurationCustomizer.isAutoFragment(); - } - @Override public void setIdleTimeout(Duration duration) { @@ -260,42 +217,90 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli getHttpClient().setIdleTimeout(duration.toMillis()); } + @Override + public int getInputBufferSize() + { + return configurationCustomizer.getInputBufferSize(); + } + @Override public void setInputBufferSize(int size) { configurationCustomizer.setInputBufferSize(size); } + @Override + public int getOutputBufferSize() + { + return configurationCustomizer.getOutputBufferSize(); + } + @Override public void setOutputBufferSize(int size) { configurationCustomizer.setOutputBufferSize(size); } + @Override + public long getMaxBinaryMessageSize() + { + return configurationCustomizer.getMaxBinaryMessageSize(); + } + @Override public void setMaxBinaryMessageSize(long size) { configurationCustomizer.setMaxBinaryMessageSize(size); } + @Override + public long getMaxTextMessageSize() + { + return configurationCustomizer.getMaxTextMessageSize(); + } + @Override public void setMaxTextMessageSize(long size) { configurationCustomizer.setMaxTextMessageSize(size); } + @Override + public long getMaxFrameSize() + { + return configurationCustomizer.getMaxFrameSize(); + } + @Override public void setMaxFrameSize(long maxFrameSize) { configurationCustomizer.setMaxFrameSize(maxFrameSize); } + @Override + public boolean isAutoFragment() + { + return configurationCustomizer.isAutoFragment(); + } + @Override public void setAutoFragment(boolean autoFragment) { configurationCustomizer.setAutoFragment(autoFragment); } + @Override + public int getMaxOutgoingFrames() + { + return configurationCustomizer.getMaxOutgoingFrames(); + } + + @Override + public void setMaxOutgoingFrames(int maxOutgoingFrames) + { + configurationCustomizer.setMaxOutgoingFrames(maxOutgoingFrames); + } + public SocketAddress getBindAddress() { return getHttpClient().getBindAddress(); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/ClientDemo.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/ClientDemo.java index 1f35d5174a6..6ff57fc3152 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/ClientDemo.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/ClientDemo.java @@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.core.OpCode; @@ -37,26 +36,13 @@ import org.eclipse.jetty.websocket.core.OpCode; */ public class ClientDemo { - public class TestSocket extends WebSocketAdapter + public class TestSocket extends Session.Listener.Abstract { @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) - { - } - - @Override - public void onWebSocketClose(int statusCode, String reason) - { - super.onWebSocketClose(statusCode, reason); - } - - @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { if (verbose) - { - System.err.printf("%s#onWebSocketConnect %s %s\n", this.getClass().getSimpleName(), session, session.getClass().getSimpleName()); - } + System.err.printf("%s#onWebSocketOpen %s %s\n", this.getClass().getSimpleName(), session, session.getClass().getSimpleName()); } public void send(byte op, byte[] data, int maxFragmentLength) diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/SimpleEchoSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/SimpleEchoSocket.java index 5a843c54dfe..9c8a40090cf 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/SimpleEchoSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/examples/SimpleEchoSocket.java @@ -16,22 +16,21 @@ package examples; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Basic Echo Client Socket */ -@WebSocket(maxTextMessageSize = 64 * 1024) +@WebSocket public class SimpleEchoSocket { private final CountDownLatch closeLatch; - @SuppressWarnings("unused") - private Session session; public SimpleEchoSocket() { @@ -47,20 +46,19 @@ public class SimpleEchoSocket public void onClose(int statusCode, String reason) { System.out.printf("Connection closed: %d - %s%n", statusCode, reason); - this.session = null; this.closeLatch.countDown(); // trigger latch } - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { - System.out.printf("Got connect: %s%n", session); - this.session = session; + System.out.printf("Open: %s%n", session); + session.setMaxTextMessageSize(64 * 1024); try { - session.getRemote().sendString("Hello"); - session.getRemote().sendString("Thanks for the conversation."); - session.close(StatusCode.NORMAL, "I'm done"); + session.sendText("Hello", Callback.NOOP); + session.sendText("Thanks for the conversation.", Callback.NOOP); + session.close(StatusCode.NORMAL, "I'm done", Callback.NOOP); } catch (Throwable t) { diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java index b6d982161a9..b15a09a59fe 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/test/java/org/eclipse/jetty/websocket/client/WebSocketClientBadUriTest.java @@ -19,7 +19,7 @@ import java.util.stream.Stream; import org.eclipse.jetty.toolchain.test.jupiter.TestTrackerExtension; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -40,7 +40,7 @@ public class WebSocketClientBadUriTest { public CountDownLatch openLatch = new CountDownLatch(1); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { openLatch.countDown(); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java index e777fd9778f..9d9665dd49a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java @@ -14,21 +14,20 @@ package org.eclipse.jetty.websocket.common; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; -import java.util.concurrent.Executor; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.thread.AutoLock; -import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.core.CloseStatus; -import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.FrameHandler; @@ -48,71 +47,45 @@ import org.slf4j.LoggerFactory; public class JettyWebSocketFrameHandler implements FrameHandler { - private enum SuspendState - { - DEMANDING, - SUSPENDING, - SUSPENDED, - CLOSED - } - - private final AutoLock lock = new AutoLock(); + private final AtomicBoolean closeNotified = new AtomicBoolean(); private final Logger log; private final WebSocketContainer container; private final Object endpointInstance; - private final BatchMode batchMode; - private final AtomicBoolean closeNotified = new AtomicBoolean(); + private final JettyWebSocketFrameHandlerMetadata metadata; private MethodHandle openHandle; private MethodHandle closeHandle; private MethodHandle errorHandle; private MethodHandle textHandle; - private final Class textSinkClass; private MethodHandle binaryHandle; + private final Class textSinkClass; private final Class binarySinkClass; private MethodHandle frameHandle; private MethodHandle pingHandle; private MethodHandle pongHandle; private UpgradeRequest upgradeRequest; private UpgradeResponse upgradeResponse; - - private final Configuration.Customizer customizer; private MessageSink textSink; private MessageSink binarySink; private MessageSink activeMessageSink; private WebSocketSession session; - private SuspendState state = SuspendState.DEMANDING; - private Runnable delayedOnFrame; - private CoreSession coreSession; - public JettyWebSocketFrameHandler(WebSocketContainer container, - Object endpointInstance, - MethodHandle openHandle, MethodHandle closeHandle, MethodHandle errorHandle, - MethodHandle textHandle, MethodHandle binaryHandle, - Class textSinkClass, - Class binarySinkClass, - MethodHandle frameHandle, - MethodHandle pingHandle, MethodHandle pongHandle, - BatchMode batchMode, - Configuration.Customizer customizer) + public JettyWebSocketFrameHandler(WebSocketContainer container, Object endpointInstance, JettyWebSocketFrameHandlerMetadata metadata) { this.log = LoggerFactory.getLogger(endpointInstance.getClass()); - this.container = container; this.endpointInstance = endpointInstance; + this.metadata = metadata; - this.openHandle = openHandle; - this.closeHandle = closeHandle; - this.errorHandle = errorHandle; - this.textHandle = textHandle; - this.binaryHandle = binaryHandle; - this.textSinkClass = textSinkClass; - this.binarySinkClass = binarySinkClass; - this.frameHandle = frameHandle; - this.pingHandle = pingHandle; - this.pongHandle = pongHandle; - - this.batchMode = batchMode; - this.customizer = customizer; + this.openHandle = InvokerUtils.bindTo(metadata.getOpenHandle(), endpointInstance); + this.closeHandle = InvokerUtils.bindTo(metadata.getCloseHandle(), endpointInstance); + this.errorHandle = InvokerUtils.bindTo(metadata.getErrorHandle(), endpointInstance); + this.textHandle = InvokerUtils.bindTo(metadata.getTextHandle(), endpointInstance); + this.binaryHandle = InvokerUtils.bindTo(metadata.getBinaryHandle(), endpointInstance); + this.textSinkClass = metadata.getTextSink(); + this.binarySinkClass = metadata.getBinarySink(); + this.frameHandle = InvokerUtils.bindTo(metadata.getFrameHandle(), endpointInstance); + this.pingHandle = InvokerUtils.bindTo(metadata.getPingHandle(), endpointInstance); + this.pongHandle = InvokerUtils.bindTo(metadata.getPongHandle(), endpointInstance); } public void setUpgradeRequest(UpgradeRequest upgradeRequest) @@ -135,11 +108,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler return upgradeResponse; } - public BatchMode getBatchMode() - { - return batchMode; - } - public WebSocketSession getSession() { return session; @@ -150,8 +118,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler { try { - customizer.customize(coreSession); - this.coreSession = coreSession; + metadata.customize(coreSession); session = new WebSocketSession(container, coreSession, this); if (!session.isOpen()) throw new IllegalStateException("Session is not open"); @@ -165,13 +132,11 @@ public class JettyWebSocketFrameHandler implements FrameHandler pingHandle = InvokerUtils.bindTo(pingHandle, session); pongHandle = InvokerUtils.bindTo(pongHandle, session); - Executor executor = container.getExecutor(); - if (textHandle != null) - textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, session); + textSink = createMessageSink(textSinkClass, session, textHandle, isAutoDemand()); if (binaryHandle != null) - binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, session); + binarySink = createMessageSink(binarySinkClass, session, binaryHandle, isAutoDemand()); if (openHandle != null) openHandle.invoke(); @@ -180,74 +145,90 @@ public class JettyWebSocketFrameHandler implements FrameHandler container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session)); callback.succeeded(); - demand(); } catch (Throwable cause) { callback.failed(new WebSocketException(endpointInstance.getClass().getSimpleName() + " OPEN method error: " + cause.getMessage(), cause)); } + finally + { + autoDemand(); + } + } + + private static MessageSink createMessageSink(Class sinkClass, WebSocketSession session, MethodHandle msgHandle, boolean autoDemanding) + { + if (msgHandle == null) + return null; + if (sinkClass == null) + return null; + + try + { + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); + MethodHandle ctorHandle = lookup.findConstructor(sinkClass, + MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, boolean.class)); + return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgHandle, autoDemanding); + } + catch (NoSuchMethodException e) + { + throw new RuntimeException("Missing expected MessageSink constructor found at: " + sinkClass.getName(), e); + } + catch (IllegalAccessException | InstantiationException | InvocationTargetException e) + { + throw new RuntimeException("Unable to create MessageSink: " + sinkClass.getName(), e); + } + catch (RuntimeException e) + { + throw e; + } + catch (Throwable t) + { + throw new RuntimeException(t); + } } @Override - public void onFrame(Frame frame, Callback callback) + public void onFrame(Frame frame, Callback coreCallback) { - try (AutoLock l = lock.lock()) - { - switch (state) - { - case DEMANDING: - break; - - case SUSPENDING: - delayedOnFrame = () -> onFrame(frame, callback); - state = SuspendState.SUSPENDED; - return; - - default: - throw new IllegalStateException(); - } - - // If we have received a close frame, set state to closed to disallow further suspends and resumes. - if (frame.getOpCode() == OpCode.CLOSE) - state = SuspendState.CLOSED; - } - - // Send to raw frame handling on user side (eg: WebSocketFrameListener) + CompletableFuture frameCallback = null; if (frameHandle != null) { try { - frameHandle.invoke(new JettyWebSocketFrame(frame)); + frameCallback = new org.eclipse.jetty.websocket.api.Callback.Completable(); + frameHandle.invoke(new JettyWebSocketFrame(frame), frameCallback); } catch (Throwable cause) { - throw new WebSocketException(endpointInstance.getClass().getSimpleName() + " FRAME method error: " + cause.getMessage(), cause); + coreCallback.failed(new WebSocketException(endpointInstance.getClass().getSimpleName() + " FRAME method error: " + cause.getMessage(), cause)); + return; } } + Callback.Completable eventCallback = new Callback.Completable(); switch (frame.getOpCode()) { - case OpCode.CLOSE: - onCloseFrame(frame, callback); - break; - case OpCode.PING: - onPingFrame(frame, callback); - break; - case OpCode.PONG: - onPongFrame(frame, callback); - break; - case OpCode.TEXT: - onTextFrame(frame, callback); - break; - case OpCode.BINARY: - onBinaryFrame(frame, callback); - break; - case OpCode.CONTINUATION: - onContinuationFrame(frame, callback); - break; - default: - callback.failed(new IllegalStateException()); - } + case OpCode.CLOSE -> onCloseFrame(frame, eventCallback); + case OpCode.PING -> onPingFrame(frame, eventCallback); + case OpCode.PONG -> onPongFrame(frame, eventCallback); + case OpCode.TEXT -> onTextFrame(frame, eventCallback); + case OpCode.BINARY -> onBinaryFrame(frame, eventCallback); + case OpCode.CONTINUATION -> onContinuationFrame(frame, eventCallback); + default -> coreCallback.failed(new IllegalStateException()); + }; + + // Combine the callback from the frame handler and the event handler. + CompletableFuture callback = eventCallback; + if (frameCallback != null) + callback = frameCallback.thenCompose(ignored -> eventCallback); + callback.whenComplete((r, x) -> + { + if (x == null) + coreCallback.succeeded(); + else + coreCallback.failed(x); + }); } @Override @@ -257,11 +238,13 @@ public class JettyWebSocketFrameHandler implements FrameHandler { cause = convertCause(cause); if (errorHandle != null) + { errorHandle.invoke(cause); + } else { if (log.isDebugEnabled()) - log.warn("Unhandled Error: Endpoint {}", endpointInstance.getClass().getName(), cause); + log.debug("Unhandled Error: Endpoint {}", endpointInstance.getClass().getName(), cause); else log.warn("Unhandled Error: Endpoint {} : {}", endpointInstance.getClass().getName(), cause.toString()); } @@ -275,24 +258,18 @@ public class JettyWebSocketFrameHandler implements FrameHandler } } - private void onCloseFrame(Frame frame, Callback callback) - { - notifyOnClose(CloseStatus.getCloseStatus(frame), callback); - } - @Override public void onClosed(CloseStatus closeStatus, Callback callback) { - try (AutoLock l = lock.lock()) - { - // We are now closed and cannot suspend or resume. - state = SuspendState.CLOSED; - } - notifyOnClose(closeStatus, callback); container.notifySessionListeners((listener) -> listener.onWebSocketSessionClosed(session)); } + private void onCloseFrame(Frame frame, Callback callback) + { + notifyOnClose(CloseStatus.getCloseStatus(frame), callback); + } + private void notifyOnClose(CloseStatus closeStatus, Callback callback) { // Make sure onClose is only notified once. @@ -306,7 +283,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler { if (closeHandle != null) closeHandle.invoke(closeStatus.getCode(), closeStatus.getReason()); - callback.succeeded(); } catch (Throwable cause) @@ -315,40 +291,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler } } - public String toString() - { - return String.format("%s@%x[%s]", this.getClass().getSimpleName(), this.hashCode(), endpointInstance.getClass().getName()); - } - - private void acceptMessage(Frame frame, Callback callback) - { - // No message sink is active - if (activeMessageSink == null) - { - callback.succeeded(); - demand(); - return; - } - - // Accept the payload into the message sink - activeMessageSink.accept(frame, callback); - if (frame.isFin()) - activeMessageSink = null; - } - - private void onBinaryFrame(Frame frame, Callback callback) - { - if (activeMessageSink == null) - activeMessageSink = binarySink; - - acceptMessage(frame, callback); - } - - private void onContinuationFrame(Frame frame, Callback callback) - { - acceptMessage(frame, callback); - } - private void onPingFrame(Frame frame, Callback callback) { if (pingHandle != null) @@ -358,35 +300,34 @@ public class JettyWebSocketFrameHandler implements FrameHandler ByteBuffer payload = frame.getPayload(); if (payload == null) payload = BufferUtil.EMPTY_BUFFER; - + else + payload = BufferUtil.copy(payload); pingHandle.invoke(payload); + callback.succeeded(); + autoDemand(); } catch (Throwable cause) { - throw new WebSocketException(endpointInstance.getClass().getSimpleName() + " PING method error: " + cause.getMessage(), cause); + callback.failed(new WebSocketException(endpointInstance.getClass().getSimpleName() + " PING method error: " + cause.getMessage(), cause)); } - - callback.succeeded(); - demand(); } else { // Automatically respond. - getSession().getRemote().sendPong(frame.getPayload(), new WriteCallback() + getSession().sendPong(frame.getPayload(), new org.eclipse.jetty.websocket.api.Callback() { @Override - public void writeSuccess() + public void succeed() { callback.succeeded(); - demand(); + autoDemand(); } @Override - public void writeFailed(Throwable x) + public void fail(Throwable x) { - // Ignore failures, we might be output closed and receive ping. + // Ignore failures, we might be output closed and receive a PING. callback.succeeded(); - demand(); } }); } @@ -401,111 +342,74 @@ public class JettyWebSocketFrameHandler implements FrameHandler ByteBuffer payload = frame.getPayload(); if (payload == null) payload = BufferUtil.EMPTY_BUFFER; - + else + payload = BufferUtil.copy(payload); pongHandle.invoke(payload); + callback.succeeded(); + autoDemand(); } catch (Throwable cause) { - throw new WebSocketException(endpointInstance.getClass().getSimpleName() + " PONG method error: " + cause.getMessage(), cause); + callback.failed(new WebSocketException(endpointInstance.getClass().getSimpleName() + " PONG method error: " + cause.getMessage(), cause)); } } - - callback.succeeded(); - demand(); + else + { + autoDemand(); + } } private void onTextFrame(Frame frame, Callback callback) { if (activeMessageSink == null) activeMessageSink = textSink; - - acceptMessage(frame, callback); + acceptFrame(frame, callback); } - @Override - public boolean isAutoDemanding() + private void onBinaryFrame(Frame frame, Callback callback) { - return false; + if (activeMessageSink == null) + activeMessageSink = binarySink; + acceptFrame(frame, callback); } - public void suspend() + private void onContinuationFrame(Frame frame, Callback callback) { - try (AutoLock l = lock.lock()) - { - switch (state) - { - case DEMANDING: - state = SuspendState.SUSPENDING; - break; - - default: - throw new IllegalStateException(state.name()); - } - } + acceptFrame(frame, callback); } - public void resume() + private void acceptFrame(Frame frame, Callback callback) { - boolean needDemand = false; - Runnable delayedFrame = null; - try (AutoLock l = lock.lock()) + // No message sink is active. + if (activeMessageSink == null) { - switch (state) - { - case DEMANDING: - throw new IllegalStateException("Already Resumed"); - - case SUSPENDED: - needDemand = true; - delayedFrame = delayedOnFrame; - delayedOnFrame = null; - state = SuspendState.DEMANDING; - break; - - case SUSPENDING: - if (delayedOnFrame != null) - throw new IllegalStateException(); - state = SuspendState.DEMANDING; - break; - - default: - throw new IllegalStateException(state.name()); - } + callback.succeeded(); + autoDemand(); + return; } - if (needDemand) - { - if (delayedFrame != null) - delayedFrame.run(); - else - session.getCoreSession().demand(1); - } + // Accept the payload into the message sink. + activeMessageSink.accept(frame, callback); + if (frame.isFin()) + activeMessageSink = null; } - private void demand() + boolean isAutoDemand() { - boolean demand = false; - try (AutoLock l = lock.lock()) - { - switch (state) - { - case DEMANDING: - demand = true; - break; + return metadata.isAutoDemand(); + } - case SUSPENDING: - state = SuspendState.SUSPENDED; - break; - - default: - throw new IllegalStateException(state.name()); - } - } - - if (demand) + private void autoDemand() + { + if (isAutoDemand()) session.getCoreSession().demand(1); } + public String toString() + { + return String.format("%s@%x[%s]", this.getClass().getSimpleName(), this.hashCode(), endpointInstance.getClass().getName()); + } + public static Throwable convertCause(Throwable cause) { if (cause instanceof MessageTooLargeException) diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java index db85ec467a1..aa010794a53 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -19,41 +19,29 @@ import java.io.Reader; import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; -import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WebSocketFrameListener; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.core.CoreSession; +import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.internal.ByteBufferMessageSink; +import org.eclipse.jetty.websocket.common.internal.PartialByteBufferMessageSink; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; -import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; -import org.eclipse.jetty.websocket.core.messages.ByteArrayMessageSink; -import org.eclipse.jetty.websocket.core.messages.ByteBufferMessageSink; import org.eclipse.jetty.websocket.core.messages.InputStreamMessageSink; -import org.eclipse.jetty.websocket.core.messages.MessageSink; -import org.eclipse.jetty.websocket.core.messages.PartialByteBufferMessageSink; import org.eclipse.jetty.websocket.core.messages.PartialStringMessageSink; import org.eclipse.jetty.websocket.core.messages.ReaderMessageSink; import org.eclipse.jetty.websocket.core.messages.StringMessageSink; @@ -68,12 +56,7 @@ import org.eclipse.jetty.websocket.core.util.ReflectUtils; *

*
    *
  • Is @{@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated
  • - *
  • Extends {@link org.eclipse.jetty.websocket.api.WebSocketAdapter}
  • - *
  • Implements {@link org.eclipse.jetty.websocket.api.WebSocketListener}
  • - *
  • Implements {@link org.eclipse.jetty.websocket.api.WebSocketConnectionListener}
  • - *
  • Implements {@link org.eclipse.jetty.websocket.api.WebSocketPartialListener}
  • - *
  • Implements {@link org.eclipse.jetty.websocket.api.WebSocketPingPongListener}
  • - *
  • Implements {@link org.eclipse.jetty.websocket.api.WebSocketFrameListener}
  • + *
  • Implements {@link org.eclipse.jetty.websocket.api.Session.Listener}
  • *
*/ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle @@ -85,14 +68,8 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle private static final InvokerUtils.Arg[] binaryBufferCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), - new InvokerUtils.Arg(ByteBuffer.class).required() - }; - - private static final InvokerUtils.Arg[] binaryArrayCallingArgs = new InvokerUtils.Arg[]{ - new InvokerUtils.Arg(Session.class), - new InvokerUtils.Arg(byte[].class).required(), - new InvokerUtils.Arg(int.class), // offset - new InvokerUtils.Arg(int.class) // length + new InvokerUtils.Arg(ByteBuffer.class).required(), + new InvokerUtils.Arg(Callback.class).required() }; private static final InvokerUtils.Arg[] inputStreamCallingArgs = new InvokerUtils.Arg[]{ @@ -114,13 +91,8 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle private static final InvokerUtils.Arg[] binaryPartialBufferCallingArgs = new InvokerUtils.Arg[]{ new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(ByteBuffer.class).required(), - new InvokerUtils.Arg(boolean.class).required() - }; - - private static final InvokerUtils.Arg[] binaryPartialArrayCallingArgs = new InvokerUtils.Arg[]{ - new InvokerUtils.Arg(Session.class), - new InvokerUtils.Arg(byte[].class).required(), - new InvokerUtils.Arg(boolean.class).required() + new InvokerUtils.Arg(boolean.class).required(), + new InvokerUtils.Arg(Callback.class).required() }; private final WebSocketContainer container; @@ -153,16 +125,12 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle public JettyWebSocketFrameHandlerMetadata createMetadata(Class endpointClass) { - if (WebSocketConnectionListener.class.isAssignableFrom(endpointClass)) - { + if (Session.Listener.class.isAssignableFrom(endpointClass)) return createListenerMetadata(endpointClass); - } WebSocket websocket = endpointClass.getAnnotation(WebSocket.class); if (websocket != null) - { return createAnnotatedMetadata(websocket, endpointClass); - } throw new InvalidWebSocketException("Unrecognized WebSocket endpoint: " + endpointClass.getName()); } @@ -171,62 +139,10 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { JettyWebSocketFrameHandlerMetadata metadata = getMetadata(endpointInstance.getClass()); - final MethodHandle openHandle = InvokerUtils.bindTo(metadata.getOpenHandle(), endpointInstance); - final MethodHandle closeHandle = InvokerUtils.bindTo(metadata.getCloseHandle(), endpointInstance); - final MethodHandle errorHandle = InvokerUtils.bindTo(metadata.getErrorHandle(), endpointInstance); - final MethodHandle textHandle = InvokerUtils.bindTo(metadata.getTextHandle(), endpointInstance); - final MethodHandle binaryHandle = InvokerUtils.bindTo(metadata.getBinaryHandle(), endpointInstance); - final Class textSinkClass = metadata.getTextSink(); - final Class binarySinkClass = metadata.getBinarySink(); - final MethodHandle frameHandle = InvokerUtils.bindTo(metadata.getFrameHandle(), endpointInstance); - final MethodHandle pingHandle = InvokerUtils.bindTo(metadata.getPingHandle(), endpointInstance); - final MethodHandle pongHandle = InvokerUtils.bindTo(metadata.getPongHandle(), endpointInstance); - BatchMode batchMode = metadata.getBatchMode(); - // Decorate the endpointInstance while we are still upgrading for access to things like HttpSession. components.getObjectFactory().decorate(endpointInstance); - return new JettyWebSocketFrameHandler( - container, - endpointInstance, - openHandle, closeHandle, errorHandle, - textHandle, binaryHandle, - textSinkClass, binarySinkClass, - frameHandle, pingHandle, pongHandle, - batchMode, - metadata); - } - - public static MessageSink createMessageSink(MethodHandle msgHandle, Class sinkClass, Executor executor, WebSocketSession session) - { - if (msgHandle == null) - return null; - if (sinkClass == null) - return null; - - try - { - MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); - MethodHandle ctorHandle = lookup.findConstructor(sinkClass, - MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); - return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgHandle); - } - catch (NoSuchMethodException e) - { - throw new RuntimeException("Missing expected MessageSink constructor found at: " + sinkClass.getName(), e); - } - catch (IllegalAccessException | InstantiationException | InvocationTargetException e) - { - throw new RuntimeException("Unable to create MessageSink: " + sinkClass.getName(), e); - } - catch (RuntimeException e) - { - throw e; - } - catch (Throwable t) - { - throw new RuntimeException(t); - } + return new JettyWebSocketFrameHandler(container, endpointInstance, metadata); } private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method) @@ -244,99 +160,121 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle private JettyWebSocketFrameHandlerMetadata createListenerMetadata(Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); + metadata.setAutoDemand(Session.Listener.AutoDemanding.class.isAssignableFrom(endpointClass)); + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); - if (!WebSocketConnectionListener.class.isAssignableFrom(endpointClass)) - throw new IllegalArgumentException("Class " + endpointClass + " does not implement " + WebSocketConnectionListener.class); - - Method openMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketConnect", Session.class); - MethodHandle open = toMethodHandle(lookup, openMethod); - metadata.setOpenHandler(open, openMethod); - - Method closeMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketClose", int.class, String.class); - MethodHandle close = toMethodHandle(lookup, closeMethod); - metadata.setCloseHandler(close, closeMethod); - - Method errorMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketError", Throwable.class); - MethodHandle error = toMethodHandle(lookup, errorMethod); - metadata.setErrorHandler(error, errorMethod); - - // Simple Data Listener - if (WebSocketListener.class.isAssignableFrom(endpointClass)) + Method openMethod = findMethod(endpointClass, "onWebSocketOpen", Session.class); + if (openMethod != null) { - Method textMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketText", String.class); - MethodHandle text = toMethodHandle(lookup, textMethod); - metadata.setTextHandler(StringMessageSink.class, text, textMethod); - - Method binaryMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketBinary", byte[].class, int.class, int.class); - MethodHandle binary = toMethodHandle(lookup, binaryMethod); - metadata.setBinaryHandle(ByteArrayMessageSink.class, binary, binaryMethod); + MethodHandle connectHandle = toMethodHandle(lookup, openMethod); + metadata.setOpenHandle(connectHandle, openMethod); } - // Ping/Pong Listener - if (WebSocketPingPongListener.class.isAssignableFrom(endpointClass)) + Method frameMethod = findMethod(endpointClass, "onWebSocketFrame", Frame.class, Callback.class); + if (frameMethod != null) { - Method pongMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPong", ByteBuffer.class); - MethodHandle pong = toMethodHandle(lookup, pongMethod); - metadata.setPongHandle(pong, pongMethod); - - Method pingMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPing", ByteBuffer.class); - MethodHandle ping = toMethodHandle(lookup, pingMethod); - metadata.setPingHandle(ping, pingMethod); + MethodHandle frameHandle = toMethodHandle(lookup, frameMethod); + metadata.setFrameHandle(frameHandle, frameMethod); } - // Partial Data / Message Listener - if (WebSocketPartialListener.class.isAssignableFrom(endpointClass)) + Method pingMethod = findMethod(endpointClass, "onWebSocketPing", ByteBuffer.class); + if (pingMethod != null) { - Method textMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialText", String.class, boolean.class); - MethodHandle text = toMethodHandle(lookup, textMethod); - metadata.setTextHandler(PartialStringMessageSink.class, text, textMethod); - - Method binaryMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class); - MethodHandle binary = toMethodHandle(lookup, binaryMethod); - metadata.setBinaryHandle(PartialByteBufferMessageSink.class, binary, binaryMethod); + MethodHandle pingHandle = toMethodHandle(lookup, pingMethod); + metadata.setPingHandle(pingHandle, pingMethod); } - // Frame Listener - if (WebSocketFrameListener.class.isAssignableFrom(endpointClass)) + Method pongMethod = findMethod(endpointClass, "onWebSocketPong", ByteBuffer.class); + if (pongMethod != null) { - Method frameMethod = ReflectUtils.findMethod(WebSocketFrameListener.class, "onWebSocketFrame", Frame.class); - MethodHandle frame = toMethodHandle(lookup, frameMethod); - metadata.setFrameHandler(frame, frameMethod); + MethodHandle pongHandle = toMethodHandle(lookup, pongMethod); + metadata.setPongHandle(pongHandle, pongMethod); + } + + Method partialTextMethod = findMethod(endpointClass, "onWebSocketPartialText", String.class, boolean.class); + if (partialTextMethod != null) + { + MethodHandle partialTextHandle = toMethodHandle(lookup, partialTextMethod); + metadata.setTextHandle(PartialStringMessageSink.class, partialTextHandle, partialTextMethod); + } + + Method partialBinaryMethod = findMethod(endpointClass, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class, Callback.class); + if (partialBinaryMethod != null) + { + MethodHandle partialBinaryHandle = toMethodHandle(lookup, partialBinaryMethod); + metadata.setBinaryHandle(PartialByteBufferMessageSink.class, partialBinaryHandle, partialBinaryMethod); + } + + Method textMethod = findMethod(endpointClass, "onWebSocketText", String.class); + if (textMethod != null) + { + MethodHandle textHandle = toMethodHandle(lookup, textMethod); + metadata.setTextHandle(StringMessageSink.class, textHandle, textMethod); + } + + Method binaryMethod = findMethod(endpointClass, "onWebSocketBinary", ByteBuffer.class, Callback.class); + if (binaryMethod != null) + { + MethodHandle binaryHandle = toMethodHandle(lookup, binaryMethod); + metadata.setBinaryHandle(ByteBufferMessageSink.class, binaryHandle, binaryMethod); + } + + Method errorMethod = findMethod(endpointClass, "onWebSocketError", Throwable.class); + if (errorMethod != null) + { + MethodHandle errorHandle = toMethodHandle(lookup, errorMethod); + metadata.setErrorHandle(errorHandle, errorMethod); + } + + Method closeMethod = findMethod(endpointClass, "onWebSocketClose", int.class, String.class); + if (closeMethod != null) + { + MethodHandle closeHandle = toMethodHandle(lookup, closeMethod); + metadata.setCloseHandle(closeHandle, closeMethod); } return metadata; } + private Method findMethod(Class klass, String name, Class... parameters) + { + // Verify if the method is overridden in the endpoint class, to avoid + // calling all methods of Session.Listener even if they are not overridden. + Method method = ReflectUtils.findMethod(klass, name, parameters); + if (method == null) + return null; + if (!isOverridden(method)) + return null; + // The method is overridden, but it may be declared in a non-public + // class, for example an anonymous class, where it won't be accessible, + // therefore replace it with the accessible version from Session.Listener. + if (!Modifier.isPublic(klass.getModifiers())) + method = ReflectUtils.findMethod(Session.Listener.class, name, parameters); + return method; + } + + private boolean isOverridden(Method method) + { + return method != null && method.getDeclaringClass() != Session.Listener.class; + } + private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket anno, Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); - - int max = anno.inputBufferSize(); - if (max >= 0) - metadata.setInputBufferSize(max); - max = anno.maxBinaryMessageSize(); - if (max >= 0) - metadata.setMaxBinaryMessageSize(max); - max = anno.maxTextMessageSize(); - if (max >= 0) - metadata.setMaxTextMessageSize(max); - max = anno.idleTimeout(); - if (max >= 0) - metadata.setIdleTimeout(Duration.ofMillis(max)); - metadata.setBatchMode(anno.batchMode()); + metadata.setAutoDemand(anno.autoDemand()); MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass); Method onmethod; - // OnWebSocketConnect [0..1] - onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketConnect.class); + // OnWebSocketOpen [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketOpen.class); if (onmethod != null) { - assertSignatureValid(endpointClass, onmethod, OnWebSocketConnect.class); + assertSignatureValid(endpointClass, onmethod, OnWebSocketOpen.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class).required(); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION); - metadata.setOpenHandler(methodHandle, onmethod); + metadata.setOpenHandle(methodHandle, onmethod); } // OnWebSocketClose [0..1] @@ -348,7 +286,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle final InvokerUtils.Arg STATUS_CODE = new InvokerUtils.Arg(int.class); final InvokerUtils.Arg REASON = new InvokerUtils.Arg(String.class); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, STATUS_CODE, REASON); - metadata.setCloseHandler(methodHandle, onmethod); + metadata.setCloseHandle(methodHandle, onmethod); } // OnWebSocketError [0..1] @@ -359,7 +297,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class); final InvokerUtils.Arg CAUSE = new InvokerUtils.Arg(Throwable.class).required(); MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, CAUSE); - metadata.setErrorHandler(methodHandle, onmethod); + metadata.setErrorHandle(methodHandle, onmethod); } // OnWebSocketFrame [0..1] @@ -369,13 +307,14 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle assertSignatureValid(endpointClass, onmethod, OnWebSocketFrame.class); final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class); final InvokerUtils.Arg FRAME = new InvokerUtils.Arg(Frame.class).required(); - MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, FRAME); - metadata.setFrameHandler(methodHandle, onmethod); + final InvokerUtils.Arg CALLBACK = new InvokerUtils.Arg(Callback.class).required(); + MethodHandle methodHandle = InvokerUtils.mutatedInvoker(lookup, endpointClass, onmethod, SESSION, FRAME, CALLBACK); + metadata.setFrameHandle(methodHandle, onmethod); } // OnWebSocketMessage [0..2] Method[] onMessages = ReflectUtils.findAnnotatedMethods(endpointClass, OnWebSocketMessage.class); - if (onMessages != null && onMessages.length > 0) + if (onMessages != null) { // The different kind of @OnWebSocketMessage method parameter signatures expected for (Method onMsg : onMessages) @@ -387,7 +326,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { // Normal Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); - metadata.setTextHandler(StringMessageSink.class, methodHandle, onMsg); + metadata.setTextHandle(StringMessageSink.class, methodHandle, onMsg); continue; } @@ -400,18 +339,12 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle continue; } - methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, binaryArrayCallingArgs); - if (methodHandle != null) - { - // byte[] Binary Message - assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); - metadata.setBinaryHandle(ByteArrayMessageSink.class, methodHandle, onMsg); - continue; - } - methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, inputStreamCallingArgs); if (methodHandle != null) { + if (!metadata.isAutoDemand()) + throw new InvalidWebSocketException("InputStream methods require auto-demanding WebSocket endpoints"); + // InputStream Binary Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); metadata.setBinaryHandle(InputStreamMessageSink.class, methodHandle, onMsg); @@ -421,9 +354,12 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle methodHandle = InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, readerCallingArgs); if (methodHandle != null) { + if (!metadata.isAutoDemand()) + throw new InvalidWebSocketException("Reader methods require auto-demanding WebSocket endpoints"); + // Reader Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); - metadata.setTextHandler(ReaderMessageSink.class, methodHandle, onMsg); + metadata.setTextHandle(ReaderMessageSink.class, methodHandle, onMsg); continue; } @@ -432,7 +368,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { // Partial Text Message assertSignatureValid(endpointClass, onMsg, OnWebSocketMessage.class); - metadata.setTextHandler(PartialStringMessageSink.class, methodHandle, onMsg); + metadata.setTextHandle(PartialStringMessageSink.class, methodHandle, onMsg); continue; } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerMetadata.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerMetadata.java index db2f617971e..0a5009558f8 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerMetadata.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerMetadata.java @@ -15,38 +15,32 @@ package org.eclipse.jetty.websocket.common; import java.lang.invoke.MethodHandle; -import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException; import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.messages.MessageSink; public class JettyWebSocketFrameHandlerMetadata extends Configuration.ConfigurationCustomizer { + private boolean autoDemand; private MethodHandle openHandle; private MethodHandle closeHandle; private MethodHandle errorHandle; - private MethodHandle frameHandle; - private MethodHandle textHandle; private Class textSink; private MethodHandle binaryHandle; private Class binarySink; - private MethodHandle pingHandle; private MethodHandle pongHandle; - // Batch Configuration - private BatchMode batchMode = BatchMode.OFF; - - public void setBatchMode(BatchMode batchMode) + public boolean isAutoDemand() { - this.batchMode = batchMode; + return autoDemand; } - public BatchMode getBatchMode() + public void setAutoDemand(boolean autoDemand) { - return batchMode; + this.autoDemand = autoDemand; } public void setBinaryHandle(Class sinkClass, MethodHandle binary, Object origin) @@ -66,7 +60,7 @@ public class JettyWebSocketFrameHandlerMetadata extends Configuration.Configurat return binarySink; } - public void setCloseHandler(MethodHandle close, Object origin) + public void setCloseHandle(MethodHandle close, Object origin) { assertNotSet(this.closeHandle, "CLOSE Handler", origin); this.closeHandle = close; @@ -77,7 +71,7 @@ public class JettyWebSocketFrameHandlerMetadata extends Configuration.Configurat return closeHandle; } - public void setErrorHandler(MethodHandle error, Object origin) + public void setErrorHandle(MethodHandle error, Object origin) { assertNotSet(this.errorHandle, "ERROR Handler", origin); this.errorHandle = error; @@ -88,7 +82,7 @@ public class JettyWebSocketFrameHandlerMetadata extends Configuration.Configurat return errorHandle; } - public void setFrameHandler(MethodHandle frame, Object origin) + public void setFrameHandle(MethodHandle frame, Object origin) { assertNotSet(this.frameHandle, "FRAME Handler", origin); this.frameHandle = frame; @@ -99,10 +93,10 @@ public class JettyWebSocketFrameHandlerMetadata extends Configuration.Configurat return frameHandle; } - public void setOpenHandler(MethodHandle open, Object origin) + public void setOpenHandle(MethodHandle openHandle, Object origin) { assertNotSet(this.openHandle, "OPEN Handler", origin); - this.openHandle = open; + this.openHandle = openHandle; } public MethodHandle getOpenHandle() @@ -132,7 +126,7 @@ public class JettyWebSocketFrameHandlerMetadata extends Configuration.Configurat return pongHandle; } - public void setTextHandler(Class sinkClass, MethodHandle text, Object origin) + public void setTextHandle(Class sinkClass, MethodHandle text, Object origin) { assertNotSet(this.textHandle, "TEXT Handler", origin); this.textHandle = text; diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java deleted file mode 100644 index 3db93a71fcb..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java +++ /dev/null @@ -1,235 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.common; - -import java.io.IOException; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.util.Objects; - -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.FutureCallback; -import org.eclipse.jetty.websocket.api.BatchMode; -import org.eclipse.jetty.websocket.api.WriteCallback; -import org.eclipse.jetty.websocket.core.CoreSession; -import org.eclipse.jetty.websocket.core.Frame; -import org.eclipse.jetty.websocket.core.OpCode; -import org.eclipse.jetty.websocket.core.exception.ProtocolException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static java.nio.charset.StandardCharsets.UTF_8; - -public class JettyWebSocketRemoteEndpoint implements org.eclipse.jetty.websocket.api.RemoteEndpoint -{ - private static final Logger LOG = LoggerFactory.getLogger(JettyWebSocketRemoteEndpoint.class); - - private final CoreSession coreSession; - private byte messageType = -1; - private BatchMode batchMode; - - public JettyWebSocketRemoteEndpoint(CoreSession coreSession, BatchMode batchMode) - { - this.coreSession = Objects.requireNonNull(coreSession); - this.batchMode = batchMode; - } - - @Override - public void sendString(String text) throws IOException - { - sendBlocking(new Frame(OpCode.TEXT).setPayload(text)); - } - - @Override - public void sendString(String text, WriteCallback callback) - { - Callback cb = callback == null ? Callback.NOOP : Callback.from(callback::writeSuccess, callback::writeFailed); - coreSession.sendFrame(new Frame(OpCode.TEXT).setPayload(text), cb, isBatch()); - } - - @Override - public void sendBytes(ByteBuffer data) throws IOException - { - sendBlocking(new Frame(OpCode.BINARY).setPayload(data)); - } - - @Override - public void sendBytes(ByteBuffer data, WriteCallback callback) - { - coreSession.sendFrame(new Frame(OpCode.BINARY).setPayload(data), - Callback.from(callback::writeSuccess, callback::writeFailed), - isBatch()); - } - - @Override - public void sendPartialBytes(ByteBuffer fragment, boolean isLast) throws IOException - { - FutureCallback b = new FutureCallback(); - sendPartialBytes(fragment, isLast, b); - b.block(); - } - - @Override - public void sendPartialBytes(ByteBuffer fragment, boolean isLast, WriteCallback callback) - { - sendPartialBytes(fragment, isLast, Callback.from(callback::writeSuccess, callback::writeFailed)); - } - - private void sendPartialBytes(ByteBuffer fragment, boolean isLast, Callback callback) - { - Frame frame; - switch (messageType) - { - case -1: // new message - frame = new Frame(OpCode.BINARY); - messageType = OpCode.BINARY; - break; - case OpCode.BINARY: - frame = new Frame(OpCode.CONTINUATION); - break; - default: - callback.failed(new ProtocolException("Attempt to send Partial Binary during active opcode " + messageType)); - return; - } - - frame.setPayload(fragment); - frame.setFin(isLast); - - coreSession.sendFrame(frame, callback, isBatch()); - - if (isLast) - { - messageType = -1; - } - } - - @Override - public void sendPartialString(String fragment, boolean isLast) throws IOException - { - FutureCallback b = new FutureCallback(); - sendPartialText(fragment, isLast, b); - b.block(); - } - - @Override - public void sendPartialString(String fragment, boolean isLast, WriteCallback callback) - { - sendPartialText(fragment, isLast, Callback.from(callback::writeSuccess, callback::writeFailed)); - } - - @Override - public void sendPing(ByteBuffer applicationData) throws IOException - { - sendBlocking(new Frame(OpCode.PING).setPayload(applicationData)); - } - - @Override - public void sendPing(ByteBuffer applicationData, WriteCallback callback) - { - coreSession.sendFrame(new Frame(OpCode.PING).setPayload(applicationData), - Callback.from(callback::writeSuccess, callback::writeFailed), false); - } - - @Override - public void sendPong(ByteBuffer applicationData) throws IOException - { - sendBlocking(new Frame(OpCode.PONG).setPayload(applicationData)); - } - - @Override - public void sendPong(ByteBuffer applicationData, WriteCallback callback) - { - coreSession.sendFrame(new Frame(OpCode.PONG).setPayload(applicationData), - Callback.from(callback::writeSuccess, callback::writeFailed), false); - } - - private void sendPartialText(String fragment, boolean isLast, Callback callback) - { - Frame frame; - switch (messageType) - { - case -1: // new message - frame = new Frame(OpCode.TEXT); - messageType = OpCode.TEXT; - break; - case OpCode.TEXT: - frame = new Frame(OpCode.CONTINUATION); - break; - default: - callback.failed(new ProtocolException("Attempt to send Partial Text during active opcode " + messageType)); - return; - } - - frame.setPayload(BufferUtil.toBuffer(fragment, UTF_8)); - frame.setFin(isLast); - - coreSession.sendFrame(frame, callback, isBatch()); - - if (isLast) - { - messageType = -1; - } - } - - private void sendBlocking(Frame frame) throws IOException - { - FutureCallback b = new FutureCallback(); - coreSession.sendFrame(frame, b, false); - b.block(); - } - - @Override - public BatchMode getBatchMode() - { - return batchMode; - } - - @Override - public void setBatchMode(BatchMode mode) - { - batchMode = mode; - } - - @Override - public int getMaxOutgoingFrames() - { - return coreSession.getMaxOutgoingFrames(); - } - - @Override - public void setMaxOutgoingFrames(int maxOutgoingFrames) - { - coreSession.setMaxOutgoingFrames(maxOutgoingFrames); - } - - private boolean isBatch() - { - return BatchMode.ON == batchMode; - } - - @Override - public SocketAddress getRemoteAddress() - { - return coreSession.getRemoteAddress(); - } - - @Override - public void flush() throws IOException - { - FutureCallback b = new FutureCallback(); - coreSession.flush(b); - b.block(); - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java index 2868c656c82..23447fc5c09 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Graceful; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; @@ -76,7 +77,7 @@ public class SessionTracker extends AbstractLifeCycle implements WebSocketSessio break; // SHUTDOWN is abnormal close status so it will hard close connection after sent. - session.close(StatusCode.SHUTDOWN, "Container being shut down"); + session.close(StatusCode.SHUTDOWN, "Container being shut down", Callback.NOOP); } }); } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index f3bf94fc4b2..8999602a90d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -15,29 +15,31 @@ package org.eclipse.jetty.websocket.common; import java.io.IOException; import java.net.SocketAddress; +import java.nio.ByteBuffer; import java.time.Duration; import java.util.Objects; -import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.websocket.api.CloseStatus; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; -import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.core.CoreSession; +import org.eclipse.jetty.websocket.core.Frame; +import org.eclipse.jetty.websocket.core.OpCode; +import org.eclipse.jetty.websocket.core.exception.ProtocolException; -public class WebSocketSession implements Session, SuspendToken, Dumpable +import static java.nio.charset.StandardCharsets.UTF_8; + +public class WebSocketSession implements Session, Dumpable { private final CoreSession coreSession; private final JettyWebSocketFrameHandler frameHandler; - private final JettyWebSocketRemoteEndpoint remoteEndpoint; private final UpgradeRequest upgradeRequest; private final UpgradeResponse upgradeResponse; + private byte messageType = OpCode.UNDEFINED; public WebSocketSession(WebSocketContainer container, CoreSession coreSession, JettyWebSocketFrameHandler frameHandler) { @@ -45,126 +47,197 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable this.coreSession = Objects.requireNonNull(coreSession); this.upgradeRequest = frameHandler.getUpgradeRequest(); this.upgradeResponse = frameHandler.getUpgradeResponse(); - this.remoteEndpoint = new JettyWebSocketRemoteEndpoint(coreSession, frameHandler.getBatchMode()); container.notifySessionListeners((listener) -> listener.onWebSocketSessionCreated(this)); } @Override - public void close() + public void demand() { - coreSession.close(StatusCode.NORMAL, null, Callback.NOOP); + if (frameHandler.isAutoDemand()) + throw new IllegalStateException("auto-demanding endpoint cannot explicitly demand"); + coreSession.demand(1); } @Override - public void close(CloseStatus closeStatus) + public void sendBinary(ByteBuffer buffer, Callback callback) { - coreSession.close(closeStatus.getCode(), closeStatus.getPhrase(), Callback.NOOP); + callback = Objects.requireNonNullElse(callback, Callback.NOOP); + coreSession.sendFrame(new Frame(OpCode.BINARY).setPayload(buffer), + org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail), + false); } @Override - public void close(int statusCode, String reason) + public void sendPartialBinary(ByteBuffer buffer, boolean last, Callback callback) { - coreSession.close(statusCode, reason, Callback.NOOP); - } - - @Override - public void close(int statusCode, String reason, WriteCallback callback) - { - coreSession.close(statusCode, reason, Callback.from(callback::writeSuccess, callback::writeFailed)); - } - - @Override - public WebSocketBehavior getBehavior() - { - switch (coreSession.getBehavior()) + callback = Objects.requireNonNullElse(callback, Callback.NOOP); + Frame frame = switch (messageType) { - case CLIENT: - return WebSocketBehavior.CLIENT; - case SERVER: - return WebSocketBehavior.SERVER; - default: - return null; + case OpCode.UNDEFINED -> + { + // new message + messageType = OpCode.BINARY; + yield new Frame(OpCode.BINARY); + } + case OpCode.BINARY -> new Frame(OpCode.CONTINUATION); + default -> + { + callback.fail(new ProtocolException("Attempt to send partial BINARY during " + OpCode.name(messageType))); + yield null; + } + }; + + if (frame != null) + { + frame.setPayload(buffer); + frame.setFin(last); + + var cb = org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail); + coreSession.sendFrame(frame, cb, false); + + if (last) + messageType = OpCode.UNDEFINED; } } + @Override + public void sendText(String text, Callback callback) + { + callback = Objects.requireNonNullElse(callback, Callback.NOOP); + var cb = org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail); + coreSession.sendFrame(new Frame(OpCode.TEXT).setPayload(text), cb, false); + } + + @Override + public void sendPartialText(String text, boolean last, Callback callback) + { + Frame frame = switch (messageType) + { + case OpCode.UNDEFINED -> + { + // new message + messageType = OpCode.TEXT; + yield new Frame(OpCode.TEXT); + } + case OpCode.TEXT -> new Frame(OpCode.CONTINUATION); + default -> + { + callback.fail(new ProtocolException("Attempt to send partial TEXT during " + OpCode.name(messageType))); + yield null; + } + }; + + if (frame != null) + { + frame.setPayload(BufferUtil.toBuffer(text, UTF_8)); + frame.setFin(last); + + var cb = org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail); + coreSession.sendFrame(frame, cb, false); + + if (last) + messageType = OpCode.UNDEFINED; + } + } + + @Override + public void sendPing(ByteBuffer applicationData, Callback callback) + { + coreSession.sendFrame(new Frame(OpCode.PING).setPayload(applicationData), + org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail), false); + } + + @Override + public void sendPong(ByteBuffer applicationData, Callback callback) + { + coreSession.sendFrame(new Frame(OpCode.PONG).setPayload(applicationData), + org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail), false); + } + + @Override + public void close(int statusCode, String reason, Callback callback) + { + coreSession.close(statusCode, reason, org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail)); + } + @Override public Duration getIdleTimeout() { return coreSession.getIdleTimeout(); } - @Override - public int getInputBufferSize() - { - return coreSession.getInputBufferSize(); - } - - @Override - public int getOutputBufferSize() - { - return coreSession.getOutputBufferSize(); - } - - @Override - public long getMaxBinaryMessageSize() - { - return coreSession.getMaxBinaryMessageSize(); - } - - @Override - public long getMaxTextMessageSize() - { - return coreSession.getMaxTextMessageSize(); - } - - @Override - public long getMaxFrameSize() - { - return coreSession.getMaxFrameSize(); - } - - @Override - public boolean isAutoFragment() - { - return coreSession.isAutoFragment(); - } - @Override public void setIdleTimeout(Duration duration) { coreSession.setIdleTimeout(duration); } + @Override + public int getInputBufferSize() + { + return coreSession.getInputBufferSize(); + } + @Override public void setInputBufferSize(int size) { coreSession.setInputBufferSize(size); } + @Override + public int getOutputBufferSize() + { + return coreSession.getOutputBufferSize(); + } + @Override public void setOutputBufferSize(int size) { coreSession.setOutputBufferSize(size); } + @Override + public long getMaxBinaryMessageSize() + { + return coreSession.getMaxBinaryMessageSize(); + } + @Override public void setMaxBinaryMessageSize(long size) { coreSession.setMaxBinaryMessageSize(size); } + @Override + public long getMaxTextMessageSize() + { + return coreSession.getMaxTextMessageSize(); + } + @Override public void setMaxTextMessageSize(long size) { coreSession.setMaxTextMessageSize(size); } + @Override + public long getMaxFrameSize() + { + return coreSession.getMaxFrameSize(); + } + @Override public void setMaxFrameSize(long maxFrameSize) { coreSession.setMaxFrameSize(maxFrameSize); } + @Override + public boolean isAutoFragment() + { + return coreSession.isAutoFragment(); + } + @Override public void setAutoFragment(boolean autoFragment) { @@ -172,15 +245,21 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable } @Override - public String getProtocolVersion() + public int getMaxOutgoingFrames() { - return upgradeRequest.getProtocolVersion(); + return coreSession.getMaxOutgoingFrames(); } @Override - public JettyWebSocketRemoteEndpoint getRemote() + public void setMaxOutgoingFrames(int maxOutgoingFrames) { - return remoteEndpoint; + coreSession.setMaxOutgoingFrames(maxOutgoingFrames); + } + + @Override + public String getProtocolVersion() + { + return upgradeRequest.getProtocolVersion(); } @Override @@ -202,13 +281,13 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable } @Override - public SocketAddress getLocalAddress() + public SocketAddress getLocalSocketAddress() { return coreSession.getLocalAddress(); } @Override - public SocketAddress getRemoteAddress() + public SocketAddress getRemoteSocketAddress() { return coreSession.getRemoteAddress(); } @@ -225,19 +304,6 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable return this.upgradeResponse; } - @Override - public SuspendToken suspend() - { - frameHandler.suspend(); - return this; - } - - @Override - public void resume() - { - frameHandler.resume(); - } - public CoreSession getCoreSession() { return coreSession; @@ -246,21 +312,20 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable @Override public void dump(Appendable out, String indent) throws IOException { - Dumpable.dumpObjects(out, indent, this, upgradeRequest, coreSession, remoteEndpoint, frameHandler); + Dumpable.dumpObjects(out, indent, this, upgradeRequest, coreSession, frameHandler); } @Override public String dumpSelf() { - return String.format("%s@%x[behavior=%s,idleTimeout=%dms]", + return String.format("%s@%x[idleTimeout=%dms]", this.getClass().getSimpleName(), hashCode(), - getPolicy().getBehavior(), getIdleTimeout().toMillis()); } @Override public String toString() { - return String.format("WebSocketSession[%s,to=%s,%s,%s]", getBehavior(), getIdleTimeout(), coreSession, frameHandler); + return String.format("WebSocketSession[to=%s,%s,%s]", getIdleTimeout(), coreSession, frameHandler); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/ByteBufferMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/ByteBufferMessageSink.java new file mode 100644 index 00000000000..4d9b6b3ef26 --- /dev/null +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/ByteBufferMessageSink.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.internal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.core.CoreSession; +import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; + +public class ByteBufferMessageSink extends org.eclipse.jetty.websocket.core.messages.ByteBufferMessageSink +{ + public ByteBufferMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) + { + super(session, methodHandle, autoDemand, false); + + MethodType onMessageType = MethodType.methodType(Void.TYPE, ByteBuffer.class, Callback.class); + if (methodHandle.type() != onMessageType) + throw InvalidSignatureException.build(onMessageType, methodHandle.type()); + } + + @Override + protected void invoke(MethodHandle methodHandle, ByteBuffer byteBuffer, org.eclipse.jetty.util.Callback callback) throws Throwable + { + methodHandle.invoke(byteBuffer, Callback.from(callback::succeeded, callback::failed)); + } +} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/PartialByteBufferMessageSink.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/PartialByteBufferMessageSink.java new file mode 100644 index 00000000000..479e90cb779 --- /dev/null +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/PartialByteBufferMessageSink.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.common.internal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.core.CoreSession; +import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; + +public class PartialByteBufferMessageSink extends org.eclipse.jetty.websocket.core.messages.PartialByteBufferMessageSink +{ + public PartialByteBufferMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand) + { + super(session, methodHandle, autoDemand); + + MethodType onMessageType = MethodType.methodType(Void.TYPE, ByteBuffer.class, boolean.class, Callback.class); + if (methodHandle.type() != onMessageType) + throw InvalidSignatureException.build(onMessageType, methodHandle.type()); + } + + @Override + protected void invoke(MethodHandle methodHandle, ByteBuffer byteBuffer, boolean fin, org.eclipse.jetty.util.Callback callback) throws Throwable + { + methodHandle.invoke(byteBuffer, fin, Callback.from(callback::succeeded, callback::failed)); + } +} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java index 6cc0c3d91ce..91552bd21dc 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/EndPoints.java @@ -13,24 +13,19 @@ package org.eclipse.jetty.websocket.common; -import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketFrameListener; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.util.TextUtils; @@ -44,14 +39,15 @@ public class EndPoints { } - public static class ListenerBasicSocket implements WebSocketListener + public static class ListenerBasicSocket implements Session.Listener.AutoDemanding { public EventQueue events = new EventQueue(); @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) + public void onWebSocketBinary(ByteBuffer payload, Callback callback) { - events.add("onWebSocketBinary([%d], %d, %d)", payload.length, offset, len); + events.add("onWebSocketBinary([%d])", payload.remaining()); + callback.succeed(); } @Override @@ -61,9 +57,9 @@ public class EndPoints } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - events.add("onWebSocketConnect(%s)", session); + events.add("onWebSocketOpen(%s)", session); } @Override @@ -79,7 +75,7 @@ public class EndPoints } } - public static class ListenerFrameSocket implements WebSocketFrameListener + public static class ListenerFrameSocket implements Session.Listener.AutoDemanding { public EventQueue events = new EventQueue(); @@ -90,9 +86,9 @@ public class EndPoints } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - events.add("onWebSocketConnect(%s)", session); + events.add("onWebSocketOpen(%s)", session); } @Override @@ -102,13 +98,14 @@ public class EndPoints } @Override - public void onWebSocketFrame(Frame frame) + public void onWebSocketFrame(Frame frame, Callback callback) { events.add("onWebSocketFrame(%s)", frame.toString()); + callback.succeed(); } } - public static class ListenerPartialSocket implements WebSocketPartialListener + public static class ListenerPartialSocket implements Session.Listener.AutoDemanding { public EventQueue events = new EventQueue(); @@ -119,9 +116,9 @@ public class EndPoints } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - events.add("onWebSocketConnect(%s)", session); + events.add("onWebSocketOpen(%s)", session); } @Override @@ -137,13 +134,14 @@ public class EndPoints } @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { events.add("onWebSocketPartialBinary(%s, %b)", BufferUtil.toDetailString(payload), fin); + callback.succeed(); } } - public static class ListenerPingPongSocket implements WebSocketPingPongListener + public static class ListenerPingPongSocket implements Session.Listener.AutoDemanding { public EventQueue events = new EventQueue(); @@ -154,9 +152,9 @@ public class EndPoints } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - events.add("onWebSocketConnect(%s)", session); + events.add("onWebSocketOpen(%s)", session); } @Override @@ -184,52 +182,15 @@ public class EndPoints @WebSocket public static class BadDuplicateBinarySocket { - /** - * First method - * - * @param payload the payload - * @param offset the offset - * @param len the len - */ @OnWebSocketMessage - public void binMe(byte[] payload, int offset, int len) + public void binMe(ByteBuffer payload, Callback callback) { - /* ignore */ + callback.succeed(); } - /** - * Second method (also binary) - * - * @param stream the input stream - */ @OnWebSocketMessage public void streamMe(InputStream stream) { - /* ignore */ - } - } - - @WebSocket - public static class AnnotatedBinaryArraySocket - { - public EventQueue events = new EventQueue(); - - @OnWebSocketMessage - public void onBinary(byte[] payload, int offset, int length) - { - events.add("onBinary([%d],%d,%d)", payload.length, offset, length); - } - - @OnWebSocketClose - public void onClose(int statusCode, String reason) - { - events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason)); - } - - @OnWebSocketConnect - public void onConnect(Session sess) - { - events.add("onConnect(%s)", sess); } } @@ -251,10 +212,10 @@ public class EndPoints events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason)); } - @OnWebSocketConnect - public void onConnect(Session sess) + @OnWebSocketOpen + public void onOpen(Session sess) { - events.add("onConnect(%s)", sess); + events.add("onOpen(%s)", sess); } } @@ -269,10 +230,10 @@ public class EndPoints events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason)); } - @OnWebSocketConnect - public void onConnect(Session sess) + @OnWebSocketOpen + public void onOpen(Session sess) { - events.add("onConnect(%s)", sess); + events.add("onOpen(%s)", sess); } @OnWebSocketError @@ -299,10 +260,10 @@ public class EndPoints events.add("onClose(%d, %s)", statusCode, TextUtils.quote(reason)); } - @OnWebSocketConnect - public void onConnect(Session sess) + @OnWebSocketOpen + public void onOpen(Session sess) { - events.add("onConnect(%s)", sess); + events.add("onOpen(%s)", sess); } @OnWebSocketMessage @@ -379,18 +340,31 @@ public class EndPoints } } + @WebSocket(autoDemand = false) + public static class BadAutoDemandWithInputStream + { + @OnWebSocketMessage + public void onMessage(InputStream stream) + { + } + } + + @WebSocket(autoDemand = false) + public static class BadAutoDemandWithReader + { + @OnWebSocketMessage + public void onMessage(Reader reader) + { + } + } + @WebSocket public static class FrameSocket { - /** - * A frame - * - * @param frame the frame - */ @OnWebSocketFrame - public void frameMe(Frame frame) + public void frameMe(Frame frame, Callback callback) { - /* ignore */ + callback.succeed(); } } @@ -401,16 +375,9 @@ public class EndPoints public static class MyEchoBinarySocket extends MyEchoSocket { @OnWebSocketMessage - public void echoBin(byte[] buf, int offset, int length) + public void echoBin(ByteBuffer payload, Callback callback) { - try - { - getRemote().sendBytes(ByteBuffer.wrap(buf, offset, length)); - } - catch (IOException e) - { - e.printStackTrace(); - } + getSession().sendBinary(payload, callback); } } @@ -423,24 +390,16 @@ public class EndPoints public static class MyEchoSocket { private Session session; - private RemoteEndpoint remote; - public RemoteEndpoint getRemote() - { - return remote; - } - - @OnWebSocketClose - public void onClose(int statusCode, String reason) - { - this.session = null; - } - - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { this.session = session; - this.remote = session.getRemote(); + } + + public Session getSession() + { + return session; } @OnWebSocketMessage @@ -453,14 +412,13 @@ public class EndPoints return; } - try - { - remote.sendString(message); - } - catch (IOException e) - { - e.printStackTrace(); - } + session.sendText(message, Callback.NOOP); + } + + @OnWebSocketClose + public void onClose(int statusCode, String reason) + { + this.session = null; } } @@ -478,7 +436,7 @@ public class EndPoints @OnWebSocketMessage public void onText(Session session, String text) { - session.getRemote().sendString(text, null); + session.sendText(text, null); } } @@ -498,8 +456,8 @@ public class EndPoints */ public static class NotASocket { - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { /* do nothing */ } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java index e1801ca9a39..b4ccd135e94 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java @@ -25,7 +25,6 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.core.Behavior; @@ -91,7 +90,7 @@ public class JettyWebSocketFrameHandlerTest return endpointFactory.newJettyFrameHandler(wsEndpoint); } - public static class ConnectionOnly implements WebSocketConnectionListener + public static class ConnectionOnly implements Session.Listener { public EventQueue events = new EventQueue(); @@ -102,9 +101,9 @@ public class JettyWebSocketFrameHandlerTest } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - events.add("onWebSocketConnect(%s)", session); + events.add("onWebSocketOpen(%s)", session); } @Override @@ -127,7 +126,7 @@ public class JettyWebSocketFrameHandlerTest // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketClose\\([^\\)]*\\)"); } @@ -222,7 +221,7 @@ public class JettyWebSocketFrameHandlerTest // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketPartialText\\(\"Hello\", false\\)", "onWebSocketPartialText\\(\" \", false\\)", "onWebSocketPartialText\\(\"World\", true\\)", @@ -252,9 +251,9 @@ public class JettyWebSocketFrameHandlerTest // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketText\\(\"Hello World\"\\)", - "onWebSocketBinary\\(\\[12\\], 0, 12\\)", + "onWebSocketBinary\\(\\[12\\]\\)", "onWebSocketClose\\(NORMAL, \"Normal\"\\)" ); } @@ -274,7 +273,7 @@ public class JettyWebSocketFrameHandlerTest // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketError\\(\\(RuntimeException\\) \"Nothing to see here\"\\)" ); } @@ -298,7 +297,7 @@ public class JettyWebSocketFrameHandlerTest // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketFrame\\(.*TEXT@[0-9a-f]*.len=5,fin=false,.*\\)", "onWebSocketFrame\\(.*CONTINUATION@[0-9a-f]*.len=1,fin=false,.*\\)", "onWebSocketFrame\\(.*CONTINUATION@[0-9a-f]*.len=5,fin=true,.*\\)", @@ -330,7 +329,7 @@ public class JettyWebSocketFrameHandlerTest // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketPing\\(.*ByteBuffer.*You there.*\\)", "onWebSocketPong\\(.*ByteBuffer.*You there.*\\)", "onWebSocketClose\\(NORMAL, \"Normal\"\\)" diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java index 701c74c5b2f..98cf8a877a7 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java @@ -14,10 +14,10 @@ package org.eclipse.jetty.websocket.common; import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.internal.ByteBufferMessageSink; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.DuplicateAnnotationException; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; -import org.eclipse.jetty.websocket.core.messages.ByteArrayMessageSink; import org.eclipse.jetty.websocket.core.messages.InputStreamMessageSink; import org.eclipse.jetty.websocket.core.messages.ReaderMessageSink; import org.eclipse.jetty.websocket.core.messages.StringMessageSink; @@ -104,29 +104,16 @@ public class LocalEndpointMetadataTest assertThat(e.getMessage(), containsString("must not be static")); } - /** - * Test Case for socket for binary array messages - */ @Test - public void testAnnotatedBinaryArraySocket() throws Exception + public void testAnnotatedBadAutoDemandWithInputStream() { - JettyWebSocketFrameHandlerMetadata metadata = createMetadata(EndPoints.AnnotatedBinaryArraySocket.class); + assertThrows(InvalidWebSocketException.class, () -> createMetadata(EndPoints.BadAutoDemandWithInputStream.class)); + } - String classId = EndPoints.AnnotatedBinaryArraySocket.class.getSimpleName(); - - assertThat(classId + ".binaryHandle", metadata.getBinaryHandle(), EXISTS); - assertThat(classId + ".binarySink", metadata.getBinarySink(), equalTo(ByteArrayMessageSink.class)); - - assertThat(classId + ".textHandle", metadata.getTextHandle(), nullValue()); - assertThat(classId + ".textSink", metadata.getTextSink(), nullValue()); - - assertThat(classId + ".openHandle", metadata.getOpenHandle(), EXISTS); - assertThat(classId + ".closeHandle", metadata.getCloseHandle(), EXISTS); - assertThat(classId + ".errorHandle", metadata.getErrorHandle(), nullValue()); - - assertThat(classId + ".frameHandle", metadata.getFrameHandle(), nullValue()); - assertThat(classId + ".pingHandle", metadata.getPingHandle(), nullValue()); - assertThat(classId + ".pongHandle", metadata.getPongHandle(), nullValue()); + @Test + public void testAnnotatedBadAutoDemandWithReader() + { + assertThrows(InvalidWebSocketException.class, () -> createMetadata(EndPoints.BadAutoDemandWithReader.class)); } /** @@ -165,7 +152,7 @@ public class LocalEndpointMetadataTest String classId = EndPoints.MyEchoBinarySocket.class.getSimpleName(); assertThat(classId + ".binaryHandle", metadata.getBinaryHandle(), EXISTS); - assertThat(classId + ".binarySink", metadata.getBinarySink(), equalTo(ByteArrayMessageSink.class)); + assertThat(classId + ".binarySink", metadata.getBinarySink(), equalTo(ByteBufferMessageSink.class)); assertThat(classId + ".textHandle", metadata.getTextHandle(), EXISTS); assertThat(classId + ".textSink", metadata.getTextSink(), equalTo(StringMessageSink.class)); @@ -337,7 +324,7 @@ public class LocalEndpointMetadataTest String classId = EndPoints.ListenerBasicSocket.class.getSimpleName(); assertThat(classId + ".binaryHandle", metadata.getBinaryHandle(), EXISTS); - assertThat(classId + ".binarySink", metadata.getBinarySink(), equalTo(ByteArrayMessageSink.class)); + assertThat(classId + ".binarySink", metadata.getBinarySink(), equalTo(ByteBufferMessageSink.class)); assertThat(classId + ".textHandle", metadata.getTextHandle(), EXISTS); assertThat(classId + ".textSink", metadata.getTextSink(), equalTo(StringMessageSink.class)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/MessageInputStreamTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/MessageInputStreamTest.java index fe233f30aa3..025e1e8ee35 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/MessageInputStreamTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/MessageInputStreamTest.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.messages.MessageInputStream; @@ -42,7 +43,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append a single message (simple, short) Frame frame = new Frame(OpCode.TEXT); @@ -63,7 +64,7 @@ public class MessageInputStreamTest @Test public void testMultipleReadsIntoSingleByteArray() throws IOException { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append a single message (simple, short) Frame frame = new Frame(OpCode.TEXT); @@ -95,7 +96,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { final AtomicBoolean hadError = new AtomicBoolean(false); final CountDownLatch startLatch = new CountDownLatch(1); @@ -140,7 +141,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { final AtomicBoolean hadError = new AtomicBoolean(false); @@ -175,7 +176,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { final AtomicBoolean hadError = new AtomicBoolean(false); @@ -221,7 +222,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append parts of message Frame msg1 = new Frame(OpCode.BINARY).setPayload("Hello ").setFin(false); @@ -248,7 +249,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append parts of message Frame msg1 = new Frame(OpCode.BINARY).setPayload("Hello ").setFin(false); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java index 8a15dcaa0da..c8970e284a1 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/OutgoingMessageCapture.java @@ -22,12 +22,13 @@ import java.util.concurrent.LinkedBlockingDeque; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.toolchain.test.Hex; -import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.common.internal.ByteBufferMessageSink; import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; -import org.eclipse.jetty.websocket.core.messages.ByteBufferMessageSink; import org.eclipse.jetty.websocket.core.messages.MessageSink; import org.eclipse.jetty.websocket.core.messages.StringMessageSink; import org.slf4j.Logger; @@ -45,7 +46,6 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes private final MethodHandle wholeTextHandle; private final MethodHandle wholeBinaryHandle; private MessageSink messageSink; - private long maxMessageSize = 2 * 1024 * 1024; public OutgoingMessageCapture() { @@ -55,7 +55,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes MethodHandle text = lookup.findVirtual(this.getClass(), "onWholeText", MethodType.methodType(Void.TYPE, String.class)); this.wholeTextHandle = text.bindTo(this); - MethodHandle binary = lookup.findVirtual(this.getClass(), "onWholeBinary", MethodType.methodType(Void.TYPE, ByteBuffer.class)); + MethodHandle binary = lookup.findVirtual(this.getClass(), "onWholeBinary", MethodType.methodType(Void.TYPE, ByteBuffer.class, Callback.class)); this.wholeBinaryHandle = binary.bindTo(this); } catch (NoSuchMethodException | IllegalAccessException e) @@ -65,7 +65,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes } @Override - public void sendFrame(Frame frame, Callback callback, boolean batch) + public void sendFrame(Frame frame, org.eclipse.jetty.util.Callback callback, boolean batch) { switch (frame.getOpCode()) { @@ -96,7 +96,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes String event = String.format("TEXT:fin=%b:len=%d", frame.isFin(), frame.getPayloadLength()); LOG.debug(event); events.offer(event); - messageSink = new StringMessageSink(this, wholeTextHandle); + messageSink = new StringMessageSink(this, wholeTextHandle, true); break; } case OpCode.BINARY: @@ -104,7 +104,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes String event = String.format("BINARY:fin=%b:len=%d", frame.isFin(), frame.getPayloadLength()); LOG.debug(event); events.offer(event); - messageSink = new ByteBufferMessageSink(this, wholeBinaryHandle); + messageSink = new ByteBufferMessageSink(this, wholeBinaryHandle, true); break; } case OpCode.CONTINUATION: @@ -119,7 +119,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes if (OpCode.isDataFrame(frame.getOpCode())) { Frame copy = Frame.copy(frame); - messageSink.accept(copy, Callback.from(() -> {}, Throwable::printStackTrace)); + messageSink.accept(copy, org.eclipse.jetty.util.Callback.from(() -> {}, Throwable::printStackTrace)); if (frame.isFin()) messageSink = null; } @@ -140,16 +140,10 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes } @SuppressWarnings("unused") - public void onWholeBinary(ByteBuffer buf) + public void onWholeBinary(ByteBuffer buf, Callback callback) { - ByteBuffer copy = null; - if (buf != null) - { - copy = ByteBuffer.allocate(buf.remaining()); - copy.put(buf); - copy.flip(); - } - this.binaryMessages.offer(copy); + this.binaryMessages.offer(BufferUtil.copy(buf)); + callback.succeed(); } private String dataHint(ByteBuffer payload) diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AdapterEchoSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AdapterEchoSocket.java index 8ac340d22ef..f0fa7c3e0fe 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AdapterEchoSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AdapterEchoSocket.java @@ -13,30 +13,22 @@ package org.eclipse.jetty.websocket.common.endpoints.adapters; -import java.io.IOException; - -import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.api.Session; /** * Example EchoSocket using Adapter. */ -public class AdapterEchoSocket extends WebSocketAdapter +public class AdapterEchoSocket extends Session.Listener.Abstract { @Override public void onWebSocketText(String message) { - if (isConnected()) + if (isOpen()) { - try - { - System.out.printf("Echoing back message [%s]%n", message); - // echo the message back - getRemote().sendString(message); - } - catch (IOException e) - { - e.printStackTrace(System.err); - } + System.out.printf("Echoing back message [%s]%n", message); + // echo the message back + getSession().sendText(message, Callback.NOOP); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AnnotatedEchoSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AnnotatedEchoSocket.java index cf84ed378b8..037e060d04e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AnnotatedEchoSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AnnotatedEchoSocket.java @@ -15,14 +15,21 @@ package org.eclipse.jetty.websocket.common.endpoints.adapters; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Example EchoSocket using Annotations. */ -@WebSocket(maxTextMessageSize = 64 * 1024) +@WebSocket public class AnnotatedEchoSocket { + @OnWebSocketOpen + public void onOpen(Session session) + { + session.setMaxTextMessageSize(64 * 1024); + } + @OnWebSocketMessage public void onText(Session session, String message) { @@ -30,7 +37,7 @@ public class AnnotatedEchoSocket { System.out.printf("Echoing back message [%s]%n", message); // echo the message back - session.getRemote().sendString(message, null); + session.sendText(message, null); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/ListenerEchoSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/ListenerEchoSocket.java index e7166867c27..42ae6e7112e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/ListenerEchoSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/ListenerEchoSocket.java @@ -13,20 +13,23 @@ package org.eclipse.jetty.websocket.common.endpoints.adapters; +import java.nio.ByteBuffer; + +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketListener; /** * Example EchoSocket using Listener. */ -public class ListenerEchoSocket implements WebSocketListener +public class ListenerEchoSocket implements Session.Listener { private Session outbound; @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) + public void onWebSocketBinary(ByteBuffer payload, Callback callback) { - /* only interested in text messages */ + // only interested in text messages. + callback.succeed(); } @Override @@ -36,7 +39,7 @@ public class ListenerEchoSocket implements WebSocketListener } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { this.outbound = session; } @@ -54,7 +57,7 @@ public class ListenerEchoSocket implements WebSocketListener { System.out.printf("Echoing back message [%s]%n", message); // echo the message back - outbound.getRemote().sendString(message, null); + outbound.sendText(message, null); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java index 71af709fa06..475f7910c9d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java @@ -26,10 +26,9 @@ import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.thread.Invocable; +import org.eclipse.jetty.websocket.api.Configurable; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; import org.eclipse.jetty.websocket.common.SessionTracker; import org.eclipse.jetty.websocket.core.WebSocketComponents; @@ -48,7 +47,7 @@ import org.slf4j.LoggerFactory; * URI paths to WebSocket endpoints and configure WebSocket parameters such as idle timeouts, * max WebSocket message sizes, etc.

*/ -public class ServerWebSocketContainer extends ContainerLifeCycle implements WebSocketContainer, WebSocketPolicy, Invocable +public class ServerWebSocketContainer extends ContainerLifeCycle implements WebSocketContainer, Configurable, Invocable { private static final Logger LOG = LoggerFactory.getLogger(ServerWebSocketContainer.class); @@ -113,12 +112,6 @@ public class ServerWebSocketContainer extends ContainerLifeCycle implements WebS } } - @Override - public WebSocketBehavior getBehavior() - { - return configuration.getBehavior(); - } - @Override public Duration getIdleTimeout() { @@ -203,6 +196,18 @@ public class ServerWebSocketContainer extends ContainerLifeCycle implements WebS configuration.setAutoFragment(autoFragment); } + @Override + public int getMaxOutgoingFrames() + { + return configuration.getMaxOutgoingFrames(); + } + + @Override + public void setMaxOutgoingFrames(int maxOutgoingFrames) + { + configuration.setMaxOutgoingFrames(maxOutgoingFrames); + } + /** *

Maps the given {@code pathSpec} to the creator of WebSocket endpoints.

*

The {@code pathSpec} format is that supported by @@ -296,12 +301,7 @@ public class ServerWebSocketContainer extends ContainerLifeCycle implements WebS this.invocationType = invocationType; } - private static class Configuration extends org.eclipse.jetty.websocket.core.Configuration.ConfigurationCustomizer implements WebSocketPolicy + private static class Configuration extends org.eclipse.jetty.websocket.core.Configuration.ConfigurationCustomizer implements Configurable { - @Override - public WebSocketBehavior getBehavior() - { - return WebSocketBehavior.SERVER; - } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java index 7b3cf15a9af..2ddeb1ca344 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnoMaxMessageEndpoint.java @@ -15,17 +15,24 @@ package org.eclipse.jetty.websocket.tests; import java.io.IOException; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -@SuppressWarnings("unused") -@WebSocket(maxTextMessageSize = 100 * 1024) +@WebSocket public class AnnoMaxMessageEndpoint { + @OnWebSocketOpen + public void onOpen(Session session) + { + session.setMaxTextMessageSize(100 * 1024); + } + @OnWebSocketMessage public void onMessage(Session session, String msg) throws IOException { - session.getRemote().sendString(msg); + session.sendText(msg, Callback.NOOP); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnotatedPartialListenerTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnotatedPartialListenerTest.java index 94305bfe218..eb12483e66d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnotatedPartialListenerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/AnnotatedPartialListenerTest.java @@ -24,9 +24,8 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException; @@ -42,26 +41,26 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class AnnotatedPartialListenerTest { - public static class PartialEchoSocket implements WebSocketPartialListener + public static class PartialEchoSocket implements Session.Listener.AutoDemanding { private Session session; @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { this.session = session; } @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { - session.getRemote().sendPartialBytes(payload, fin, WriteCallback.NOOP); + session.sendPartialBinary(payload, fin, callback); } @Override public void onWebSocketPartialText(String payload, boolean fin) { - session.getRemote().sendPartialString(payload, fin, WriteCallback.NOOP); + session.sendPartialText(payload, fin, Callback.NOOP); } } @@ -98,12 +97,13 @@ public class AnnotatedPartialListenerTest } @OnWebSocketMessage - public void onMessage(ByteBuffer buffer, boolean last) + public void onMessage(ByteBuffer buffer, boolean last, Callback callback) { MessageSegment messageSegment = new MessageSegment(); messageSegment.buffer = BufferUtil.copy(buffer); messageSegment.last = last; messages.add(messageSegment); + callback.succeed(); } } @@ -111,12 +111,12 @@ public class AnnotatedPartialListenerTest public static class InvalidDoubleBinaryListener { @OnWebSocketMessage - public void onMessage(ByteBuffer bytes, boolean last) + public void onMessage(ByteBuffer bytes, boolean last, Callback callback) { } @OnWebSocketMessage - public void onMessage(ByteBuffer bytes) + public void onMessage(ByteBuffer bytes, Callback callback) { } } @@ -175,9 +175,10 @@ public class AnnotatedPartialListenerTest PartialStringListener endpoint = new PartialStringListener(); try (Session session = client.connect(endpoint, serverUri).get(5, TimeUnit.SECONDS)) { - session.getRemote().sendPartialString("hell", false); - session.getRemote().sendPartialString("o w", false); - session.getRemote().sendPartialString("orld", true); + Callback.Completable.with(c -> session.sendPartialText("hell", false, c)) + .compose(c -> session.sendPartialText("o w", false, c)) + .compose(c -> session.sendPartialText("orld", true, c)) + .get(); } PartialStringListener.MessageSegment segment; @@ -201,9 +202,10 @@ public class AnnotatedPartialListenerTest PartialByteBufferListener endpoint = new PartialByteBufferListener(); try (Session session = client.connect(endpoint, serverUri).get(5, TimeUnit.SECONDS)) { - session.getRemote().sendPartialBytes(BufferUtil.toBuffer("hell"), false); - session.getRemote().sendPartialBytes(BufferUtil.toBuffer("o w"), false); - session.getRemote().sendPartialBytes(BufferUtil.toBuffer("orld"), true); + Callback.Completable.with(c -> session.sendPartialBinary(BufferUtil.toBuffer("hell"), false, c)) + .compose(c -> session.sendPartialBinary(BufferUtil.toBuffer("o w"), false, c)) + .compose(c -> session.sendPartialBinary(BufferUtil.toBuffer("orld"), true, c)) + .get(); } PartialByteBufferListener.MessageSegment segment; diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseTrackingEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseTrackingEndpoint.java index fe50e849776..ebc6453f613 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseTrackingEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseTrackingEndpoint.java @@ -21,8 +21,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.core.WebSocketConnection; import org.eclipse.jetty.websocket.core.WebSocketCoreSession; @@ -35,7 +36,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -public class CloseTrackingEndpoint extends WebSocketAdapter +public class CloseTrackingEndpoint extends Session.Listener.AbstractAutoDemanding { private static final Logger LOG = LoggerFactory.getLogger(CloseTrackingEndpoint.class); @@ -43,7 +44,7 @@ public class CloseTrackingEndpoint extends WebSocketAdapter public String closeReason = null; public CountDownLatch closeLatch = new CountDownLatch(1); public AtomicInteger closeCount = new AtomicInteger(0); - public CountDownLatch openLatch = new CountDownLatch(1); + public CountDownLatch connectLatch = new CountDownLatch(1); public CountDownLatch errorLatch = new CountDownLatch(1); public LinkedBlockingQueue messageQueue = new LinkedBlockingQueue<>(); @@ -89,11 +90,11 @@ public class CloseTrackingEndpoint extends WebSocketAdapter } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - LOG.debug("onWebSocketConnect({})", session); - super.onWebSocketConnect(session); - openLatch.countDown(); + super.onWebSocketOpen(session); + LOG.debug("onWebSocketOpen({})", session); + connectLatch.countDown(); } @Override @@ -112,10 +113,11 @@ public class CloseTrackingEndpoint extends WebSocketAdapter } @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) + public void onWebSocketBinary(ByteBuffer payload, Callback callback) { - LOG.debug("onWebSocketBinary({},{},{})", payload, offset, len); - binaryMessageQueue.offer(ByteBuffer.wrap(payload, offset, len)); + LOG.debug("onWebSocketBinary({})", payload.remaining()); + binaryMessageQueue.offer(BufferUtil.copy(payload)); + callback.succeed(); } public EndPoint getEndPoint() diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java index 61f0166487c..7852522711d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConcurrentConnectTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; @@ -104,9 +105,9 @@ public class ConcurrentConnectTest for (EventSocket l : listeners) { - l.session.getRemote().sendString("ping"); + l.session.sendText("ping", Callback.NOOP); assertThat(l.textMessages.poll(5, TimeUnit.SECONDS), is("ping")); - l.session.close(StatusCode.NORMAL, "close from client"); + l.session.close(StatusCode.NORMAL, "close from client", Callback.NOOP); } for (EventSocket l : listeners) diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java index f741f464c5d..fefd2287326 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectMessageEndpoint.java @@ -13,19 +13,18 @@ package org.eclipse.jetty.websocket.tests; -import java.io.IOException; - +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @SuppressWarnings("unused") @WebSocket public class ConnectMessageEndpoint { - @OnWebSocketConnect - public void onConnect(Session session) throws IOException + @OnWebSocketOpen + public void onOpen(Session session) { - session.getRemote().sendString("Greeting from onConnect"); + session.sendText("Greeting from onOpen", Callback.NOOP); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectionHeaderTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectionHeaderTest.java index fd93c0546ea..0c90af8522a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectionHeaderTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ConnectionHeaderTest.java @@ -22,6 +22,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.JettyUpgradeListener; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -92,7 +93,7 @@ public class ConnectionHeaderTest { // Generate text frame String msg = "this is an echo ... cho ... ho ... o"; - session.getRemote().sendString(msg); + session.sendText(msg, Callback.NOOP); // Read frame (hopefully text frame) String response = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/DemandWithBlockingStreamsTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/DemandWithBlockingStreamsTest.java new file mode 100644 index 00000000000..7173bcc5452 --- /dev/null +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/DemandWithBlockingStreamsTest.java @@ -0,0 +1,153 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DemandWithBlockingStreamsTest +{ + private final Server server = new Server(); + private final ServerConnector connector = new ServerConnector(server, 1, 1); + private final WebSocketClient client = new WebSocketClient(); + + private void start(Consumer configurer) throws Exception + { + server.addConnector(connector); + + ContextHandler context = new ContextHandler("/"); + + WebSocketUpgradeHandler wsHandler = WebSocketUpgradeHandler.from(server, context); + context.setHandler(wsHandler); + configurer.accept(wsHandler); + + server.setHandler(context); + server.start(); + + client.start(); + } + + @AfterEach + public void dispose() + { + LifeCycle.stop(client); + LifeCycle.stop(server); + } + + @Test + public void testBinaryStreamExplicitDemandThrows() throws Exception + { + StreamEndPoint serverEndPoint = new StreamEndPoint(); + start(wsHandler -> wsHandler.configure(container -> + container.addMapping("/*", (rq, rs, cb) -> serverEndPoint))); + + URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/"); + EventSocket clientEndPoint = new EventSocket(); + client.connect(clientEndPoint, uri).get(5, TimeUnit.SECONDS); + + clientEndPoint.session.sendBinary(ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)), Callback.NOOP); + + // The server-side tried to demand(), should get an error. + assertTrue(serverEndPoint.errorLatch.await(5, TimeUnit.SECONDS)); + assertTrue(serverEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertEquals(StatusCode.SERVER_ERROR, clientEndPoint.closeCode); + } + + @Test + public void testTextStreamExplicitDemandThrows() throws Exception + { + StreamEndPoint serverEndPoint = new StreamEndPoint(); + start(wsHandler -> wsHandler.configure(container -> + container.addMapping("/*", (rq, rs, cb) -> serverEndPoint))); + + URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/"); + EventSocket clientEndPoint = new EventSocket(); + client.connect(clientEndPoint, uri).get(5, TimeUnit.SECONDS); + + clientEndPoint.session.sendText("hello", Callback.NOOP); + + // The server-side tried to demand(), should get an error. + assertTrue(serverEndPoint.errorLatch.await(5, TimeUnit.SECONDS)); + assertTrue(serverEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertEquals(StatusCode.SERVER_ERROR, clientEndPoint.closeCode); + } + + @WebSocket + public static class StreamEndPoint + { + private final CountDownLatch errorLatch = new CountDownLatch(1); + private final CountDownLatch closeLatch = new CountDownLatch(1); + private Session session; + + @OnWebSocketOpen + public void onOpen(Session session) + { + this.session = session; + } + + @OnWebSocketMessage + public void onBinary(InputStream stream) + { + // Throws because this endpoint is auto-demanding. + session.demand(); + } + + @OnWebSocketMessage + public void onText(Reader reader) + { + // Throws because this endpoint is auto-demanding. + session.demand(); + } + + @OnWebSocketError + public void onError(Throwable cause) + { + errorLatch.countDown(); + } + + @OnWebSocketClose + public void onClose(int status, String reason) + { + closeLatch.countDown(); + } + } +} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java index 235c606b122..e0fef023d45 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EchoSocket.java @@ -16,9 +16,9 @@ package org.eclipse.jetty.websocket.tests; import java.io.IOException; import java.nio.ByteBuffer; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -@SuppressWarnings("unused") @WebSocket public class EchoSocket extends EventSocket { @@ -26,13 +26,13 @@ public class EchoSocket extends EventSocket public void onMessage(String message) throws IOException { super.onMessage(message); - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } @Override - public void onMessage(byte[] buf, int offset, int len) throws IOException + public void onMessage(ByteBuffer message, Callback callback) throws IOException { - super.onMessage(buf, offset, len); - session.getRemote().sendBytes(ByteBuffer.wrap(buf, offset, len)); + super.onMessage(message, Callback.NOOP); + session.sendBinary(message, callback); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java index 0df60aed83a..b817e395b3a 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ErrorCloseTest.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -152,7 +153,7 @@ public class ErrorCloseTest serverSocket.methodsToThrow.add("onMessage"); EventSocket clientSocket = new EventSocket(); client.connect(clientSocket, serverUri).get(5, TimeUnit.SECONDS); - clientSocket.session.getRemote().sendString("trigger onMessage error"); + clientSocket.session.sendText("trigger onMessage error", Callback.NOOP); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); @@ -194,7 +195,7 @@ public class ErrorCloseTest try (StacklessLogging ignored = new StacklessLogging(WebSocketSession.class)) { - clientSocket.session.getRemote().sendString("trigger onMessage error"); + clientSocket.session.sendText("trigger onMessage error", Callback.NOOP); assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java index 44c6db9d512..81f4d8b6743 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/EventSocket.java @@ -19,12 +19,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +37,6 @@ public class EventSocket private static final Logger LOG = LoggerFactory.getLogger(EventSocket.class); public Session session; - private String behavior; public BlockingQueue textMessages = new BlockingArrayQueue<>(); public BlockingQueue binaryMessages = new BlockingArrayQueue<>(); @@ -47,11 +48,10 @@ public class EventSocket public CountDownLatch errorLatch = new CountDownLatch(1); public CountDownLatch closeLatch = new CountDownLatch(1); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { this.session = session; - behavior = session.getPolicy().getBehavior().name(); if (LOG.isDebugEnabled()) LOG.debug("{} onOpen(): {}", this, session); openLatch.countDown(); @@ -66,12 +66,12 @@ public class EventSocket } @OnWebSocketMessage - public void onMessage(byte[] buf, int offset, int len) throws IOException + public void onMessage(ByteBuffer message, Callback callback) throws IOException { - ByteBuffer message = ByteBuffer.wrap(buf, offset, len); if (LOG.isDebugEnabled()) LOG.debug("{} onMessage(): {}", this, message); - binaryMessages.offer(message); + binaryMessages.offer(BufferUtil.copy(message)); + callback.succeed(); } @OnWebSocketClose @@ -96,6 +96,6 @@ public class EventSocket @Override public String toString() { - return String.format("[%s@%s]", behavior, Integer.toHexString(hashCode())); + return String.format("[%s@%x]", getClass().getSimpleName(), hashCode()); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ExplicitDemandTest.java similarity index 51% rename from jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java rename to jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ExplicitDemandTest.java index e5d37107b78..263ecd5840d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SuspendResumeTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ExplicitDemandTest.java @@ -21,8 +21,8 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; @@ -33,22 +33,26 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -public class SuspendResumeTest +public class ExplicitDemandTest { - @WebSocket + @WebSocket(autoDemand = false) public static class SuspendSocket extends EventSocket { - volatile SuspendToken suspendToken = null; + @Override + public void onOpen(Session session) + { + super.onOpen(session); + session.demand(); + } @Override public void onMessage(String message) throws IOException { - if ("suspend".equals(message)) - suspendToken = session.suspend(); super.onMessage(message); + if (!"suspend".equals(message)) + session.demand(); } } @@ -84,25 +88,25 @@ public class SuspendResumeTest } @Test - public void testSuspendWhenProcessingFrame() throws Exception + public void testNoDemandWhenProcessingFrame() throws Exception { URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/suspend"); EventSocket clientSocket = new EventSocket(); Future connect = client.connect(clientSocket, uri); connect.get(5, TimeUnit.SECONDS); - clientSocket.session.getRemote().sendString("suspend"); - clientSocket.session.getRemote().sendString("suspend"); - clientSocket.session.getRemote().sendString("hello world"); + clientSocket.session.sendText("suspend", Callback.NOOP); + clientSocket.session.sendText("suspend", Callback.NOOP); + clientSocket.session.sendText("hello world", Callback.NOOP); assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("suspend")); assertNull(serverSocket.textMessages.poll(1, TimeUnit.SECONDS)); - serverSocket.suspendToken.resume(); + serverSocket.session.demand(); assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("suspend")); assertNull(serverSocket.textMessages.poll(1, TimeUnit.SECONDS)); - serverSocket.suspendToken.resume(); + serverSocket.session.demand(); assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("hello world")); assertNull(serverSocket.textMessages.poll(1, TimeUnit.SECONDS)); @@ -115,68 +119,4 @@ public class SuspendResumeTest assertNull(clientSocket.error); assertNull(serverSocket.error); } - - @Test - public void testExternalSuspend() throws Exception - { - URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/suspend"); - EventSocket clientSocket = new EventSocket(); - Future connect = client.connect(clientSocket, uri); - connect.get(5, TimeUnit.SECONDS); - - // verify connection by sending a message from server to client - assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); - serverSocket.session.getRemote().sendString("verification"); - assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("verification")); - - // suspend the client so that no read events occur - SuspendToken suspendToken = clientSocket.session.suspend(); - - // verify client can still send messages - clientSocket.session.getRemote().sendString("message-from-client"); - assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("message-from-client")); - - // the message is not received as it is suspended - serverSocket.session.getRemote().sendString("message-from-server"); - assertNull(clientSocket.textMessages.poll(2, TimeUnit.SECONDS)); - - // client should receive message after it resumes - suspendToken.resume(); - assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("message-from-server")); - - // make sure both sides are closed - clientSocket.session.close(); - assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - - // check no errors occurred - assertNull(clientSocket.error); - assertNull(serverSocket.error); - } - - @Test - public void testSuspendAfterClose() throws Exception - { - URI uri = new URI("ws://localhost:" + connector.getLocalPort() + "/suspend"); - EventSocket clientSocket = new EventSocket(); - Future connect = client.connect(clientSocket, uri); - connect.get(5, TimeUnit.SECONDS); - - // verify connection by sending a message from server to client - assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); - serverSocket.session.getRemote().sendString("verification"); - assertThat(clientSocket.textMessages.poll(5, TimeUnit.SECONDS), is("verification")); - - // make sure both sides are closed - clientSocket.session.close(); - assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); - assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); - - // check no errors occurred - assertNull(clientSocket.error); - assertNull(serverSocket.error); - - // suspend after closed throws ISE - assertThrows(IllegalStateException.class, () -> clientSocket.session.suspend()); - } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java index fdf15fc562d..9c8a4f1677d 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/GetAuthHeaderEndpoint.java @@ -13,21 +13,20 @@ package org.eclipse.jetty.websocket.tests; -import java.io.IOException; - +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @SuppressWarnings("unused") @WebSocket public class GetAuthHeaderEndpoint { - @OnWebSocketConnect - public void onConnect(Session session) throws IOException + @OnWebSocketOpen + public void onOpen(Session session) { String authHeaderName = "Authorization"; String authHeaderValue = session.getUpgradeRequest().getHeader(authHeaderName); - session.getRemote().sendString("Header[" + authHeaderName + "]=" + authHeaderValue); + session.sendText("Header[" + authHeaderName + "]=" + authHeaderValue, Callback.NOOP); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java index ac6d18fa926..6d2a2d04760 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyOnCloseTest.java @@ -21,6 +21,7 @@ import java.util.function.Consumer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -123,7 +124,7 @@ public class JettyOnCloseTest client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS); assertTrue(serverEndpoint.openLatch.await(5, TimeUnit.SECONDS)); - serverEndpoint.setOnClose((session) -> session.close(StatusCode.SERVICE_RESTART, "custom close reason")); + serverEndpoint.setOnClose((session) -> session.close(StatusCode.SERVICE_RESTART, "custom close reason", Callback.NOOP)); clientEndpoint.session.close(); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); @@ -139,9 +140,9 @@ public class JettyOnCloseTest client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS); assertTrue(serverEndpoint.openLatch.await(5, TimeUnit.SECONDS)); - serverEndpoint.setOnClose(Session::close); + serverEndpoint.setOnClose(session -> session.close()); - serverEndpoint.session.close(StatusCode.NORMAL, "first close"); + serverEndpoint.session.close(StatusCode.NORMAL, "first close", Callback.NOOP); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); assertThat(clientEndpoint.closeReason, is("first close")); @@ -157,11 +158,11 @@ public class JettyOnCloseTest assertTrue(serverEndpoint.openLatch.await(5, TimeUnit.SECONDS)); serverEndpoint.setOnClose((session) -> { - session.close(StatusCode.SERVER_ERROR, "abnormal close 2"); + session.close(StatusCode.SERVER_ERROR, "abnormal close 2", Callback.NOOP); clientEndpoint.unBlockClose(); }); - serverEndpoint.session.close(StatusCode.PROTOCOL, "abnormal close 1"); + serverEndpoint.session.close(StatusCode.PROTOCOL, "abnormal close 1", Callback.NOOP); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientEndpoint.closeCode, is(StatusCode.PROTOCOL)); assertThat(clientEndpoint.closeReason, is("abnormal close 1")); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java index 933765e4115..71e8dfd80e9 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/JettyWebSocketExtensionConfigTest.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.ExtensionConfig; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; @@ -120,7 +121,7 @@ public class JettyWebSocketExtensionConfigTest CompletableFuture connect = client.connect(socket, uri, request, listener); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertTrue(correctResponseExtensions.await(5, TimeUnit.SECONDS)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/LargeDeflateTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/LargeDeflateTest.java index 235f729386b..e42d19ee679 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/LargeDeflateTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/LargeDeflateTest.java @@ -23,6 +23,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; @@ -85,8 +86,8 @@ public class LargeDeflateTest EventSocket clientSocket = new EventSocket(); Session session = _client.connect(clientSocket, URI.create("ws://localhost:" + _connector.getLocalPort() + "/ws"), upgradeRequest).get(); ByteBuffer sentMessage = largePayloads(); - session.getRemote().sendBytes(sentMessage); - session.close(StatusCode.NORMAL, "close from test"); + session.sendBinary(sentMessage, Callback.NOOP); + session.close(StatusCode.NORMAL, "close from test", Callback.NOOP); assertTrue(_serverSocket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(_serverSocket.closeCode, is(StatusCode.NORMAL)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/MaxOutgoingFramesTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/MaxOutgoingFramesTest.java index 0d280eecf36..0e55a17a793 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/MaxOutgoingFramesTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/MaxOutgoingFramesTest.java @@ -22,16 +22,14 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; -import org.eclipse.jetty.websocket.api.WriteCallback; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.core.AbstractExtension; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; -import org.eclipse.jetty.websocket.tests.util.FutureWriteCallback; +import org.eclipse.jetty.websocket.tests.util.FutureCallback; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -95,7 +93,7 @@ public class MaxOutgoingFramesTest } @Override - public void sendFrame(Frame frame, Callback callback, boolean batch) + public void sendFrame(Frame frame, org.eclipse.jetty.util.Callback callback, boolean batch) { try { @@ -110,7 +108,7 @@ public class MaxOutgoingFramesTest } } - public static class CountingCallback implements WriteCallback + public static class CountingCallback implements Callback { private final CountDownLatch successes; @@ -120,13 +118,13 @@ public class MaxOutgoingFramesTest } @Override - public void writeSuccess() + public void succeed() { successes.countDown(); } @Override - public void writeFailed(Throwable t) + public void fail(Throwable t) { t.printStackTrace(); } @@ -147,22 +145,21 @@ public class MaxOutgoingFramesTest assertTrue(socket.openLatch.await(5, TimeUnit.SECONDS)); int numFrames = 30; - RemoteEndpoint remote = socket.session.getRemote(); - remote.setMaxOutgoingFrames(numFrames); + socket.session.setMaxOutgoingFrames(numFrames); // Verify that we can send up to numFrames without any problem. // First send will block in the Extension so it needs to be done in new thread, others frames will be queued. CountingCallback countingCallback = new CountingCallback(numFrames); - new Thread(() -> remote.sendString("0", countingCallback)).start(); + new Thread(() -> socket.session.sendText("0", countingCallback)).start(); assertTrue(firstFrameBlocked.await(5, TimeUnit.SECONDS)); for (int i = 1; i < numFrames; i++) { - remote.sendString(Integer.toString(i), countingCallback); + socket.session.sendText(Integer.toString(i), countingCallback); } // Sending any more frames will result in WritePendingException. - FutureWriteCallback callback = new FutureWriteCallback(); - remote.sendString("fail", callback); + FutureCallback callback = new FutureCallback(); + socket.session.sendText("fail", callback); ExecutionException executionException = assertThrows(ExecutionException.class, () -> callback.get(5, TimeUnit.SECONDS)); assertThat(executionException.getCause(), instanceOf(WritePendingException.class)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java index 090ca481882..11415b63462 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/ParamsEndpoint.java @@ -13,21 +13,21 @@ package org.eclipse.jetty.websocket.tests; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @SuppressWarnings("unused") @WebSocket public class ParamsEndpoint { - @OnWebSocketConnect - public void onConnect(Session session) throws IOException + @OnWebSocketOpen + public void onOpen(Session session) { Map> params = session.getUpgradeRequest().getParameterMap(); StringBuilder msg = new StringBuilder(); @@ -39,6 +39,6 @@ public class ParamsEndpoint msg.append("\n"); } - session.getRemote().sendString(msg.toString()); + session.sendText(msg.toString(), Callback.NOOP); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleEchoTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleEchoTest.java index 4030ae37ca1..b203f14e0c1 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleEchoTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SimpleEchoTest.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -82,7 +83,7 @@ public class SimpleEchoTest session.setIdleTimeout(Duration.ofSeconds(timeout)); String message = "hello world 1234"; - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); String received = clientEndpoint.textMessages.poll(timeout, TimeUnit.SECONDS); assertThat(received, equalTo(message)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SingleOnMessageTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SingleOnMessageTest.java index 98e87039aaa..ae759521335 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SingleOnMessageTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/SingleOnMessageTest.java @@ -23,12 +23,12 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -84,11 +84,10 @@ public class SingleOnMessageTest assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); // The server sends a sequence of Binary and Text messages - RemoteEndpoint remote = serverSocket.session.getRemote(); - remote.sendBytes(BufferUtil.toBuffer("this should get rejected")); - remote.sendString("WebSocket_Data0"); - remote.sendString("WebSocket_Data1"); - serverSocket.session.close(StatusCode.NORMAL, "test complete"); + serverSocket.session.sendBinary(BufferUtil.toBuffer("this should get rejected"), Callback.NOOP); + serverSocket.session.sendText("WebSocket_Data0", Callback.NOOP); + serverSocket.session.sendText("WebSocket_Data1", Callback.NOOP); + serverSocket.session.close(StatusCode.NORMAL, "test complete", Callback.NOOP); // The client receives the messages and has discarded the binary message. assertThat(handler.messages.poll(5, TimeUnit.SECONDS), is("WebSocket_Data0")); @@ -107,13 +106,12 @@ public class SingleOnMessageTest assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); // The server sends a sequence of Binary and Text messages - RemoteEndpoint remote = serverSocket.session.getRemote(); - remote.sendString("this should get rejected"); - remote.sendBytes(BufferUtil.toBuffer("WebSocket_Data0")); - remote.sendBytes(BufferUtil.toBuffer("WebSocket_Data1")); - serverSocket.session.close(StatusCode.NORMAL, "test complete"); + serverSocket.session.sendText("this should get rejected", Callback.NOOP); + serverSocket.session.sendBinary(BufferUtil.toBuffer("WebSocket_Data0"), Callback.NOOP); + serverSocket.session.sendBinary(BufferUtil.toBuffer("WebSocket_Data1"), Callback.NOOP); + serverSocket.session.close(StatusCode.NORMAL, "test complete", Callback.NOOP); - // The client receives the messages and has discarded the binary message. + // The client receives the messages and has discarded the text message. assertThat(handler.messages.poll(5, TimeUnit.SECONDS), is(BufferUtil.toBuffer("WebSocket_Data0"))); assertThat(handler.messages.poll(5, TimeUnit.SECONDS), is(BufferUtil.toBuffer("WebSocket_Data1"))); assertTrue(handler.closeLatch.await(5, TimeUnit.SECONDS)); @@ -139,9 +137,10 @@ public class SingleOnMessageTest final BlockingArrayQueue messages = new BlockingArrayQueue<>(); @OnWebSocketMessage - public void onMessage(byte[] array, int offset, int length) + public void onMessage(ByteBuffer payload, Callback callback) { - messages.add(BufferUtil.toBuffer(array, offset, length)); + messages.add(BufferUtil.copy(payload)); + callback.succeed(); } } @@ -162,8 +161,8 @@ public class SingleOnMessageTest this.closeLatch.countDown(); } - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { this.session = session; this.openLatch.countDown(); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java index 054f0193603..a10fcd14d36 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStatsTest.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.core.CloseStatus; @@ -123,7 +124,7 @@ public class WebSocketStatsTest { for (int i = 0; i < numMessages; i++) { - session.getRemote().sendString(msgText); + session.sendText(msgText, Callback.NOOP); } } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java index fde36bc70ef..7ae24bedda4 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketStopTest.java @@ -13,14 +13,15 @@ package org.eclipse.jetty.websocket.tests; -import java.io.IOException; import java.net.URI; import java.nio.channels.ClosedChannelException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; @@ -100,9 +101,9 @@ public class WebSocketStopTest ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); upgradeRequest.addExtensions("permessage-deflate"); Session session = client.connect(clientSocket, uri, upgradeRequest).get(5, TimeUnit.SECONDS); - clientSocket.session.getRemote().sendString("init deflater"); + clientSocket.session.sendText("init deflater", Callback.NOOP); assertThat(serverSocket.textMessages.poll(5, TimeUnit.SECONDS), is("init deflater")); - session.close(StatusCode.NORMAL, null); + session.close(StatusCode.NORMAL, null, Callback.NOOP); // make sure both sides are closed clientSocket.session.close(); @@ -113,8 +114,10 @@ public class WebSocketStopTest assertThat(clientSocket.closeCode, is(StatusCode.NORMAL)); assertThat(serverSocket.closeCode, is(StatusCode.NORMAL)); - IOException error = assertThrows(IOException.class, - () -> session.getRemote().sendString("this should fail before ExtensionStack")); + ExecutionException error = assertThrows(ExecutionException.class, () -> + Callback.Completable.with(c -> session.sendText("this should fail before ExtensionStack", c)) + .get(5, TimeUnit.SECONDS) + ); assertThat(error.getCause(), instanceOf(ClosedChannelException.class)); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java index 519e1c3f2f1..acbc20e61af 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/autobahn/JettyAutobahnClient.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeoutException; import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -157,7 +158,7 @@ public class JettyAutobahnClient if (waitForUpgrade(wsUri, response)) { String msg = onCaseCount.textMessages.poll(10, TimeUnit.SECONDS); - onCaseCount.session.close(StatusCode.SHUTDOWN, null); + onCaseCount.session.close(StatusCode.SHUTDOWN, null, Callback.NOOP); assertTrue(onCaseCount.closeLatch.await(2, TimeUnit.SECONDS)); assertNotNull(msg); return Integer.decode(msg); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java index 0585add746e..8d2e538f280 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/BadNetworkTest.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.websocket.tests.client; -import java.io.IOException; import java.net.URI; import java.time.Duration; import java.util.concurrent.Future; @@ -22,9 +21,9 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; @@ -122,7 +121,7 @@ public class BadNetworkTest Session session = future.get(30, TimeUnit.SECONDS); // Have server disconnect abruptly - session.getRemote().sendString("abort"); + session.sendText("abort", Callback.NOOP); // Client Socket should see a close event, with status NO_CLOSE // This event is automatically supplied by the underlying WebSocketClientConnection @@ -130,55 +129,36 @@ public class BadNetworkTest wsocket.assertReceivedCloseEvent(5000, is(StatusCode.NO_CLOSE), containsString("")); } - public static class ServerEndpoint implements WebSocketListener + public static class ServerEndpoint implements Session.Listener.AutoDemanding { private static final Logger LOG = LoggerFactory.getLogger(ClientCloseTest.ServerEndpoint.class); private Session session; @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) + public void onWebSocketOpen(Session session) { + this.session = session; } @Override public void onWebSocketText(String message) { - try + if (message.equals("abort")) { - if (message.equals("abort")) - { - session.disconnect(); - } - else - { - // simple echo - session.getRemote().sendString(message); - } + session.disconnect(); } - catch (IOException e) + else { - LOG.warn("Failed to send string", e); + // simple echo + session.sendText(message, Callback.NOOP); } } - @Override - public void onWebSocketClose(int statusCode, String reason) - { - } - - @Override - public void onWebSocketConnect(Session session) - { - this.session = session; - } - @Override public void onWebSocketError(Throwable cause) { if (LOG.isDebugEnabled()) - { LOG.debug("ServerEndpoint error", cause); - } } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java index ad13d3eee5c..ecf61d5e5af 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientCloseTest.java @@ -28,11 +28,10 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketFrameListener; -import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.api.exceptions.MessageTooLargeException; import org.eclipse.jetty.websocket.api.exceptions.WebSocketTimeoutException; import org.eclipse.jetty.websocket.api.util.WSURI; @@ -74,7 +73,7 @@ public class ClientCloseTest { // Send message from client to server final String echoMsg = "echo-test"; - clientSocket.getRemote().sendString(echoMsg); + clientSocket.getSession().sendText(echoMsg, Callback.NOOP); // Verify received message String recvMsg = clientSocket.messageQueue.poll(5, SECONDS); @@ -159,7 +158,8 @@ public class ClientCloseTest // client sends close frame (code 1000, normal) final String origCloseReason = "send-more-frames"; - clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason); + Session session = clientSocket.getSession(); + session.close(StatusCode.NORMAL, origCloseReason, Callback.NOOP); // Verify received messages String recvMsg = clientSocket.messageQueue.poll(5, SECONDS); @@ -193,7 +193,8 @@ public class ClientCloseTest // client confirms connection via echo confirmConnection(clientSocket, clientConnectFuture); - clientSocket.getSession().getRemote().sendString("too-large-message"); + Session session = clientSocket.getSession(); + session.sendText("too-large-message", Callback.NOOP); clientSocket.assertReceivedCloseEvent(timeout, is(StatusCode.MESSAGE_TOO_LARGE), containsString("Text message too large")); // client should have noticed the error @@ -224,7 +225,8 @@ public class ClientCloseTest // client sends close frame (triggering server connection abort) final String origCloseReason = "abort"; - clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason); + Session session = clientSocket.getSession(); + session.close(StatusCode.NORMAL, origCloseReason, Callback.NOOP); // client reads -1 (EOF) // client triggers close event on client ws-endpoint @@ -255,7 +257,8 @@ public class ClientCloseTest // client sends close frame final String origCloseReason = "sleep|2500"; - clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason); + Session session = clientSocket.getSession(); + session.close(StatusCode.NORMAL, origCloseReason, Callback.NOOP); // client close should occur clientSocket.assertReceivedCloseEvent(clientTimeout * 2, @@ -283,8 +286,10 @@ public class ClientCloseTest confirmConnection(clientSocket, clientConnectFuture); // Close twice, first close should succeed and second close is a NOOP - clientSocket.getSession().close(StatusCode.NORMAL, "close1"); - clientSocket.getSession().close(StatusCode.NO_CODE, "close2"); + Session session1 = clientSocket.getSession(); + session1.close(StatusCode.NORMAL, "close1", Callback.NOOP); + Session session = clientSocket.getSession(); + session.close(StatusCode.NO_CODE, "close2", Callback.NOOP); // Second close is ignored, we are notified of the first close. clientSocket.assertReceivedCloseEvent(5000, is(StatusCode.NORMAL), containsString("close1")); @@ -324,7 +329,8 @@ public class ClientCloseTest // block all the server threads for (int i = 0; i < sessionCount; i++) { - clientSockets.get(i).getSession().getRemote().sendString("block"); + Session session = clientSockets.get(i).getSession(); + session.sendText("block", Callback.NOOP); } assertTimeoutPreemptively(ofSeconds(5), () -> @@ -375,7 +381,8 @@ public class ClientCloseTest try { // Block on the server so that the server does not detect a read failure - clientSocket.getSession().getRemote().sendString("block"); + Session session1 = clientSocket.getSession(); + session1.sendText("block", Callback.NOOP); // setup client endpoint for write failure (test only) EndPoint endp = clientSocket.getEndPoint(); @@ -384,7 +391,8 @@ public class ClientCloseTest // client enqueue close frame // should result in a client write failure final String origCloseReason = "Normal Close from Client"; - clientSocket.getSession().close(StatusCode.NORMAL, origCloseReason); + Session session = clientSocket.getSession(); + session.close(StatusCode.NORMAL, origCloseReason, Callback.NOOP); assertThat("OnError Latch", clientSocket.errorLatch.await(2, SECONDS), is(true)); assertThat("OnError", clientSocket.error.get(), instanceOf(EofException.class)); @@ -405,15 +413,16 @@ public class ClientCloseTest } } - public static class ServerEndpoint implements WebSocketFrameListener, WebSocketListener + public static class ServerEndpoint implements Session.Listener.AutoDemanding { private static final Logger LOG = LoggerFactory.getLogger(ServerEndpoint.class); private Session session; CountDownLatch block = new CountDownLatch(1); @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) + public void onWebSocketOpen(Session session) { + this.session = session; } @Override @@ -427,7 +436,7 @@ public class ClientCloseTest byte[] buf = new byte[1024 * 1024]; Arrays.fill(buf, (byte)'x'); String bigmsg = new String(buf, UTF_8); - session.getRemote().sendString(bigmsg); + session.sendText(bigmsg, Callback.NOOP); } else if (message.equals("block")) { @@ -438,7 +447,7 @@ public class ClientCloseTest else { // simple echo - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } } catch (Throwable t) @@ -448,17 +457,6 @@ public class ClientCloseTest } } - @Override - public void onWebSocketClose(int statusCode, String reason) - { - } - - @Override - public void onWebSocketConnect(Session session) - { - this.session = session; - } - @Override public void onWebSocketError(Throwable cause) { @@ -467,7 +465,7 @@ public class ClientCloseTest } @Override - public void onWebSocketFrame(Frame frame) + public void onWebSocketFrame(Frame frame, Callback callback) { if (frame.getOpCode() == OpCode.CLOSE) { @@ -478,12 +476,12 @@ public class ClientCloseTest { try { - session.getRemote().sendString("Hello"); - session.getRemote().sendString("World"); + session.sendText("Hello", Callback.NOOP); + session.sendText("World", Callback.NOOP); } - catch (Throwable ignore) + catch (Throwable x) { - LOG.debug("OOPS", ignore); + LOG.debug("OOPS", x); } } else if (reason.equals("abort")) @@ -494,9 +492,9 @@ public class ClientCloseTest LOG.info("Server aborting session abruptly"); session.disconnect(); } - catch (Throwable ignore) + catch (Throwable x) { - LOG.trace("IGNORED", ignore); + LOG.trace("IGNORED", x); } } else if (reason.startsWith("sleep|")) @@ -508,12 +506,13 @@ public class ClientCloseTest LOG.info("Server Sleeping for {} ms", timeMs); TimeUnit.MILLISECONDS.sleep(timeMs); } - catch (InterruptedException ignore) + catch (InterruptedException x) { - LOG.trace("IGNORED", ignore); + LOG.trace("IGNORED", x); } } } + callback.succeed(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java index e0391f2c43c..2c7547e5840 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConfigTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.websocket.tests.client; import java.net.URI; +import java.nio.ByteBuffer; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -23,7 +24,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -62,7 +63,7 @@ public class ClientConfigTest public static Stream data() { - return Stream.of("clientConfig", "annotatedConfig", "sessionConfig").map(Arguments::of); + return Stream.of("clientConfig", "sessionConfig").map(Arguments::of); } @BeforeEach @@ -93,11 +94,6 @@ public class ClientConfigTest server.stop(); } - @WebSocket(idleTimeout = IDLE_TIMEOUT, maxTextMessageSize = MAX_MESSAGE_SIZE, maxBinaryMessageSize = MAX_MESSAGE_SIZE, inputBufferSize = INPUT_BUFFER_SIZE, batchMode = BatchMode.ON) - public static class AnnotatedConfigEndpoint extends EventSocket - { - } - @WebSocket public static class SessionConfigEndpoint extends EventSocket { @@ -124,7 +120,6 @@ public class ClientConfigTest client.setMaxTextMessageSize(MAX_MESSAGE_SIZE); yield new EventSocket(); } - case "annotatedConfig" -> new AnnotatedConfigEndpoint(); case "sessionConfig" -> new SessionConfigEndpoint(); default -> throw new IllegalStateException(); }; @@ -162,7 +157,8 @@ public class ClientConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendBytes(BufferUtil.toBuffer(MESSAGE)); + ByteBuffer buffer = BufferUtil.toBuffer(MESSAGE); + clientEndpoint.session.sendBinary(buffer, Callback.NOOP); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientEndpoint.error, instanceOf(MessageTooLargeException.class)); @@ -180,7 +176,7 @@ public class ClientConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendString("hello world"); + clientEndpoint.session.sendText("hello world", Callback.NOOP); Thread.sleep(IDLE_TIMEOUT + 500); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); @@ -199,7 +195,7 @@ public class ClientConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendString(MESSAGE); + clientEndpoint.session.sendText(MESSAGE, Callback.NOOP); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientEndpoint.error, instanceOf(MessageTooLargeException.class)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java index 89028aca048..3a570c1acc3 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientConnectTest.java @@ -89,7 +89,7 @@ public class ClientConnectTest assertThat("Error", capcause, errorMatcher); // Validate that websocket didn't see an open event - assertThat("Open Latch", wsocket.openLatch.getCount(), is(1L)); + assertThat("Open Latch", wsocket.connectLatch.getCount(), is(1L)); // Return the captured cause return (E)capcause; diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java index 8252280be0b..d666fda82b7 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientSessionsTest.java @@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; @@ -118,8 +118,7 @@ public class ClientSessionsTest Collection sessions = client.getOpenSessions(); assertThat("client.connectionManager.sessions.size", sessions.size(), is(1)); - RemoteEndpoint remote = sess.getRemote(); - remote.sendString("Hello World!"); + sess.sendText("Hello World!", Callback.NOOP); Collection open = client.getOpenSessions(); assertThat("(Before Close) Open Sessions.size", open.size(), is(1)); @@ -127,7 +126,7 @@ public class ClientSessionsTest String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); assertThat("Message", received, containsString("Hello World!")); - sess.close(StatusCode.NORMAL, null); + sess.close(StatusCode.NORMAL, null, Callback.NOOP); } cliSock.assertReceivedCloseEvent(30000, is(StatusCode.NORMAL)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientWriteThread.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientWriteThread.java index c8636e22c50..710ddc02f56 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientWriteThread.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ClientWriteThread.java @@ -16,10 +16,8 @@ package org.eclipse.jetty.websocket.tests.client; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import org.eclipse.jetty.websocket.api.BatchMode; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.tests.util.FutureWriteCallback; +import org.eclipse.jetty.websocket.tests.util.FutureCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,12 +58,11 @@ public class ClientWriteThread extends Thread { LOG.debug("Writing {} messages to {}", messageCount, session); LOG.debug("Artificial Slowness {} ms", slowness); - FutureWriteCallback lastMessage = null; - RemoteEndpoint remote = session.getRemote(); + FutureCallback lastMessage = null; while (m.get() < messageCount) { - lastMessage = new FutureWriteCallback(); - remote.sendString(message + "/" + m.get() + "/", lastMessage); + lastMessage = new FutureCallback(); + session.sendText(message + "/" + m.get() + "/", lastMessage); m.incrementAndGet(); @@ -74,8 +71,6 @@ public class ClientWriteThread extends Thread TimeUnit.MILLISECONDS.sleep(slowness); } } - if (remote.getBatchMode() == BatchMode.ON) - remote.flush(); // block on write of last message if (lastMessage != null) lastMessage.get(2, TimeUnit.MINUTES); // block on write diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java index c29cdcc7639..1ecf68ef5fd 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/ConnectFutureTest.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.client.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; @@ -113,7 +114,7 @@ public class ConnectFutureTest assertTrue(connect.cancel(true)); assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); exitCreator.countDown(); - assertFalse(clientSocket.openLatch.await(1, TimeUnit.SECONDS)); + assertFalse(clientSocket.connectLatch.await(1, TimeUnit.SECONDS)); Throwable error = clientSocket.error.get(); assertThat(error, instanceOf(UpgradeException.class)); @@ -156,7 +157,7 @@ public class ConnectFutureTest assertTrue(connect.cancel(true)); assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); exitListener.countDown(); - assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); assertThat(clientSocket.error.get(), instanceOf(CancellationException.class)); } @@ -196,7 +197,7 @@ public class ConnectFutureTest assertTrue(connect.cancel(true)); assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS)); exitListener.countDown(); - assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); assertThat(clientSocket.error.get(), instanceOf(CancellationException.class)); } @@ -208,16 +209,16 @@ public class ConnectFutureTest wsHandler.configure(container -> container.addMapping("/", (upgradeRequest, upgradeResponse, callback) -> new EchoSocket()))); - CountDownLatch exitOnOpen = new CountDownLatch(1); + CountDownLatch exitOnConnect = new CountDownLatch(1); CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint() { @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { try { - super.onWebSocketConnect(session); - exitOnOpen.await(); + super.onWebSocketOpen(session); + exitOnConnect.await(); } catch (InterruptedException e) { @@ -228,9 +229,9 @@ public class ConnectFutureTest // Abort during the call to onOpened. This is after the connection upgrade, but before future completion. Future connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); - assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS)); assertTrue(connect.cancel(true)); - exitOnOpen.countDown(); + exitOnConnect.countDown(); // We got an error on the WebSocket endpoint and an error from the future. assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS)); @@ -249,8 +250,9 @@ public class ConnectFutureTest Session session = connect.get(5, TimeUnit.SECONDS); // If we can send and receive messages the future has been completed. - assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); - clientSocket.getSession().getRemote().sendString("hello"); + assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS)); + Session session1 = clientSocket.getSession(); + session1.sendText("hello", Callback.NOOP); assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), Matchers.is("hello")); // After it has been completed we should not get any errors from cancelling it. @@ -345,16 +347,16 @@ public class ConnectFutureTest wsHandler.configure(container -> container.addMapping("/", (upgradeRequest, upgradeResponse, callback) -> new EchoSocket()))); - CountDownLatch exitOnOpen = new CountDownLatch(1); + CountDownLatch exitOnConnect = new CountDownLatch(1); CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint() { @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { try { - super.onWebSocketConnect(session); - exitOnOpen.await(); + super.onWebSocketOpen(session); + exitOnConnect.await(); } catch (InterruptedException e) { @@ -365,9 +367,9 @@ public class ConnectFutureTest // Complete the CompletableFuture with an exception the during the call to onOpened. CompletableFuture connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI())); - assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); + assertTrue(clientSocket.connectLatch.await(5, TimeUnit.SECONDS)); assertTrue(connect.completeExceptionally(new WebSocketException("custom exception"))); - exitOnOpen.countDown(); + exitOnConnect.countDown(); // Exception from the future is correct. ExecutionException futureError = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java index f54636e62cd..326a4781dab 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/SlowClientTest.java @@ -20,6 +20,7 @@ import java.util.concurrent.Future; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.util.WSURI; @@ -104,7 +105,8 @@ public class SlowClientTest writer.join(); // Close - clientEndpoint.getSession().close(StatusCode.NORMAL, "Done"); + Session session = clientEndpoint.getSession(); + session.close(StatusCode.NORMAL, "Done", Callback.NOOP); // confirm close received on server clientEndpoint.assertReceivedCloseEvent(10000, is(StatusCode.NORMAL), containsString("Done")); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java index 9744137da86..0806c4c0981 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/client/WebSocketClientTest.java @@ -30,7 +30,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.util.WSURI; @@ -42,7 +42,7 @@ import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint; import org.eclipse.jetty.websocket.tests.ConnectMessageEndpoint; import org.eclipse.jetty.websocket.tests.EchoSocket; import org.eclipse.jetty.websocket.tests.ParamsEndpoint; -import org.eclipse.jetty.websocket.tests.util.FutureWriteCallback; +import org.eclipse.jetty.websocket.tests.util.FutureCallback; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -153,8 +153,7 @@ public class WebSocketClientTest Collection sessions = client.getOpenSessions(); assertThat("client.sessions.size", sessions.size(), is(1)); - RemoteEndpoint remote = cliSock.getSession().getRemote(); - remote.sendString("Hello World!"); + cliSock.getSession().sendText("Hello World!", Callback.NOOP); // wait for response from server String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); @@ -184,10 +183,9 @@ public class WebSocketClientTest Collection sessions = client.getOpenSessions(); assertThat("client.sessions.size", sessions.size(), is(1)); - RemoteEndpoint remote = cliSock.getSession().getRemote(); - remote.sendPartialString("Hello", false); - remote.sendPartialString(" ", false); - remote.sendPartialString("World", true); + cliSock.getSession().sendPartialText("Hello", false, Callback.NOOP); + cliSock.getSession().sendPartialText(" ", false, Callback.NOOP); + cliSock.getSession().sendPartialText("World", true, Callback.NOOP); // wait for response from server String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); @@ -217,10 +215,9 @@ public class WebSocketClientTest Collection sessions = client.getOpenSessions(); assertThat("client.sessions.size", sessions.size(), is(1)); - RemoteEndpoint remote = cliSock.getSession().getRemote(); - remote.sendPartialString("Hello", false); - remote.sendPartialString(" ", false); - remote.sendPartialString("World", true); + cliSock.getSession().sendPartialText("Hello", false, Callback.NOOP); + cliSock.getSession().sendPartialText(" ", false, Callback.NOOP); + cliSock.getSession().sendPartialText("World", true, Callback.NOOP); String[] parts = { "The difference between the right word ", @@ -228,9 +225,15 @@ public class WebSocketClientTest "between lightning and a lightning bug." }; - remote.sendPartialBytes(BufferUtil.toBuffer(parts[0]), false); - remote.sendPartialBytes(BufferUtil.toBuffer(parts[1]), false); - remote.sendPartialBytes(BufferUtil.toBuffer(parts[2]), true); + Session session2 = cliSock.getSession(); + ByteBuffer b2 = BufferUtil.toBuffer(parts[0]); + session2.sendPartialBinary(b2, false, Callback.NOOP); + Session session1 = cliSock.getSession(); + ByteBuffer b1 = BufferUtil.toBuffer(parts[1]); + session1.sendPartialBinary(b1, false, Callback.NOOP); + Session session = cliSock.getSession(); + ByteBuffer b = BufferUtil.toBuffer(parts[2]); + session.sendPartialBinary(b, true, Callback.NOOP); // wait for response from server String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); @@ -264,9 +267,9 @@ public class WebSocketClientTest Collection sessions = client.getOpenSessions(); assertThat("client.sessions.size", sessions.size(), is(1)); - FutureWriteCallback callback = new FutureWriteCallback(); + FutureCallback callback = new FutureCallback(); - cliSock.getSession().getRemote().sendString("Hello World!", callback); + cliSock.getSession().sendText("Hello World!", callback); callback.get(5, TimeUnit.SECONDS); // wait for response from server @@ -295,7 +298,7 @@ public class WebSocketClientTest // wait for message from server String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); - assertThat("Message", received, containsString("Greeting from onConnect")); + assertThat("Message", received, containsString("Greeting from onOpen")); } } @@ -313,10 +316,10 @@ public class WebSocketClientTest try (Session sess = future.get(5, TimeUnit.SECONDS)) { - Assertions.assertTrue(cliSock.openLatch.await(1, TimeUnit.SECONDS)); + Assertions.assertTrue(cliSock.connectLatch.await(1, TimeUnit.SECONDS)); - InetSocketAddress local = (InetSocketAddress)cliSock.getSession().getLocalAddress(); - InetSocketAddress remote = (InetSocketAddress)cliSock.getSession().getRemoteAddress(); + InetSocketAddress local = (InetSocketAddress)cliSock.getSession().getLocalSocketAddress(); + InetSocketAddress remote = (InetSocketAddress)cliSock.getSession().getRemoteSocketAddress(); assertThat("Local Socket Address", local, notNullValue()); assertThat("Remote Socket Address", remote, notNullValue()); @@ -359,7 +362,7 @@ public class WebSocketClientTest Arrays.fill(buf, (byte)'x'); String msg = StringUtil.toUTF8String(buf, 0, buf.length); - sess.getRemote().sendString(msg); + sess.sendText(msg, Callback.NOOP); // wait for message from server String received = cliSock.messageQueue.poll(5, TimeUnit.SECONDS); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java index 8d7d2253b70..e7e0ccb374e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractAnnotatedListener.java @@ -13,12 +13,12 @@ package org.eclipse.jetty.websocket.tests.listeners; -import java.io.IOException; import java.nio.ByteBuffer; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @WebSocket @@ -26,8 +26,8 @@ public class AbstractAnnotatedListener { protected Session _session; - @OnWebSocketConnect - public void onWebSocketConnect(Session session) + @OnWebSocketOpen + public void onWebSocketOpen(Session session) { _session = session; } @@ -40,25 +40,11 @@ public class AbstractAnnotatedListener public void sendText(String message, boolean last) { - try - { - _session.getRemote().sendPartialString(message, last); - } - catch (IOException e) - { - throw new RuntimeException(e); - } + _session.sendPartialText(message, last, Callback.NOOP); } public void sendBinary(ByteBuffer message, boolean last) { - try - { - _session.getRemote().sendPartialBytes(message, last); - } - catch (IOException e) - { - throw new RuntimeException(e); - } + _session.sendPartialBinary(message, last, Callback.NOOP); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java deleted file mode 100644 index 856b94ed51a..00000000000 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java +++ /dev/null @@ -1,61 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.websocket.tests.listeners; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; - -public class AbstractListener implements WebSocketConnectionListener -{ - protected Session _session; - - @Override - public void onWebSocketConnect(Session session) - { - _session = session; - } - - @Override - public void onWebSocketError(Throwable thr) - { - thr.printStackTrace(); - } - - public void sendText(String message, boolean last) - { - try - { - _session.getRemote().sendPartialString(message, last); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } - - public void sendBinary(ByteBuffer message, boolean last) - { - try - { - _session.getRemote().sendPartialBytes(message, last); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java index e1071e6de32..06999f10c0e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/BinaryListeners.java @@ -20,9 +20,8 @@ import java.util.stream.Stream; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.junit.jupiter.params.provider.Arguments; @@ -35,28 +34,48 @@ public class BinaryListeners OffsetByteArrayWholeListener.class, OffsetByteBufferPartialListener.class, AnnotatedByteBufferWholeListener.class, - AnnotatedByteArrayWholeListener.class, - AnnotatedOffsetByteArrayWholeListener.class, AnnotatedInputStreamWholeListener.class, AnnotatedReverseArgumentPartialListener.class ).map(Arguments::of); } - public static class OffsetByteArrayWholeListener extends AbstractListener implements WebSocketListener + public static class OffsetByteArrayWholeListener extends Session.Listener.Abstract { @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) + public void onWebSocketOpen(Session session) { - sendBinary(BufferUtil.toBuffer(payload, offset, len), true); + super.onWebSocketOpen(session); + session.demand(); + } + + @Override + public void onWebSocketBinary(ByteBuffer payload, Callback callback) + { + getSession().sendPartialBinary(payload, true, Callback.from(() -> + { + callback.succeed(); + getSession().demand(); + }, callback::fail)); } } - public static class OffsetByteBufferPartialListener extends AbstractListener implements WebSocketPartialListener + public static class OffsetByteBufferPartialListener extends Session.Listener.Abstract { @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketOpen(Session session) { - sendBinary(payload, fin); + super.onWebSocketOpen(session); + session.demand(); + } + + @Override + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) + { + getSession().sendPartialBinary(payload, fin, Callback.from(() -> + { + callback.succeed(); + getSession().demand(); + }, callback::fail)); } } @@ -64,29 +83,9 @@ public class BinaryListeners public static class AnnotatedByteBufferWholeListener extends AbstractAnnotatedListener { @OnWebSocketMessage - public void onMessage(ByteBuffer message) + public void onMessage(ByteBuffer message, Callback callback) { - sendBinary(message, true); - } - } - - @WebSocket - public static class AnnotatedByteArrayWholeListener extends AbstractAnnotatedListener - { - @OnWebSocketMessage - public void onMessage(byte[] message) - { - sendBinary(BufferUtil.toBuffer(message), true); - } - } - - @WebSocket - public static class AnnotatedOffsetByteArrayWholeListener extends AbstractAnnotatedListener - { - @OnWebSocketMessage - public void onMessage(byte[] message, int offset, int length) - { - sendBinary(BufferUtil.toBuffer(message, offset, length), true); + _session.sendPartialBinary(message, true, callback); } } @@ -104,9 +103,9 @@ public class BinaryListeners public static class AnnotatedReverseArgumentPartialListener extends AbstractAnnotatedListener { @OnWebSocketMessage - public void onMessage(Session session, ByteBuffer message) + public void onMessage(Session session, ByteBuffer message, Callback callback) { - sendBinary(message, true); + _session.sendPartialBinary(message, true, callback); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java index 873a89761b1..7c4b8129baf 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/TextListeners.java @@ -18,9 +18,8 @@ import java.io.Reader; import java.util.stream.Stream; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.junit.jupiter.params.provider.Arguments; @@ -38,21 +37,41 @@ public class TextListeners ).map(Arguments::of); } - public static class StringWholeListener extends AbstractListener implements WebSocketListener + public static class StringWholeListener extends Session.Listener.Abstract { + @Override + public void onWebSocketOpen(Session session) + { + super.onWebSocketOpen(session); + session.demand(); + } + @Override public void onWebSocketText(String message) { - sendText(message, true); + getSession().sendPartialText(message, true, Callback.from(getSession()::demand, x -> + { + throw new RuntimeException(x); + })); } } - public static class StringPartialListener extends AbstractListener implements WebSocketPartialListener + public static class StringPartialListener extends Session.Listener.Abstract { + @Override + public void onWebSocketOpen(Session session) + { + super.onWebSocketOpen(session); + session.demand(); + } + @Override public void onWebSocketPartialText(String message, boolean fin) { - sendText(message, fin); + getSession().sendPartialText(message, fin, Callback.from(getSession()::demand, x -> + { + throw new RuntimeException(x); + })); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java index 0470c8d1099..e552dfb4e5c 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/WebSocketListenerTest.java @@ -27,9 +27,9 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketListener; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.tests.EchoSocket; @@ -99,12 +99,12 @@ public class WebSocketListenerTest // Send and receive echo on client. String payload = "hello world"; - clientEndpoint.session.getRemote().sendString(payload); + clientEndpoint.session.sendText(payload, Callback.NOOP); String echoMessage = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); assertThat(echoMessage, is(payload)); // Close normally. - clientEndpoint.session.close(StatusCode.NORMAL, "standard close"); + clientEndpoint.session.close(StatusCode.NORMAL, "standard close", Callback.NOOP); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); assertThat(clientEndpoint.closeReason, is("standard close")); @@ -119,12 +119,12 @@ public class WebSocketListenerTest // Send and receive echo on client. ByteBuffer payload = BufferUtil.toBuffer("hello world"); - clientEndpoint.session.getRemote().sendBytes(payload); + clientEndpoint.session.sendBinary(payload, Callback.NOOP); ByteBuffer echoMessage = clientEndpoint.binaryMessages.poll(5, TimeUnit.SECONDS); assertThat(echoMessage, is(payload)); // Close normally. - clientEndpoint.session.close(StatusCode.NORMAL, "standard close"); + clientEndpoint.session.close(StatusCode.NORMAL, "standard close", Callback.NOOP); assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL)); assertThat(clientEndpoint.closeReason, is("standard close")); @@ -136,10 +136,10 @@ public class WebSocketListenerTest CountDownLatch openLatch = new CountDownLatch(1); CountDownLatch closeLatch = new CountDownLatch(1); BlockingQueue textMessages = new BlockingArrayQueue<>(); - WebSocketListener clientEndpoint = new WebSocketListener() + Session.Listener clientEndpoint = new Session.Listener.AutoDemanding() { @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { openLatch.countDown(); } @@ -162,12 +162,12 @@ public class WebSocketListenerTest // Send and receive echo on client. String payload = "hello world"; - session.getRemote().sendString(payload); + session.sendText(payload, Callback.NOOP); String echoMessage = textMessages.poll(5, TimeUnit.SECONDS); assertThat(echoMessage, is(payload)); // Close normally. - session.close(StatusCode.NORMAL, "standard close"); + session.close(StatusCode.NORMAL, "standard close", Callback.NOOP); assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxy.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxy.java index f046e34884f..e7b21021f17 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxy.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxy.java @@ -13,20 +13,18 @@ package org.eclipse.jetty.websocket.tests.proxy; +import java.io.IOException; +import java.io.UncheckedIOException; import java.net.URI; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; -import org.eclipse.jetty.websocket.api.exceptions.WebSocketException; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.slf4j.Logger; @@ -47,7 +45,7 @@ public class WebSocketProxy this.serverUri = serverUri; } - public WebSocketConnectionListener getWebSocketConnectionListener() + public Session.Listener getSessionListener() { return clientToProxy; } @@ -68,7 +66,7 @@ public class WebSocketProxy } } - public class ClientToProxy implements WebSocketPartialListener, WebSocketPingPongListener + public class ClientToProxy implements Session.Listener { private volatile Session session; private final CountDownLatch closeLatch = new CountDownLatch(1); @@ -80,49 +78,47 @@ public class WebSocketProxy public void fail(Throwable failure) { - session.close(StatusCode.SERVER_ERROR, failure.getMessage()); + String reason = failure.getMessage(); + session.close(StatusCode.SERVER_ERROR, reason, Callback.NOOP); } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { if (LOG.isDebugEnabled()) - LOG.debug("{} onWebSocketConnect({})", getClass().getSimpleName(), session); + LOG.debug("{} onWebSocketOpen({})", getClass().getSimpleName(), session); - Future connect = null; try { this.session = session; ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); upgradeRequest.setSubProtocols(session.getUpgradeRequest().getSubProtocols()); upgradeRequest.setExtensions(session.getUpgradeRequest().getExtensions()); - connect = client.connect(proxyToServer, serverUri, upgradeRequest); - - //This is blocking as we really want the client to be connected before receiving any messages. - connect.get(); + client.connect(proxyToServer, serverUri, upgradeRequest) + // Only demand for frames after the connect() is successful. + .thenAccept(ignored -> session.demand()); } - catch (Exception e) + catch (IOException x) { - if (connect != null) - connect.cancel(true); - throw new WebSocketException(e); + throw new UncheckedIOException(x); } } @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPartialBinary({}, {})", getClass().getSimpleName(), BufferUtil.toDetailString(payload), fin); - try - { - proxyToServer.getSession().getRemote().sendPartialBytes(payload, fin); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + Callback.Completable.with(c -> proxyToServer.getSession().sendPartialBinary(payload, fin, c)) + .thenRun(callback::succeed) + .thenRun(session::demand) + .exceptionally(x -> + { + callback.fail(x); + fail(x); + return null; + }); } @Override @@ -131,14 +127,7 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPartialText({}, {})", getClass().getSimpleName(), StringUtil.truncate(payload, 100), fin); - try - { - proxyToServer.getSession().getRemote().sendPartialString(payload, fin); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + proxyToServer.getSession().sendPartialText(payload, fin, Callback.from(session::demand, this::fail)); } @Override @@ -147,14 +136,7 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPing({})", getClass().getSimpleName(), BufferUtil.toDetailString(payload)); - try - { - proxyToServer.getSession().getRemote().sendPing(payload); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + proxyToServer.getSession().sendPing(payload, Callback.from(session::demand, this::fail)); } @Override @@ -163,14 +145,7 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPong({})", getClass().getSimpleName(), BufferUtil.toDetailString(payload)); - try - { - proxyToServer.getSession().getRemote().sendPong(payload); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + proxyToServer.getSession().sendPong(payload, Callback.from(session::demand, this::fail)); } @Override @@ -189,14 +164,14 @@ public class WebSocketProxy LOG.debug("{} onWebSocketClose({} {})", getClass().getSimpleName(), statusCode, reason); // Session may be null if connection to the server failed. - Session session = proxyToServer.getSession(); - if (session != null) - session.close(statusCode, reason); + Session proxyToServerSession = proxyToServer.getSession(); + if (proxyToServerSession != null) + proxyToServerSession.close(statusCode, reason, Callback.NOOP); closeLatch.countDown(); } } - public class ProxyToServer implements WebSocketPartialListener, WebSocketPingPongListener + public class ProxyToServer implements Session.Listener { private volatile Session session; private final CountDownLatch closeLatch = new CountDownLatch(1); @@ -211,32 +186,34 @@ public class WebSocketProxy // Only ProxyToServer can be failed before it is opened (if ClientToProxy fails before the connect completes). Session session = this.session; if (session != null) - session.close(StatusCode.SERVER_ERROR, failure.getMessage()); + session.close(StatusCode.SERVER_ERROR, failure.getMessage(), Callback.NOOP); } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { if (LOG.isDebugEnabled()) - LOG.debug("{} onWebSocketConnect({})", getClass().getSimpleName(), session); + LOG.debug("{} onWebSocketOpen({})", getClass().getSimpleName(), session); this.session = session; + session.demand(); } @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPartialBinary({}, {})", getClass().getSimpleName(), BufferUtil.toDetailString(payload), fin); - try - { - clientToProxy.getSession().getRemote().sendPartialBytes(payload, fin); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + Callback.Completable.with(c -> clientToProxy.getSession().sendPartialBinary(payload, fin, c)) + .thenRun(callback::succeed) + .thenRun(session::demand) + .exceptionally(x -> + { + callback.fail(x); + fail(x); + return null; + }); } @Override @@ -245,14 +222,7 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPartialText({}, {})", getClass().getSimpleName(), StringUtil.truncate(payload, 100), fin); - try - { - clientToProxy.getSession().getRemote().sendPartialString(payload, fin); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + clientToProxy.getSession().sendPartialText(payload, fin, Callback.from(session::demand, this::fail)); } @Override @@ -261,14 +231,7 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPing({})", getClass().getSimpleName(), BufferUtil.toDetailString(payload)); - try - { - clientToProxy.getSession().getRemote().sendPing(payload); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + clientToProxy.getSession().sendPing(payload, Callback.from(session::demand, this::fail)); } @Override @@ -277,14 +240,7 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketPong({})", getClass().getSimpleName(), BufferUtil.toDetailString(payload)); - try - { - clientToProxy.getSession().getRemote().sendPong(payload); - } - catch (Exception e) - { - throw new WebSocketException(e); - } + clientToProxy.getSession().sendPong(payload, Callback.from(session::demand, this::fail)); } @Override @@ -302,7 +258,8 @@ public class WebSocketProxy if (LOG.isDebugEnabled()) LOG.debug("{} onWebSocketClose({} {})", getClass().getSimpleName(), statusCode, reason); - clientToProxy.getSession().close(statusCode, reason); + Session clientToProxySession = clientToProxy.getSession(); + clientToProxySession.close(statusCode, reason, Callback.NOOP); closeLatch.countDown(); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxyTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxyTest.java index f906b682e25..95ad1df8a93 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxyTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/proxy/WebSocketProxyTest.java @@ -28,6 +28,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; @@ -72,7 +73,7 @@ public class WebSocketProxyTest context.setHandler(wsHandler); wsHandler.configure(container -> { - container.addMapping("/proxy", (rq, rs, cb) -> webSocketProxy.getWebSocketConnectionListener()); + container.addMapping("/proxy", (rq, rs, cb) -> webSocketProxy.getSessionListener()); serverSocket = new EchoSocket(); container.addMapping("/echo", (rq, rs, cb) -> { @@ -110,15 +111,16 @@ public class WebSocketProxyTest assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); // Test an echo spread across multiple frames. - clientSocket.session.getRemote().sendPartialString("hell", false); - clientSocket.session.getRemote().sendPartialString("o w", false); - clientSocket.session.getRemote().sendPartialString("orld", false); - clientSocket.session.getRemote().sendPartialString("!", true); + Callback.Completable.with(c -> clientSocket.session.sendPartialText("hell", false, c)) + .compose(c -> clientSocket.session.sendPartialText("o w", false, c)) + .compose(c -> clientSocket.session.sendPartialText("orld", false, c)) + .compose(c -> clientSocket.session.sendPartialText("!", true, c)) + .get(); String response = clientSocket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is("hello world!")); // Test we closed successfully on the client side. - clientSocket.session.close(StatusCode.NORMAL, "test initiated close"); + clientSocket.session.close(StatusCode.NORMAL, "test initiated close", Callback.NOOP); assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(clientSocket.closeCode, is(StatusCode.NORMAL)); assertThat(clientSocket.closeReason, is("test initiated close")); @@ -212,7 +214,7 @@ public class WebSocketProxyTest assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS)); assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); - clientSocket.session.getRemote().sendString("hello world!"); + clientSocket.session.sendText("hello world!", Callback.NOOP); // Verify expected client close. assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); @@ -245,7 +247,7 @@ public class WebSocketProxyTest serverSocket.session.setIdleTimeout(Duration.ZERO); // Send and receive an echo message. - clientSocket.session.getRemote().sendString("test echo message"); + clientSocket.session.sendText("test echo message", Callback.NOOP); assertThat(clientSocket.textMessages.poll(clientSessionIdleTimeout, TimeUnit.SECONDS), is("test echo message")); // Wait more than the idleTimeout period, the clientToProxy connection should fail which should fail the proxyToServer. @@ -273,19 +275,22 @@ public class WebSocketProxyTest assertTrue(serverSocket.openLatch.await(5, TimeUnit.SECONDS)); // Test unsolicited pong from client. - clientSocket.session.getRemote().sendPong(BufferUtil.toBuffer("unsolicited pong from client")); + ByteBuffer b2 = BufferUtil.toBuffer("unsolicited pong from client"); + clientSocket.session.sendPong(b2, Callback.NOOP); assertThat(serverEndpoint.pingMessages.size(), is(0)); assertThat(serverEndpoint.pongMessages.poll(5, TimeUnit.SECONDS), is(BufferUtil.toBuffer("unsolicited pong from client"))); // Test unsolicited pong from server. - serverEndpoint.session.getRemote().sendPong(BufferUtil.toBuffer("unsolicited pong from server")); + ByteBuffer b1 = BufferUtil.toBuffer("unsolicited pong from server"); + serverEndpoint.session.sendPong(b1, Callback.NOOP); assertThat(clientSocket.pingMessages.size(), is(0)); assertThat(clientSocket.pongMessages.poll(5, TimeUnit.SECONDS), is(BufferUtil.toBuffer("unsolicited pong from server"))); // Test pings from client. for (int i = 0; i < 15; i++) { - clientSocket.session.getRemote().sendPing(intToStringByteBuffer(i)); + ByteBuffer b = intToStringByteBuffer(i); + clientSocket.session.sendPing(b, Callback.NOOP); } for (int i = 0; i < 15; i++) { @@ -296,7 +301,8 @@ public class WebSocketProxyTest // Test pings from server. for (int i = 0; i < 23; i++) { - serverEndpoint.session.getRemote().sendPing(intToStringByteBuffer(i)); + ByteBuffer b = intToStringByteBuffer(i); + serverEndpoint.session.sendPing(b, Callback.NOOP); } for (int i = 0; i < 23; i++) { @@ -304,7 +310,7 @@ public class WebSocketProxyTest assertThat(serverEndpoint.pongMessages.poll(5, TimeUnit.SECONDS), is(intToStringByteBuffer(i))); } - clientSocket.session.close(StatusCode.NORMAL, "closing from test"); + clientSocket.session.close(StatusCode.NORMAL, "closing from test", Callback.NOOP); // Verify expected client close. assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS)); @@ -338,19 +344,14 @@ public class WebSocketProxyTest public BlockingQueue pongMessages = new BlockingArrayQueue<>(); @OnWebSocketFrame - public void onWebSocketFrame(Frame frame) + public void onWebSocketFrame(Frame frame, Callback callback) { switch (frame.getOpCode()) { - case OpCode.PING: - pingMessages.add(BufferUtil.copy(frame.getPayload())); - break; - case OpCode.PONG: - pongMessages.add(BufferUtil.copy(frame.getPayload())); - break; - default: - break; + case OpCode.PING -> pingMessages.add(BufferUtil.copy(frame.getPayload())); + case OpCode.PONG -> pongMessages.add(BufferUtil.copy(frame.getPayload())); } + callback.succeed(); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/AbstractCloseEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/AbstractCloseEndpoint.java index 75316e35db3..1d65952727c 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/AbstractCloseEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/AbstractCloseEndpoint.java @@ -18,7 +18,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.hamcrest.Matcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,10 +26,10 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -public abstract class AbstractCloseEndpoint extends WebSocketAdapter +public abstract class AbstractCloseEndpoint extends Session.Listener.AbstractAutoDemanding { public final Logger log; - public CountDownLatch openLatch = new CountDownLatch(1); + public CountDownLatch connectLatch = new CountDownLatch(1); public CountDownLatch closeLatch = new CountDownLatch(1); public String closeReason = null; public int closeStatusCode = -1; @@ -42,11 +41,18 @@ public abstract class AbstractCloseEndpoint extends WebSocketAdapter } @Override - public void onWebSocketConnect(Session sess) + public void onWebSocketOpen(Session sess) { - super.onWebSocketConnect(sess); - log.debug("onWebSocketConnect({})", sess); - openLatch.countDown(); + super.onWebSocketOpen(sess); + log.debug("onWebSocketOpen({})", sess); + connectLatch.countDown(); + } + + @Override + public void onWebSocketError(Throwable cause) + { + log.debug("onWebSocketError({})", cause.getClass().getSimpleName()); + errors.offer(cause); } @Override @@ -58,25 +64,13 @@ public abstract class AbstractCloseEndpoint extends WebSocketAdapter closeLatch.countDown(); } - @Override - public void onWebSocketError(Throwable cause) - { - log.debug("onWebSocketError({})", cause.getClass().getSimpleName()); - errors.offer(cause); - } - - public void assertReceivedCloseEvent(int clientTimeoutMs, Matcher statusCodeMatcher, Matcher reasonMatcher) - throws InterruptedException + public void assertReceivedCloseEvent(int clientTimeoutMs, Matcher statusCodeMatcher, Matcher reasonMatcher) throws InterruptedException { assertThat("Client Close Event Occurred", closeLatch.await(clientTimeoutMs, TimeUnit.MILLISECONDS), is(true)); assertThat("Client Close Event Status Code", closeStatusCode, statusCodeMatcher); if (reasonMatcher == null) - { assertThat("Client Close Event Reason", closeReason, nullValue()); - } else - { assertThat("Client Close Event Reason", closeReason, reasonMatcher); - } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpoint.java index 4a35dc0bae3..52c9c59afb0 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpoint.java @@ -13,6 +13,8 @@ package org.eclipse.jetty.websocket.tests.server; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; public class CloseInOnCloseEndpoint extends AbstractCloseEndpoint @@ -20,7 +22,8 @@ public class CloseInOnCloseEndpoint extends AbstractCloseEndpoint @Override public void onWebSocketClose(int statusCode, String reason) { - getSession().close(StatusCode.SERVER_ERROR, "this should be a noop"); + Session session = getSession(); + session.close(StatusCode.SERVER_ERROR, "this should be a noop", Callback.NOOP); super.onWebSocketClose(statusCode, reason); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpointNewThread.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpointNewThread.java index c4323d418d0..5cd8a2980f0 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpointNewThread.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/CloseInOnCloseEndpointNewThread.java @@ -15,6 +15,8 @@ package org.eclipse.jetty.websocket.tests.server; import java.util.concurrent.CountDownLatch; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; public class CloseInOnCloseEndpointNewThread extends AbstractCloseEndpoint @@ -27,7 +29,8 @@ public class CloseInOnCloseEndpointNewThread extends AbstractCloseEndpoint CountDownLatch complete = new CountDownLatch(1); new Thread(() -> { - getSession().close(StatusCode.SERVER_ERROR, "this should be a noop"); + Session session = getSession(); + session.close(StatusCode.SERVER_ERROR, "this should be a noop", Callback.NOOP); complete.countDown(); }).start(); complete.await(); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ContainerEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ContainerEndpoint.java index 9f1b750d64e..8ee793c2dd4 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ContainerEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ContainerEndpoint.java @@ -15,10 +15,10 @@ package org.eclipse.jetty.websocket.tests.server; import java.util.Collection; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WriteCallback; /** * On Message, return container information @@ -49,15 +49,15 @@ public class ContainerEndpoint extends AbstractCloseEndpoint { ret.append('[').append(idx++).append("] ").append(sess.toString()).append('\n'); } - session.getRemote().sendString(ret.toString(), WriteCallback.NOOP); + session.sendText(ret.toString(), Callback.NOOP); } - session.close(StatusCode.NORMAL, "ContainerEndpoint"); + session.close(StatusCode.NORMAL, "ContainerEndpoint", Callback.NOOP); } @Override - public void onWebSocketConnect(Session sess) + public void onWebSocketOpen(Session sess) { - log.debug("onWebSocketConnect({})", sess); + log.debug("onWebSocketOpen({})", sess); this.session = sess; } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DynamicServerConfigurationTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DynamicServerConfigurationTest.java index 2cbafe14a3c..032673a76a1 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DynamicServerConfigurationTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/DynamicServerConfigurationTest.java @@ -26,8 +26,8 @@ import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketContainer; import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; @@ -86,7 +86,7 @@ public class DynamicServerConfigurationTest start(new Handler.Abstract() { @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception + public boolean handle(Request request, Response response, org.eclipse.jetty.util.Callback callback) throws Exception { String pathInContext = Request.getPathInContext(request); if ("/config".equals(pathInContext)) @@ -114,7 +114,7 @@ public class DynamicServerConfigurationTest future = wsClient.connect(clientEndPoint, wsUri); try (Session session = future.get(5, SECONDS)) { - session.getRemote().sendString("OK"); + session.sendText("OK", Callback.NOOP); String reply = clientEndPoint.textMessages.poll(5, SECONDS); assertEquals("OK", reply); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastCloseEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastCloseEndpoint.java index a25f57e7be4..337e93feb37 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastCloseEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastCloseEndpoint.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.websocket.tests.server; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; @@ -22,9 +23,9 @@ import org.eclipse.jetty.websocket.api.StatusCode; public class FastCloseEndpoint extends AbstractCloseEndpoint { @Override - public void onWebSocketConnect(Session sess) + public void onWebSocketOpen(Session sess) { - log.debug("onWebSocketConnect({})", sess); - sess.close(StatusCode.NORMAL, "FastCloseServer"); + log.debug("onWebSocketOpen({})", sess); + sess.close(StatusCode.NORMAL, "FastCloseServer", Callback.NOOP); } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastFailEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastFailEndpoint.java index f95ba16bdaa..3e9776afbc2 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastFailEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FastFailEndpoint.java @@ -21,9 +21,9 @@ import org.eclipse.jetty.websocket.api.Session; public class FastFailEndpoint extends AbstractCloseEndpoint { @Override - public void onWebSocketConnect(Session sess) + public void onWebSocketOpen(Session sess) { - log.debug("onWebSocketConnect({})", sess); + log.debug("onWebSocketOpen({})", sess); // Test failure due to unhandled exception // this should trigger a fast-fail closure during open/connect throw new RuntimeException("Intentional FastFail"); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java index 30a8a17af80..e606ffb97af 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameAnnotationTest.java @@ -24,13 +24,13 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; @@ -116,10 +116,9 @@ public class FrameAnnotationTest { session = futSession.get(5, SECONDS); - RemoteEndpoint clientRemote = session.getRemote(); - clientRemote.sendPartialString("hello", false); - clientRemote.sendPartialString(" ", false); - clientRemote.sendPartialString("world", true); + session.sendPartialText("hello", false, Callback.NOOP); + session.sendPartialText(" ", false, Callback.NOOP); + session.sendPartialText("world", true, Callback.NOOP); String event = serverEndpoint.frameEvents.poll(5, SECONDS); assertThat("Event", event, is("FRAME[TEXT,fin=false,payload=hello,len=5]")); @@ -141,32 +140,33 @@ public class FrameAnnotationTest public CountDownLatch closeLatch = new CountDownLatch(1); public LinkedBlockingQueue frameEvents = new LinkedBlockingQueue<>(); - @OnWebSocketClose - public void onWebSocketClose(int statusCode, String reason) - { - closeLatch.countDown(); - } - - @OnWebSocketConnect - public void onWebSocketConnect(Session session) + @OnWebSocketOpen + public void onWebSocketOpen(Session session) { this.session = session; } + @OnWebSocketFrame + public void onWebSocketFrame(Frame frame, Callback callback) + { + frameEvents.offer(String.format("FRAME[%s,fin=%b,payload=%s,len=%d]", + OpCode.name(frame.getOpCode()), + frame.isFin(), + BufferUtil.toUTF8String(frame.getPayload()), + frame.getPayloadLength())); + callback.succeed(); + } + @OnWebSocketError public void onWebSocketError(Throwable cause) { cause.printStackTrace(System.err); } - @OnWebSocketFrame - public void onWebSocketFrame(Frame frame) + @OnWebSocketClose + public void onWebSocketClose(int statusCode, String reason) { - frameEvents.offer(String.format("FRAME[%s,fin=%b,payload=%s,len=%d]", - OpCode.name(frame.getOpCode()), - frame.isFin(), - BufferUtil.toUTF8String(frame.getPayload()), - frame.getPayloadLength())); + closeLatch.countDown(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java index f124f5a5bd3..682f2c9dcd0 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/FrameListenerTest.java @@ -24,10 +24,9 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Frame; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketFrameListener; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -112,10 +111,9 @@ public class FrameListenerTest { session = futSession.get(5, SECONDS); - RemoteEndpoint clientRemote = session.getRemote(); - clientRemote.sendPartialString("hello", false); - clientRemote.sendPartialString(" ", false); - clientRemote.sendPartialString("world", true); + session.sendPartialText("hello", false, Callback.NOOP); + session.sendPartialText(" ", false, Callback.NOOP); + session.sendPartialText("world", true, Callback.NOOP); String event = serverEndpoint.frameEvents.poll(5, SECONDS); assertThat("Event", event, is("FRAME[TEXT,fin=false,payload=hello,len=5]")); @@ -130,22 +128,29 @@ public class FrameListenerTest } } - public static class FrameEndpoint implements WebSocketFrameListener + public static class FrameEndpoint implements Session.Listener { public Session session; public CountDownLatch closeLatch = new CountDownLatch(1); public LinkedBlockingQueue frameEvents = new LinkedBlockingQueue<>(); @Override - public void onWebSocketClose(int statusCode, String reason) + public void onWebSocketOpen(Session session) { - closeLatch.countDown(); + this.session = session; + session.demand(); } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketFrame(Frame frame, Callback callback) { - this.session = session; + frameEvents.offer(String.format("FRAME[%s,fin=%b,payload=%s,len=%d]", + OpCode.name(frame.getOpCode()), + frame.isFin(), + BufferUtil.toUTF8String(frame.getPayload()), + frame.getPayloadLength())); + callback.succeed(); + session.demand(); } @Override @@ -155,13 +160,9 @@ public class FrameListenerTest } @Override - public void onWebSocketFrame(Frame frame) + public void onWebSocketClose(int statusCode, String reason) { - frameEvents.offer(String.format("FRAME[%s,fin=%b,payload=%s,len=%d]", - OpCode.name(frame.getOpCode()), - frame.isFin(), - BufferUtil.toUTF8String(frame.getPayload()), - frame.getPayloadLength())); + closeLatch.countDown(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java index 629be35e894..9fc6d6f589b 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/PartialListenerTest.java @@ -25,9 +25,8 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -112,10 +111,9 @@ public class PartialListenerTest { session = futSession.get(5, SECONDS); - RemoteEndpoint clientRemote = session.getRemote(); - clientRemote.sendPartialString("hello", false); - clientRemote.sendPartialString(" ", false); - clientRemote.sendPartialString("world", true); + session.sendPartialText("hello", false, Callback.NOOP); + session.sendPartialText(" ", false, Callback.NOOP); + session.sendPartialText("world", true, Callback.NOOP); String event = serverEndpoint.partialEvents.poll(5, SECONDS); assertThat("Event", event, is("TEXT[payload=hello, fin=false]")); @@ -144,10 +142,9 @@ public class PartialListenerTest { session = futSession.get(5, SECONDS); - RemoteEndpoint clientRemote = session.getRemote(); - clientRemote.sendPartialBytes(BufferUtil.toBuffer("hello"), false); - clientRemote.sendPartialBytes(BufferUtil.toBuffer(" "), false); - clientRemote.sendPartialBytes(BufferUtil.toBuffer("world"), true); + session.sendPartialBinary(BufferUtil.toBuffer("hello"), false, Callback.NOOP); + session.sendPartialBinary(BufferUtil.toBuffer(" "), false, Callback.NOOP); + session.sendPartialBinary(BufferUtil.toBuffer("world"), true, Callback.NOOP); String event = serverEndpoint.partialEvents.poll(5, SECONDS); assertThat("Event", event, is("BINARY[payload=<<>>, fin=false]")); @@ -179,18 +176,17 @@ public class PartialListenerTest { session = futSession.get(5, SECONDS); - RemoteEndpoint clientRemote = session.getRemote(); - clientRemote.sendPartialString("hello", false); - clientRemote.sendPartialString(" ", false); - clientRemote.sendPartialString("world", true); + session.sendPartialText("hello", false, Callback.NOOP); + session.sendPartialText(" ", false, Callback.NOOP); + session.sendPartialText("world", true, Callback.NOOP); - clientRemote.sendPartialBytes(BufferUtil.toBuffer("greetings"), false); - clientRemote.sendPartialBytes(BufferUtil.toBuffer(" "), false); - clientRemote.sendPartialBytes(BufferUtil.toBuffer("mars"), true); + session.sendPartialBinary(BufferUtil.toBuffer("greetings"), false, Callback.NOOP); + session.sendPartialBinary(BufferUtil.toBuffer(" "), false, Callback.NOOP); + session.sendPartialBinary(BufferUtil.toBuffer("mars"), true, Callback.NOOP); - clientRemote.sendPartialString("salutations", false); - clientRemote.sendPartialString(" ", false); - clientRemote.sendPartialString("phobos", true); + session.sendPartialText("salutations", false, Callback.NOOP); + session.sendPartialText(" ", false, Callback.NOOP); + session.sendPartialText("phobos", true, Callback.NOOP); String event; event = serverEndpoint.partialEvents.poll(5, SECONDS); @@ -220,22 +216,33 @@ public class PartialListenerTest } } - public static class PartialEndpoint implements WebSocketPartialListener + public static class PartialEndpoint implements Session.Listener { public Session session; public CountDownLatch closeLatch = new CountDownLatch(1); public LinkedBlockingQueue partialEvents = new LinkedBlockingQueue<>(); @Override - public void onWebSocketClose(int statusCode, String reason) + public void onWebSocketOpen(Session session) { - closeLatch.countDown(); + this.session = session; + session.demand(); } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketPartialText(String payload, boolean fin) { - this.session = session; + partialEvents.offer(String.format("TEXT[payload=%s, fin=%b]", TextUtils.maxStringLength(30, payload), fin)); + session.demand(); + } + + @Override + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) + { + // our testcases always send bytes limited in the US-ASCII range. + partialEvents.offer(String.format("BINARY[payload=<<<%s>>>, fin=%b]", BufferUtil.toUTF8String(payload), fin)); + callback.succeed(); + session.demand(); } @Override @@ -245,16 +252,9 @@ public class PartialListenerTest } @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketClose(int statusCode, String reason) { - // our testcases always send bytes limited in the US-ASCII range. - partialEvents.offer(String.format("BINARY[payload=<<<%s>>>, fin=%b]", BufferUtil.toUTF8String(payload), fin)); - } - - @Override - public void onWebSocketPartialText(String payload, boolean fin) - { - partialEvents.offer(String.format("TEXT[payload=%s, fin=%b]", TextUtils.maxStringLength(30, payload), fin)); + closeLatch.countDown(); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java index e454be331a3..18f093a5ecd 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerCloseTest.java @@ -22,6 +22,7 @@ import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.util.WSURI; @@ -163,7 +164,7 @@ public class ServerCloseTest AbstractCloseEndpoint serverEndpoint = serverEndpointCreator.pollLastCreated(); serverEndpoint.assertReceivedCloseEvent(5000, is(StatusCode.SERVER_ERROR), containsString("Intentional FastFail")); - // Validate errors (must be "java.lang.RuntimeException: Intentional Exception from onWebSocketConnect") + // Validate errors (must be "java.lang.RuntimeException: Intentional Exception from onWebSocketOpen") assertThat("socket.onErrors", serverEndpoint.errors.size(), greaterThanOrEqualTo(1)); Throwable cause = serverEndpoint.errors.poll(5, SECONDS); assertThat("Error type", cause, instanceOf(RuntimeException.class)); @@ -235,7 +236,7 @@ public class ServerCloseTest { session = futSession.get(5, SECONDS); - session.getRemote().sendString("openSessions"); + session.sendText("openSessions", Callback.NOOP); String msg = clientEndpoint.messageQueue.poll(5, SECONDS); @@ -269,8 +270,9 @@ public class ServerCloseTest // Hard close from the server. Server onClosed() will try to close again which should be a NOOP. AbstractCloseEndpoint serverEndpoint = serverEndpointCreator.pollLastCreated(); - assertTrue(serverEndpoint.openLatch.await(5, SECONDS)); - serverEndpoint.getSession().close(StatusCode.SHUTDOWN, "SHUTDOWN hard close"); + assertTrue(serverEndpoint.connectLatch.await(5, SECONDS)); + Session session = serverEndpoint.getSession(); + session.close(StatusCode.SHUTDOWN, "SHUTDOWN hard close", Callback.NOOP); // Verify that client got close clientEndpoint.assertReceivedCloseEvent(5000, is(StatusCode.SHUTDOWN), containsString("SHUTDOWN hard close")); @@ -293,8 +295,9 @@ public class ServerCloseTest // Hard close from the server. Server onClosed() will try to close again which should be a NOOP. AbstractCloseEndpoint serverEndpoint = serverEndpointCreator.pollLastCreated(); - assertTrue(serverEndpoint.openLatch.await(5, SECONDS)); - serverEndpoint.getSession().close(StatusCode.SHUTDOWN, "SHUTDOWN hard close"); + assertTrue(serverEndpoint.connectLatch.await(5, SECONDS)); + Session session = serverEndpoint.getSession(); + session.close(StatusCode.SHUTDOWN, "SHUTDOWN hard close", Callback.NOOP); // Verify that client got close clientEndpoint.assertReceivedCloseEvent(5000, is(StatusCode.SHUTDOWN), containsString("SHUTDOWN hard close")); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java index 165ae27bba5..5fb63b6acc7 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/ServerConfigTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.websocket.tests.server; import java.net.URI; +import java.nio.ByteBuffer; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -28,7 +29,7 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.PathMappingsHandler; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -64,7 +65,6 @@ public class ServerConfigTest private static final int MAX_MESSAGE_SIZE = 20; private static final int IDLE_TIMEOUT = 500; - private final EventSocket annotatedEndpoint = new AnnotatedConfigEndpoint(); private final EventSocket sessionConfigEndpoint = new SessionConfigEndpoint(); private final EventSocket standardEndpoint = new EventSocket(); @@ -73,7 +73,6 @@ public class ServerConfigTest return switch (path) { case "servletConfig", "containerConfig" -> standardEndpoint; - case "annotatedConfig" -> annotatedEndpoint; case "sessionConfig" -> sessionConfigEndpoint; default -> throw new IllegalStateException(); }; @@ -81,12 +80,7 @@ public class ServerConfigTest public static Stream data() { - return Stream.of("servletConfig", "annotatedConfig", "containerConfig", "sessionConfig").map(Arguments::of); - } - - @WebSocket(idleTimeout = IDLE_TIMEOUT, maxTextMessageSize = MAX_MESSAGE_SIZE, maxBinaryMessageSize = MAX_MESSAGE_SIZE, inputBufferSize = INPUT_BUFFER_SIZE, batchMode = BatchMode.ON) - public static class AnnotatedConfigEndpoint extends EventSocket - { + return Stream.of("servletConfig", "containerConfig", "sessionConfig").map(Arguments::of); } @WebSocket @@ -119,16 +113,6 @@ public class ServerConfigTest } } - public static class AnnotatedConfigWebSocketUpgradeHandler - { - public static WebSocketUpgradeHandler from(Server server, ContextHandler context, Object wsEndPoint) - { - return WebSocketUpgradeHandler.from(server, context) - .configure(container -> - container.addMapping("/", (rq, rs, cb) -> wsEndPoint)); - } - } - public static class SessionConfigWebSocketUpgradeHandler { public static WebSocketUpgradeHandler from(Server server, ContextHandler context, Object wsEndPoint) @@ -178,7 +162,6 @@ public class ServerConfigTest PathMappingsHandler pathsHandler = new PathMappingsHandler(); context.setHandler(pathsHandler); pathsHandler.addMapping(new ServletPathSpec("/servletConfig"), ConfigWebSocketUpgradeHandler.from(server, context, standardEndpoint)); - pathsHandler.addMapping(new ServletPathSpec("/annotatedConfig"), AnnotatedConfigWebSocketUpgradeHandler.from(server, context, annotatedEndpoint)); pathsHandler.addMapping(new ServletPathSpec("/sessionConfig"), SessionConfigWebSocketUpgradeHandler.from(server, context, sessionConfigEndpoint)); pathsHandler.addMapping(new ServletPathSpec("/"), WebSocketUpgradeHandler.from(server, context) .configure(container -> @@ -241,7 +224,8 @@ public class ServerConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendBytes(BufferUtil.toBuffer(MESSAGE)); + ByteBuffer buffer = BufferUtil.toBuffer(MESSAGE); + clientEndpoint.session.sendBinary(buffer, Callback.NOOP); assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(serverEndpoint.error, instanceOf(MessageTooLargeException.class)); @@ -262,7 +246,7 @@ public class ServerConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendString("hello world"); + clientEndpoint.session.sendText("hello world", Callback.NOOP); String msg = serverEndpoint.textMessages.poll(500, TimeUnit.MILLISECONDS); assertThat(msg, is("hello world")); Thread.sleep(IDLE_TIMEOUT + 500); @@ -286,7 +270,7 @@ public class ServerConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendString(MESSAGE); + clientEndpoint.session.sendText(MESSAGE, Callback.NOOP); assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(serverEndpoint.error, instanceOf(MessageTooLargeException.class)); diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerEndpoint.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerEndpoint.java index b60bfcbafd7..47bda829ded 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerEndpoint.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerEndpoint.java @@ -13,11 +13,11 @@ package org.eclipse.jetty.websocket.tests.server; -import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -44,7 +44,7 @@ public class SlowServerEndpoint { try { - session.getRemote().sendString("Hello/" + i + "/"); + session.sendText("Hello/" + i + "/", Callback.NOOP); // fake some slowness TimeUnit.MILLISECONDS.sleep(random.nextInt(2000)); } @@ -58,14 +58,7 @@ public class SlowServerEndpoint else { // echo message. - try - { - session.getRemote().sendString(msg); - } - catch (IOException ignore) - { - LOG.trace("IGNORED", ignore); - } + session.sendText(msg, Callback.NOOP); } } } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java index 1df42ff23de..082f9a3b80e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/server/SlowServerTest.java @@ -21,6 +21,7 @@ import java.util.concurrent.LinkedBlockingQueue; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -96,7 +97,7 @@ public class SlowServerTest int messageCount = 10; - session.getRemote().sendString("send-slow|" + messageCount); + session.sendText("send-slow|" + messageCount, Callback.NOOP); // Verify receive LinkedBlockingQueue responses = clientEndpoint.messageQueue; diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureWriteCallback.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureCallback.java similarity index 73% rename from jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureWriteCallback.java rename to jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureCallback.java index 670ba0d98c3..27fbe4f5cde 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureWriteCallback.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/FutureCallback.java @@ -15,20 +15,19 @@ package org.eclipse.jetty.websocket.tests.util; import java.util.concurrent.Future; -import org.eclipse.jetty.util.FutureCallback; -import org.eclipse.jetty.websocket.api.WriteCallback; +import org.eclipse.jetty.websocket.api.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Allows events to a {@link WriteCallback} to drive a {@link Future} for the user. + * Allows events to a {@link Callback} to drive a {@link Future} for the user. */ -public class FutureWriteCallback extends FutureCallback implements WriteCallback +public class FutureCallback extends org.eclipse.jetty.util.FutureCallback implements Callback { - private static final Logger LOG = LoggerFactory.getLogger(FutureWriteCallback.class); + private static final Logger LOG = LoggerFactory.getLogger(FutureCallback.class); @Override - public void writeFailed(Throwable cause) + public void fail(Throwable cause) { if (LOG.isDebugEnabled()) LOG.debug(".writeFailed", cause); @@ -36,7 +35,7 @@ public class FutureWriteCallback extends FutureCallback implements WriteCallback } @Override - public void writeSuccess() + public void succeed() { if (LOG.isDebugEnabled()) LOG.debug(".writeSuccess"); diff --git a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/main/java/org/eclipse/jetty/ee10/demos/WebSocketServer.java b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/main/java/org/eclipse/jetty/ee10/demos/WebSocketServer.java index 30a1aec38ca..f0d5e429520 100644 --- a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/main/java/org/eclipse/jetty/ee10/demos/WebSocketServer.java +++ b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/main/java/org/eclipse/jetty/ee10/demos/WebSocketServer.java @@ -18,8 +18,8 @@ import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory; import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -39,7 +39,7 @@ public class WebSocketServer @OnWebSocketMessage public void onMessage(Session session, String message) { - session.getRemote().sendString(message, WriteCallback.NOOP); + session.sendText(message, Callback.NOOP); } } diff --git a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/test/java/org/eclipse/jetty/ee10/demos/WebSocketServerTest.java b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/test/java/org/eclipse/jetty/ee10/demos/WebSocketServerTest.java index 3749a491961..c15db6da22f 100644 --- a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/test/java/org/eclipse/jetty/ee10/demos/WebSocketServerTest.java +++ b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-embedded/src/test/java/org/eclipse/jetty/ee10/demos/WebSocketServerTest.java @@ -20,6 +20,7 @@ import java.util.concurrent.LinkedBlockingQueue; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; @@ -68,7 +69,7 @@ public class WebSocketServerTest Future sessionFut = webSocketClient.connect(clientEndpoint, wsUri); Session session = sessionFut.get(2, SECONDS); - session.getRemote().sendString("Hello World"); + session.sendText("Hello World", Callback.NOOP); String response = clientEndpoint.messages.poll(2, SECONDS); assertThat("Response", response, is("Hello World")); diff --git a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/java/org/example/WebSocketChatServlet.java b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/java/org/example/WebSocketChatServlet.java index 41436dcf687..b5b43620c6b 100644 --- a/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/java/org/example/WebSocketChatServlet.java +++ b/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/java/org/example/WebSocketChatServlet.java @@ -26,11 +26,10 @@ import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeResponse; import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketCreator; import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @SuppressWarnings("serial") @@ -71,13 +70,11 @@ public class WebSocketChatServlet extends JettyWebSocketServlet implements Jetty public class ChatWebSocket { volatile Session session; - volatile RemoteEndpoint remote; - @OnWebSocketConnect - public void onOpen(Session sess) + @OnWebSocketOpen + public void onOpen(Session session) { - this.session = sess; - this.remote = sess.getRemote(); + this.session = session; members.add(this); } @@ -103,7 +100,7 @@ public class WebSocketChatServlet extends JettyWebSocketServlet implements Jetty } // Async write the message back. - member.remote.sendString(data, null); + member.session.sendText(data, null); } } diff --git a/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/SimpleEchoSocket.java b/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/SimpleEchoSocket.java index a230af84381..5090a98dcb0 100644 --- a/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/SimpleEchoSocket.java +++ b/jetty-ee10/jetty-ee10-osgi/test-jetty-ee10-osgi/src/test/java/org/eclipse/jetty/ee10/osgi/test/SimpleEchoSocket.java @@ -16,22 +16,21 @@ package org.eclipse.jetty.ee10.osgi.test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Basic Echo Client Socket */ -@WebSocket(maxTextMessageSize = 64 * 1024) +@WebSocket public class SimpleEchoSocket { private final CountDownLatch closeLatch; - @SuppressWarnings("unused") - private Session session; public SimpleEchoSocket() { @@ -46,18 +45,17 @@ public class SimpleEchoSocket @OnWebSocketClose public void onClose(int statusCode, String reason) { - this.session = null; this.closeLatch.countDown(); // trigger latch } - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { - this.session = session; + session.setMaxTextMessageSize(64 * 1024); try { - session.getRemote().sendString("Foo"); - session.close(StatusCode.NORMAL, "I'm done"); + session.sendText("Foo", Callback.NOOP); + session.close(StatusCode.NORMAL, "I'm done", Callback.NOOP); } catch (Throwable t) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java index 86127afa9d4..a6f0f6c0816 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.ee10.servlet; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.file.InvalidPathException; import java.time.Duration; @@ -188,9 +189,9 @@ public class DefaultServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(DefaultServlet.class); + private ServletContextHandler _contextHandler; private ServletResourceService _resourceService; private WelcomeServletMode _welcomeServletMode; - private ResourceFactory.Closeable _resourceFactory; private Resource _baseResource; private boolean _isPathInfoOnly; @@ -202,19 +203,17 @@ public class DefaultServlet extends HttpServlet @Override public void init() throws ServletException { - ServletContextHandler servletContextHandler = initContextHandler(getServletContext()); - _resourceService = new ServletResourceService(servletContextHandler); + _contextHandler = initContextHandler(getServletContext()); + _resourceService = new ServletResourceService(_contextHandler); _resourceService.setWelcomeFactory(_resourceService); - - _baseResource = servletContextHandler.getBaseResource(); - _resourceFactory = ResourceFactory.closeable(); + _baseResource = _contextHandler.getBaseResource(); String rb = getInitParameter("baseResource", "resourceBase"); if (rb != null) { try { - _baseResource = _resourceFactory.newResource(rb); + _baseResource = Objects.requireNonNull(_contextHandler.newResource(rb)); } catch (Exception e) { @@ -230,17 +229,19 @@ public class DefaultServlet extends HttpServlet HttpContent.Factory contentFactory = (HttpContent.Factory)getServletContext().getAttribute(HttpContent.Factory.class.getName()); if (contentFactory == null) { - MimeTypes mimeTypes = servletContextHandler.getMimeTypes(); - contentFactory = new ResourceHttpContentFactory(ResourceFactory.of(_baseResource), mimeTypes); + MimeTypes mimeTypes = _contextHandler.getMimeTypes(); + ResourceFactory resourceFactory = _baseResource != null ? ResourceFactory.of(_baseResource) : this::getResource; + contentFactory = new ResourceHttpContentFactory(resourceFactory, mimeTypes); // Use the servers default stylesheet unless there is one explicitly set by an init param. - Resource styleSheet = servletContextHandler.getServer().getDefaultStyleSheet(); + Resource styleSheet = _contextHandler.getServer().getDefaultStyleSheet(); String stylesheetParam = getInitParameter("stylesheet"); if (stylesheetParam != null) { try { - Resource s = _resourceFactory.newResource(stylesheetParam); + HttpContent styleSheetContent = contentFactory.getContent(stylesheetParam); + Resource s = styleSheetContent == null ? null : styleSheetContent.getResource(); if (Resources.isReadableFile(s)) styleSheet = s; else @@ -267,7 +268,7 @@ public class DefaultServlet extends HttpServlet long cacheValidationTime = getInitParameter("cacheValidationTime") != null ? Long.parseLong(getInitParameter("cacheValidationTime")) : -2; if (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2 || cacheValidationTime != -2) { - ByteBufferPool bufferPool = getByteBufferPool(servletContextHandler); + ByteBufferPool bufferPool = getByteBufferPool(_contextHandler); ValidatingCachingHttpContentFactory cached = new ValidatingCachingHttpContentFactory(contentFactory, (cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool); contentFactory = cached; @@ -281,8 +282,8 @@ public class DefaultServlet extends HttpServlet } _resourceService.setHttpContentFactory(contentFactory); - if (servletContextHandler.getWelcomeFiles() == null) - servletContextHandler.setWelcomeFiles(new String[]{"index.html", "index.jsp"}); + if (_contextHandler.getWelcomeFiles() == null) + _contextHandler.setWelcomeFiles(new String[]{"index.html", "index.jsp"}); _resourceService.setAcceptRanges(getInitBoolean("acceptRanges", _resourceService.isAcceptRanges())); _resourceService.setDirAllowed(getInitBoolean("dirAllowed", _resourceService.isDirAllowed())); @@ -336,7 +337,6 @@ public class DefaultServlet extends HttpServlet if (LOG.isDebugEnabled()) { LOG.debug(" .baseResource = {}", _baseResource); - LOG.debug(" .resourceFactory = {}", _resourceFactory); LOG.debug(" .resourceService = {}", _resourceService); LOG.debug(" .isPathInfoOnly = {}", _isPathInfoOnly); LOG.debug(" .welcomeServletMode = {}", _welcomeServletMode); @@ -372,13 +372,6 @@ public class DefaultServlet extends HttpServlet return null; } - @Override - public void destroy() - { - super.destroy(); - IO.close(_resourceFactory); - } - private List parsePrecompressedFormats(String precompressed, Boolean gzip, List dft) { if (precompressed == null && gzip == null) @@ -554,6 +547,23 @@ public class DefaultServlet extends HttpServlet doGet(req, resp); } + private Resource getResource(URI uri) + { + String uriPath = uri.getRawPath(); + Resource result = null; + try + { + result = _contextHandler.getResource(uriPath); + } + catch (IOException x) + { + LOG.trace("IGNORED", x); + } + if (LOG.isDebugEnabled()) + LOG.debug("Resource {}={}", uriPath, result); + return result; + } + private static class ServletCoreRequest extends Request.Wrapper { // TODO fully implement this class and move it to the top level diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-cdi/src/test/java/org/eclipse/jetty/ee10/cdi/tests/websocket/JettyWebSocketCdiTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-cdi/src/test/java/org/eclipse/jetty/ee10/cdi/tests/websocket/JettyWebSocketCdiTest.java index ca24b2e423c..a283cf7ad06 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-cdi/src/test/java/org/eclipse/jetty/ee10/cdi/tests/websocket/JettyWebSocketCdiTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-cdi/src/test/java/org/eclipse/jetty/ee10/cdi/tests/websocket/JettyWebSocketCdiTest.java @@ -27,11 +27,12 @@ import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletConta import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.jupiter.api.AfterEach; @@ -112,7 +113,7 @@ public class JettyWebSocketCdiTest TestClientEndpoint clientEndpoint = new TestClientEndpoint(); URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo"); Session session = _client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS); - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world")); session.close(); assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS)); @@ -126,7 +127,7 @@ public class JettyWebSocketCdiTest private Session session; - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { logger.info("onOpen() session:" + session); @@ -136,7 +137,7 @@ public class JettyWebSocketCdiTest @OnWebSocketMessage public void onMessage(String message) throws IOException { - this.session.getRemote().sendString(message); + this.session.sendText(message, Callback.NOOP); } @OnWebSocketError diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/websocket/JettySimpleEchoSocket.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/websocket/JettySimpleEchoSocket.java index f2aa9387a8f..7639a677e14 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/websocket/JettySimpleEchoSocket.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/websocket/JettySimpleEchoSocket.java @@ -16,11 +16,12 @@ package org.eclipse.jetty.ee10.test.websocket; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +29,7 @@ import org.slf4j.LoggerFactory; /** * Basic Echo Client Socket */ -@WebSocket(maxTextMessageSize = 64 * 1024) +@WebSocket public class JettySimpleEchoSocket { private static final Logger LOG = LoggerFactory.getLogger(JettySimpleEchoSocket.class); @@ -54,15 +55,16 @@ public class JettySimpleEchoSocket this.closeLatch.countDown(); // trigger latch } - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { - LOG.debug("Got connect: {}", session); + LOG.debug("Open: {}", session); this.session = session; + session.setMaxTextMessageSize(64 * 1024); try { - session.getRemote().sendString("Foo"); - session.close(StatusCode.NORMAL, "I'm done"); + session.sendText("Foo", Callback.NOOP); + session.close(StatusCode.NORMAL, "I'm done", Callback.NOOP); } catch (Throwable t) { diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java index 8fc6f60ce11..c1422eb4879 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-provided-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java @@ -25,10 +25,11 @@ import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -78,7 +79,7 @@ public class WebSocketClientServlet extends HttpServlet ClientSocket clientSocket = new ClientSocket(); URI wsUri = WSURI.toWebsocket(req.getRequestURL()).resolve("echo"); client.connect(clientSocket, wsUri).get(5, TimeUnit.SECONDS); - clientSocket.session.getRemote().sendString("test message"); + clientSocket.session.sendText("test message", Callback.NOOP); String response = clientSocket.textMessages.poll(5, TimeUnit.SECONDS); clientSocket.session.close(); clientSocket.closeLatch.await(5, TimeUnit.SECONDS); @@ -106,7 +107,7 @@ public class WebSocketClientServlet extends HttpServlet public CountDownLatch closeLatch = new CountDownLatch(1); public ArrayBlockingQueue textMessages = new ArrayBlockingQueue<>(10); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { this.session = session; diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java index ab9d3a42006..2296e524372 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-websocket-client-webapp/src/main/java/org/eclipse/jetty/ee10/tests/webapp/websocket/WebSocketClientServlet.java @@ -28,10 +28,11 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; import org.eclipse.jetty.io.ClientConnector; 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.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.util.WSURI; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -86,7 +87,7 @@ public class WebSocketClientServlet extends HttpServlet ClientSocket clientSocket = new ClientSocket(); URI wsUri = WSURI.toWebsocket(req.getRequestURL()).resolve("echo"); client.connect(clientSocket, wsUri).get(5, TimeUnit.SECONDS); - clientSocket.session.getRemote().sendString("test message"); + clientSocket.session.sendText("test message", Callback.NOOP); String response = clientSocket.textMessages.poll(5, TimeUnit.SECONDS); clientSocket.session.close(); clientSocket.closeLatch.await(5, TimeUnit.SECONDS); @@ -110,7 +111,7 @@ public class WebSocketClientServlet extends HttpServlet public CountDownLatch closeLatch = new CountDownLatch(1); public ArrayBlockingQueue textMessages = new ArrayBlockingQueue<>(10); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { this.session = session; diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/HugeResourceTest.java b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/HugeResourceTest.java index 6f894aefc22..fda0e4dd151 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/HugeResourceTest.java +++ b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/HugeResourceTest.java @@ -509,7 +509,7 @@ public class HugeResourceTest Thread.sleep(100); stalled.set(false); demand.get().run(); - assertTrue(complete.await(30, TimeUnit.SECONDS)); + assertTrue(complete.await(90, TimeUnit.SECONDS)); Response response = responseRef.get(); assertThat("HTTP Response Code", response.getStatus(), is(200)); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/JakartaWebSocketClientContainer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/JakartaWebSocketClientContainer.java index 5e17f9311a9..e9773e4a915 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/JakartaWebSocketClientContainer.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/JakartaWebSocketClientContainer.java @@ -334,7 +334,7 @@ public class JakartaWebSocketClientContainer extends JakartaWebSocketContainer i { container.addManaged(this); if (LOG.isDebugEnabled()) - LOG.debug("{} registered for Context shutdown to {}", this, container); + LOG.debug("{} registered for WebApp shutdown to {}", this, container); return; } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/pom.xml b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/pom.xml index 0acf34f1d0d..08990ec01a1 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/pom.xml +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/pom.xml @@ -56,10 +56,16 @@ org.slf4j slf4j-api + org.eclipse.jetty jetty-slf4j-impl test + + org.awaitility + awaitility + test + diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandler.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandler.java index 6893eb98b28..8527b3a6951 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandler.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandler.java @@ -322,12 +322,6 @@ public class JakartaWebSocketFrameHandler implements FrameHandler } } - @Override - public boolean isAutoDemanding() - { - return false; - } - public Set getMessageHandlers() { return messageHandlerMap.values().stream() @@ -600,6 +594,11 @@ public class JakartaWebSocketFrameHandler implements FrameHandler { callback.succeeded(); coreSession.demand(1); + }, x -> + { + // Ignore failures, as we might be OSHUT but receive a PING. + callback.succeeded(); + coreSession.demand(1); }), false); } @@ -616,14 +615,19 @@ public class JakartaWebSocketFrameHandler implements FrameHandler // Use JSR356 PongMessage interface JakartaWebSocketPongMessage pongMessage = new JakartaWebSocketPongMessage(payload); pongHandle.invoke(pongMessage); + callback.succeeded(); + coreSession.demand(1); } catch (Throwable cause) { - throw new WebSocketException(endpointInstance.getClass().getSimpleName() + " PONG method error: " + cause.getMessage(), cause); + callback.failed(new WebSocketException(endpointInstance.getClass().getSimpleName() + " PONG method error: " + cause.getMessage(), cause)); } } - callback.succeeded(); - coreSession.demand(1); + else + { + callback.succeeded(); + coreSession.demand(1); + } } public void onText(Frame frame, Callback callback) @@ -646,14 +650,9 @@ public class JakartaWebSocketFrameHandler implements FrameHandler { switch (dataType) { - case OpCode.TEXT: - onText(frame, callback); - break; - case OpCode.BINARY: - onBinary(frame, callback); - break; - default: - throw new ProtocolException("Unable to process continuation during dataType " + dataType); + case OpCode.TEXT -> onText(frame, callback); + case OpCode.BINARY -> onBinary(frame, callback); + default -> callback.failed(new ProtocolException("Unable to process continuation during dataType " + dataType)); } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java index 3d0174cb590..dd90ab38a13 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java @@ -197,8 +197,8 @@ public abstract class JakartaWebSocketFrameHandlerFactory else { MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(), - MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); - return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle()); + MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, boolean.class)); + return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle(), true); } } catch (NoSuchMethodException e) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java index aa18950a2f0..d3715baf894 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java @@ -45,7 +45,7 @@ public class DecodedBinaryMessageSink extends AbstractDecodedMessageSink.Basi MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(DecodedBinaryMessageSink.class, "onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class)) .bindTo(this); - return new ByteBufferMessageSink(coreSession, methodHandle); + return new ByteBufferMessageSink(coreSession, methodHandle, true); } public void onWholeMessage(ByteBuffer wholeMessage) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java index 5de362c1e96..6e7b34c3ce0 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java @@ -42,7 +42,7 @@ public class DecodedBinaryStreamMessageSink extends AbstractDecodedMessageSin MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(DecodedBinaryStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, InputStream.class)) .bindTo(this); - return new InputStreamMessageSink(coreSession, methodHandle); + return new InputStreamMessageSink(coreSession, methodHandle, true); } public void onStreamStart(InputStream stream) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextMessageSink.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextMessageSink.java index 1f480a0f856..6b9f98b6b13 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextMessageSink.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextMessageSink.java @@ -44,7 +44,7 @@ public class DecodedTextMessageSink extends AbstractDecodedMessageSink.Basic< MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(getClass(), "onMessage", MethodType.methodType(void.class, String.class)) .bindTo(this); - return new StringMessageSink(coreSession, methodHandle); + return new StringMessageSink(coreSession, methodHandle, true); } public void onMessage(String wholeMessage) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java index 36c901988ee..59bed6e8b85 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java @@ -42,7 +42,7 @@ public class DecodedTextStreamMessageSink extends AbstractDecodedMessageSink. MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(DecodedTextStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, Reader.class)) .bindTo(this); - return new ReaderMessageSink(coreSession, methodHandle); + return new ReaderMessageSink(coreSession, methodHandle, true); } public void onStreamStart(Reader reader) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/AbstractSessionTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/AbstractSessionTest.java index f8a26ac940a..d759362e836 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/AbstractSessionTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/AbstractSessionTest.java @@ -22,20 +22,20 @@ import jakarta.websocket.Session; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.WebSocketComponents; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.assertTrue; public abstract class AbstractSessionTest { - protected static JakartaWebSocketSession session; - protected static JakartaWebSocketContainer container = new DummyContainer(); - protected static WebSocketComponents components = new WebSocketComponents(); - protected static TestCoreSession coreSession = new TestCoreSession(); + protected JakartaWebSocketContainer container = new DummyContainer(); + protected WebSocketComponents components = new WebSocketComponents(); + protected TestCoreSession coreSession = new TestCoreSession(components); + protected JakartaWebSocketSession session; - @BeforeAll - public static void initSession() throws Exception + @BeforeEach + public void initSession() throws Exception { container.start(); components.start(); @@ -46,8 +46,8 @@ public abstract class AbstractSessionTest .newDefaultEndpointConfig(websocketPojo.getClass())); } - @AfterAll - public static void stopContainer() throws Exception + @AfterEach + public void stopContainer() throws Exception { components.stop(); container.stop(); @@ -56,6 +56,12 @@ public abstract class AbstractSessionTest public static class TestCoreSession extends CoreSession.Empty { private final Semaphore demand = new Semaphore(0); + private final WebSocketComponents components; + + public TestCoreSession(WebSocketComponents components) + { + this.components = components; + } @Override public WebSocketComponents getWebSocketComponents() diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java index f399c870f60..4e21e76d317 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java @@ -26,7 +26,6 @@ import java.util.function.Consumer; import jakarta.websocket.DecodeException; import jakarta.websocket.Decoder; import jakarta.websocket.EndpointConfig; -import org.eclipse.jetty.ee10.websocket.jakarta.common.AbstractSessionTest; import org.eclipse.jetty.ee10.websocket.jakarta.common.decoders.RegisteredDecoder; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.websocket.core.Frame; @@ -47,7 +46,7 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture); MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class); List decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class); - DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink<>(AbstractSessionTest.session.getCoreSession(), copyHandle, decoders); + DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink<>(session.getCoreSession(), copyHandle, decoders); FutureCallback finCallback = new FutureCallback(); ByteBuffer data = ByteBuffer.allocate(16); @@ -70,7 +69,7 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture); MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class); List decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class); - DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink<>(AbstractSessionTest.session.getCoreSession(), copyHandle, decoders); + DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink<>(session.getCoreSession(), copyHandle, decoders); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java index 9a20624855f..b91f0e28702 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; -import org.eclipse.jetty.ee10.websocket.jakarta.common.AbstractSessionTest; import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FutureCallback; @@ -36,6 +35,7 @@ import org.eclipse.jetty.websocket.core.messages.InputStreamMessageSink; import org.junit.jupiter.api.Test; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -46,7 +46,7 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback finCallback = new FutureCallback(); ByteBuffer data = BufferUtil.toBuffer("Hello World", UTF_8); @@ -64,7 +64,7 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback fin1Callback = new FutureCallback(); ByteBuffer data1 = BufferUtil.toBuffer("Hello World", UTF_8); @@ -77,6 +77,8 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest assertThat("Writer.contents", byteStream.toString(UTF_8), is("Hello World")); assertThat("FinCallback.done", fin1Callback.isDone(), is(true)); + await().atMost(1, TimeUnit.SECONDS).until(() -> !sink.isDispatched()); + FutureCallback fin2Callback = new FutureCallback(); ByteBuffer data2 = BufferUtil.toBuffer("Greetings Earthling", UTF_8); sink.accept(new Frame(OpCode.BINARY).setPayload(data2).setFin(true), fin2Callback); @@ -93,7 +95,7 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); @@ -118,7 +120,7 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/ReaderMessageSinkTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/ReaderMessageSinkTest.java index 57c2f9c1239..af3264e8d91 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/ReaderMessageSinkTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/common/messages/ReaderMessageSinkTest.java @@ -41,7 +41,7 @@ public class ReaderMessageSinkTest extends AbstractMessageSinkTest CompletableFuture copyFuture = new CompletableFuture<>(); ReaderCopy copy = new ReaderCopy(copyFuture); MethodHandle copyHandle = getAcceptHandle(copy, Reader.class); - ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle); + ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("Hello World"), finCallback); @@ -58,7 +58,7 @@ public class ReaderMessageSinkTest extends AbstractMessageSinkTest CompletableFuture copyFuture = new CompletableFuture<>(); ReaderCopy copy = new ReaderCopy(copyFuture); MethodHandle copyHandle = getAcceptHandle(copy, Reader.class); - ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle); + ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/EventSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/EventSocket.java index 8568d8c0d24..b91a1d654fb 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/EventSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/EventSocket.java @@ -55,7 +55,7 @@ public class EventSocket this.session = session; this.endpointConfig = endpointConfig; if (LOG.isDebugEnabled()) - LOG.debug("{} onOpen(): {}", toString(), session); + LOG.debug("{} onOpen(): {}", this, session); openLatch.countDown(); } @@ -63,7 +63,7 @@ public class EventSocket public void onMessage(String message) throws IOException { if (LOG.isDebugEnabled()) - LOG.debug("{} onMessage(): {}", toString(), message); + LOG.debug("{} onMessage(): {}", this, message); textMessages.offer(message); } @@ -71,7 +71,7 @@ public class EventSocket public void onMessage(ByteBuffer message) throws IOException { if (LOG.isDebugEnabled()) - LOG.debug("{} onMessage(): {}", toString(), message); + LOG.debug("{} onMessage(): {}", this, message); binaryMessages.offer(message); } @@ -79,8 +79,7 @@ public class EventSocket public void onClose(CloseReason reason) { if (LOG.isDebugEnabled()) - LOG.debug("{} onClose(): {}", toString(), reason); - + LOG.debug("{} onClose(): {}", this, reason); closeReason = reason; closeLatch.countDown(); } @@ -89,7 +88,7 @@ public class EventSocket public void onError(Throwable cause) { if (LOG.isDebugEnabled()) - LOG.debug("{} onError(): {}", toString(), cause); + LOG.debug("{} onError()", this, cause); error = cause; errorLatch.countDown(); } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/NetworkFuzzer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/NetworkFuzzer.java index b93d8ffcf06..bb127c34cbc 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/NetworkFuzzer.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/NetworkFuzzer.java @@ -229,6 +229,7 @@ public class NetworkFuzzer extends Fuzzer.Adapter implements Fuzzer, AutoCloseab this.coreSession = coreSession; this.openLatch.countDown(); callback.succeeded(); + coreSession.demand(1); } @Override @@ -236,6 +237,7 @@ public class NetworkFuzzer extends Fuzzer.Adapter implements Fuzzer, AutoCloseab { receivedFrames.offer(Frame.copy(frame)); callback.succeeded(); + coreSession.demand(1); } @Override diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/framehandlers/FrameEcho.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/framehandlers/FrameEcho.java index 58d838fbf02..0a8de6276e2 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/framehandlers/FrameEcho.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/framehandlers/FrameEcho.java @@ -32,6 +32,7 @@ public class FrameEcho implements FrameHandler { this.coreSession = coreSession; callback.succeeded(); + coreSession.demand(1); } @Override diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/CloseInOnOpenTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/CloseInOnOpenTest.java index 08262a4c35c..eb8a4ed5348 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/CloseInOnOpenTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/CloseInOnOpenTest.java @@ -73,7 +73,7 @@ public class CloseInOnOpenTest } @Test - public void testCloseInOnWebSocketConnect() throws Exception + public void testCloseInOnWebSocketOpen() throws Exception { URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws"); EventSocket clientEndpoint = new EventSocket(); @@ -89,7 +89,7 @@ public class CloseInOnOpenTest public static class ClosingListener { @OnOpen - public void onWebSocketConnect(Session session) throws Exception + public void onWebSocketOpen(Session session) throws Exception { session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "I am a WS that closes immediately")); } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/JakartaClientShutdownWithServerWebAppTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/JakartaClientShutdownWithServerWebAppTest.java index 03d7436a576..0a9df6bd856 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/JakartaClientShutdownWithServerWebAppTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/JakartaClientShutdownWithServerWebAppTest.java @@ -38,11 +38,13 @@ import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +@Isolated public class JakartaClientShutdownWithServerWebAppTest { private WSServer server; @@ -175,14 +177,15 @@ public class JakartaClientShutdownWithServerWebAppTest assertThat(response.getStatus(), is(HttpStatus.OK_200)); // Collect the toString result of the ShutdownContainers from the dump. - List results = Arrays.stream(server.getServer().dump().split("\n")) + String dump = server.getServer().dump(); + List results = Arrays.stream(dump.split("\n")) .filter(line -> line.contains("+> " + JakartaWebSocketShutdownContainer.class.getSimpleName())).toList(); // We only have 3 Shutdown Containers and they all contain only 1 item to be shutdown. - assertThat(results.size(), is(3)); + assertThat(dump, results.size(), is(3)); for (String result : results) { - assertThat(result, containsString("size=1")); + assertThat(dump, result, containsString("size=1")); } } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java index 6241d7986c6..f40c49749bf 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java @@ -37,7 +37,7 @@ public class JakartaAutobahnSocket public CountDownLatch closeLatch = new CountDownLatch(1); @OnOpen - public void onConnect(Session session) + public void onOpen(Session session) { this.session = session; session.setMaxTextMessageBufferSize(Integer.MAX_VALUE); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java index 2c0f1683f5c..90391e09030 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java @@ -37,10 +37,9 @@ import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.api.Configurable; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketContainer; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.WebSocketSessionListener; import org.eclipse.jetty.websocket.common.SessionTracker; import org.eclipse.jetty.websocket.core.Configuration; @@ -56,7 +55,7 @@ import org.eclipse.jetty.websocket.core.util.ReflectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class JettyWebSocketServerContainer extends ContainerLifeCycle implements WebSocketContainer, WebSocketPolicy, LifeCycle.Listener +public class JettyWebSocketServerContainer extends ContainerLifeCycle implements WebSocketContainer, Configurable, LifeCycle.Listener { public static final String JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE = WebSocketContainer.class.getName(); @@ -284,96 +283,102 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements return sessionTracker.getSessions(); } - @Override - public WebSocketBehavior getBehavior() - { - return WebSocketBehavior.SERVER; - } - @Override public Duration getIdleTimeout() { return customizer.getIdleTimeout(); } - @Override - public int getInputBufferSize() - { - return customizer.getInputBufferSize(); - } - - @Override - public int getOutputBufferSize() - { - return customizer.getOutputBufferSize(); - } - - @Override - public long getMaxBinaryMessageSize() - { - return customizer.getMaxBinaryMessageSize(); - } - - @Override - public long getMaxTextMessageSize() - { - return customizer.getMaxTextMessageSize(); - } - - @Override - public long getMaxFrameSize() - { - return customizer.getMaxFrameSize(); - } - - @Override - public boolean isAutoFragment() - { - return customizer.isAutoFragment(); - } - @Override public void setIdleTimeout(Duration duration) { customizer.setIdleTimeout(duration); } + @Override + public int getInputBufferSize() + { + return customizer.getInputBufferSize(); + } + @Override public void setInputBufferSize(int size) { customizer.setInputBufferSize(size); } + @Override + public int getOutputBufferSize() + { + return customizer.getOutputBufferSize(); + } + @Override public void setOutputBufferSize(int size) { customizer.setOutputBufferSize(size); } + @Override + public long getMaxBinaryMessageSize() + { + return customizer.getMaxBinaryMessageSize(); + } + @Override public void setMaxBinaryMessageSize(long size) { customizer.setMaxBinaryMessageSize(size); } + @Override + public long getMaxTextMessageSize() + { + return customizer.getMaxTextMessageSize(); + } + @Override public void setMaxTextMessageSize(long size) { customizer.setMaxTextMessageSize(size); } + @Override + public long getMaxFrameSize() + { + return customizer.getMaxFrameSize(); + } + @Override public void setMaxFrameSize(long maxFrameSize) { customizer.setMaxFrameSize(maxFrameSize); } + @Override + public boolean isAutoFragment() + { + return customizer.isAutoFragment(); + } + @Override public void setAutoFragment(boolean autoFragment) { customizer.setAutoFragment(autoFragment); } + @Override + public int getMaxOutgoingFrames() + { + return customizer.getMaxOutgoingFrames(); + } + + @Override + public void setMaxOutgoingFrames(int maxOutgoingFrames) + { + customizer.setMaxOutgoingFrames(maxOutgoingFrames); + } + @Override public void dump(Appendable out, String indent) throws IOException { diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServletFactory.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServletFactory.java index 5700481e3b4..39f5886ac34 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServletFactory.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServletFactory.java @@ -15,10 +15,9 @@ package org.eclipse.jetty.ee10.websocket.server; import java.util.Set; -import org.eclipse.jetty.websocket.api.WebSocketBehavior; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.Configurable; -public interface JettyWebSocketServletFactory extends WebSocketPolicy +public interface JettyWebSocketServletFactory extends Configurable { /** * Add a WebSocket mapping to a provided {@link JettyWebSocketCreator}. @@ -69,10 +68,4 @@ public interface JettyWebSocketServletFactory extends WebSocketPolicy * @return a set the available extension names. */ Set getAvailableExtensionNames(); - - @Override - default WebSocketBehavior getBehavior() - { - return WebSocketBehavior.SERVER; - } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/test/java/org/eclipse/jetty/ee10/websocket/server/browser/BrowserSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/test/java/org/eclipse/jetty/ee10/websocket/server/browser/BrowserSocket.java index d2260c4597e..63a34ae8a76 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/test/java/org/eclipse/jetty/ee10/websocket/server/browser/BrowserSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/test/java/org/eclipse/jetty/ee10/websocket/server/browser/BrowserSocket.java @@ -16,6 +16,7 @@ package org.eclipse.jetty.ee10.websocket.server.browser; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -28,12 +29,12 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.Dumpable; -import org.eclipse.jetty.websocket.api.RemoteEndpoint; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,13 +44,13 @@ public class BrowserSocket { private static class WriteMany implements Runnable { - private RemoteEndpoint remote; + private Session session; private int size; private int count; - public WriteMany(RemoteEndpoint remote, int size, int count) + public WriteMany(Session session, int size, int count) { - this.remote = remote; + this.session = session; this.size = size; this.count = count; } @@ -71,7 +72,7 @@ public class BrowserSocket randomText[i] = letters[rand.nextInt(lettersLen)]; } msg = String.format("ManyThreads [%s]", String.valueOf(randomText)); - remote.sendString(msg, null); + session.sendText(msg, null); } } } @@ -88,10 +89,10 @@ public class BrowserSocket this.requestedExtensions = reqExts; } - @OnWebSocketConnect - public void onConnect(Session session) + @OnWebSocketOpen + public void onOpen(Session session) { - LOG.info("Connect [{}]", session); + LOG.info("Open [{}]", session); this.session = session; } @@ -186,15 +187,9 @@ public class BrowserSocket } case "ping": { - try - { - LOG.info("PING!"); - this.session.getRemote().sendPing(BufferUtil.toBuffer("ping from server")); - } - catch (IOException e) - { - LOG.warn("Unable to send ping", e); - } + LOG.info("PING!"); + ByteBuffer b = BufferUtil.toBuffer("ping from server"); + this.session.sendPing(b, Callback.NOOP); break; } case "many": @@ -218,7 +213,7 @@ public class BrowserSocket // Setup threads for (int n = 0; n < threadCount; n++) { - threads[n] = new Thread(new WriteMany(session.getRemote(), size, count), "WriteMany[" + n + "]"); + threads[n] = new Thread(new WriteMany(session, size, count), "WriteMany[" + n + "]"); } // Execute threads @@ -289,7 +284,7 @@ public class BrowserSocket } // Async write - session.getRemote().sendString(message, null); + session.sendText(message, null); } private void writeMessage(String format, Object... args) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/CloseInOnOpenTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/CloseInOnOpenTest.java index e1810f05fd7..c8b810199c6 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/CloseInOnOpenTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/CloseInOnOpenTest.java @@ -21,9 +21,9 @@ import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -72,7 +72,7 @@ public class CloseInOnOpenTest } @Test - public void testCloseInOnWebSocketConnect() throws Exception + public void testCloseInOnWebSocketOpen() throws Exception { URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws"); EventSocket clientEndpoint = new EventSocket(); @@ -84,12 +84,12 @@ public class CloseInOnOpenTest assertThat(serverContainer.getOpenSessions().size(), is(0)); } - public static class ClosingListener implements WebSocketConnectionListener + public static class ClosingListener implements Session.Listener { @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { - session.close(StatusCode.POLICY_VIOLATION, "I am a WS that closes immediately"); + session.close(StatusCode.POLICY_VIOLATION, "I am a WS that closes immediately", Callback.NOOP); } } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EchoSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EchoSocket.java index a7f759c0e1c..d3eea015991 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EchoSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EchoSocket.java @@ -16,9 +16,9 @@ package org.eclipse.jetty.ee10.websocket.tests; import java.io.IOException; import java.nio.ByteBuffer; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -@SuppressWarnings("unused") @WebSocket public class EchoSocket extends EventSocket { @@ -26,13 +26,13 @@ public class EchoSocket extends EventSocket public void onMessage(String message) throws IOException { super.onMessage(message); - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } @Override - public void onMessage(byte[] buf, int offset, int len) throws IOException + public void onMessage(ByteBuffer message, Callback callback) { - super.onMessage(buf, offset, len); - session.getRemote().sendBytes(ByteBuffer.wrap(buf, offset, len)); + super.onMessage(message, Callback.NOOP); + session.sendBinary(message, callback); } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EventSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EventSocket.java index 866e9256417..9fa65cd008b 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EventSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/EventSocket.java @@ -19,12 +19,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +37,6 @@ public class EventSocket private static final Logger LOG = LoggerFactory.getLogger(EventSocket.class); public Session session; - private String behavior; public BlockingQueue textMessages = new BlockingArrayQueue<>(); public BlockingQueue binaryMessages = new BlockingArrayQueue<>(); @@ -47,11 +48,10 @@ public class EventSocket public CountDownLatch errorLatch = new CountDownLatch(1); public CountDownLatch closeLatch = new CountDownLatch(1); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { this.session = session; - behavior = session.getPolicy().getBehavior().name(); if (LOG.isDebugEnabled()) LOG.debug("{} onOpen(): {}", this, session); openLatch.countDown(); @@ -66,12 +66,12 @@ public class EventSocket } @OnWebSocketMessage - public void onMessage(byte[] buf, int offset, int len) throws IOException + public void onMessage(ByteBuffer message, Callback callback) { - ByteBuffer message = ByteBuffer.wrap(buf, offset, len); if (LOG.isDebugEnabled()) LOG.debug("{} onMessage(): {}", this, message); - binaryMessages.offer(message); + binaryMessages.offer(BufferUtil.copy(message)); + callback.succeed(); } @OnWebSocketClose @@ -88,7 +88,7 @@ public class EventSocket public void onError(Throwable cause) { if (LOG.isDebugEnabled()) - LOG.debug("{} onError(): {}", this, cause); + LOG.debug("{} onError()", this, cause); error = cause; errorLatch.countDown(); } @@ -96,6 +96,6 @@ public class EventSocket @Override public String toString() { - return String.format("[%s@%s]", behavior, Integer.toHexString(hashCode())); + return String.format("[%s@%x]", getClass().getSimpleName(), hashCode()); } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyClientClassLoaderTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyClientClassLoaderTest.java index bfd400a7b47..2b4cd4e104b 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyClientClassLoaderTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyClientClassLoaderTest.java @@ -34,10 +34,11 @@ import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.websocket.api.Callback; +import org.eclipse.jetty.websocket.api.Configurable; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.common.WebSocketSession; @@ -84,10 +85,10 @@ public class JettyClientClassLoaderTest { LinkedBlockingQueue textMessages = new LinkedBlockingQueue<>(); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) throws Exception { - session.getRemote().sendString("ContextClassLoader: " + Thread.currentThread().getContextClassLoader()); + session.sendText("ContextClassLoader: " + Thread.currentThread().getContextClassLoader(), Callback.NOOP); } @OnWebSocketMessage @@ -152,7 +153,7 @@ public class JettyClientClassLoaderTest @OnWebSocketMessage public void onMessage(Session session, String message) throws Exception { - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } } @@ -162,7 +163,7 @@ public class JettyClientClassLoaderTest // Copy over the individual jars required for Javax WebSocket. app.createWebInf(); - app.copyLib(WebSocketPolicy.class, "jetty-websocket-jetty-api.jar"); + app.copyLib(Configurable.class, "jetty-websocket-jetty-api.jar"); app.copyLib(WebSocketClient.class, "jetty-websocket-jetty-client.jar"); app.copyLib(WebSocketSession.class, "jetty-websocket-jetty-common.jar"); app.copyLib(ContainerLifeCycle.class, "jetty-util.jar"); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketFilterTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketFilterTest.java index 6a12637b51a..9f64616172c 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketFilterTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketFilterTest.java @@ -42,6 +42,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -124,7 +125,7 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); @@ -158,7 +159,7 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); @@ -195,7 +196,7 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); @@ -253,8 +254,8 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hElLo wOrLd"); - session.getRemote().sendString("getIdleTimeout"); + session.sendText("hElLo wOrLd", Callback.NOOP); + session.sendText("getIdleTimeout", Callback.NOOP); } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(socket.textMessages.poll(), is("hello world")); @@ -266,8 +267,8 @@ public class JettyWebSocketFilterTest connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hElLo wOrLd"); - session.getRemote().sendString("getIdleTimeout"); + session.sendText("hElLo wOrLd", Callback.NOOP); + session.sendText("getIdleTimeout", Callback.NOOP); } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(socket.textMessages.poll(), is("HELLO WORLD")); @@ -297,7 +298,7 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hElLo wOrLd"); + session.sendText("hElLo wOrLd", Callback.NOOP); } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(socket.textMessages.poll(), is("hElLo wOrLd")); @@ -329,8 +330,8 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); - session.getRemote().sendString("getIdleTimeout"); + session.sendText("hello world", Callback.NOOP); + session.sendText("getIdleTimeout", Callback.NOOP); } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(socket.textMessages.poll(), is("hello world")); @@ -363,8 +364,8 @@ public class JettyWebSocketFilterTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); - session.getRemote().sendString("getIdleTimeout"); + session.sendText("hello world", Callback.NOOP); + session.sendText("getIdleTimeout", Callback.NOOP); } assertTrue(socket.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(socket.textMessages.poll(), is("hello world")); @@ -388,9 +389,9 @@ public class JettyWebSocketFilterTest public void onMessage(Session session, String message) throws IOException { if ("getIdleTimeout".equals(message)) - session.getRemote().sendString(Long.toString(session.getIdleTimeout().toMillis())); + session.sendText(Long.toString(session.getIdleTimeout().toMillis()), Callback.NOOP); else - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } } } @@ -413,9 +414,9 @@ public class JettyWebSocketFilterTest public void onMessage(Session session, String message) throws IOException { if ("getIdleTimeout".equals(message)) - session.getRemote().sendString(Long.toString(session.getIdleTimeout().toMillis())); + session.sendText(Long.toString(session.getIdleTimeout().toMillis()), Callback.NOOP); else - session.getRemote().sendString(message.toLowerCase()); + session.sendText(message.toLowerCase(), Callback.NOOP); } } @@ -426,9 +427,9 @@ public class JettyWebSocketFilterTest public void onMessage(Session session, String message) throws IOException { if ("getIdleTimeout".equals(message)) - session.getRemote().sendString(Long.toString(session.getIdleTimeout().toMillis())); + session.sendText(Long.toString(session.getIdleTimeout().toMillis()), Callback.NOOP); else - session.getRemote().sendString(message.toUpperCase()); + session.sendText(message.toUpperCase(), Callback.NOOP); } } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketRestartTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketRestartTest.java index 6da5e7393c8..05bfdecffab 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketRestartTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketRestartTest.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletConta import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; @@ -112,7 +113,7 @@ public class JettyWebSocketRestartTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletAttributeTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletAttributeTest.java index a351742b8cd..1aa1f8589ac 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletAttributeTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletAttributeTest.java @@ -25,8 +25,8 @@ import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -118,7 +118,7 @@ public class JettyWebSocketServletAttributeTest EventSocket clientEndpoint = new EventSocket(); try (Session session = client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello", WriteCallback.NOOP); + session.sendText("hello", Callback.NOOP); String path = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); assertEquals(param, path); } @@ -137,7 +137,7 @@ public class JettyWebSocketServletAttributeTest @OnWebSocketMessage public void onText(Session session, String text) throws IOException { - session.getRemote().sendString(param); + session.sendText(param, Callback.NOOP); } } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletTest.java index c1802eb8169..988fe64e414 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/JettyWebSocketServletTest.java @@ -23,6 +23,7 @@ import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory; import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.jupiter.api.AfterEach; @@ -83,7 +84,7 @@ public class JettyWebSocketServletTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyBinaryEchoSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyBinaryEchoSocket.java index 40d8bcb864a..2bb9c620f9d 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyBinaryEchoSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyBinaryEchoSocket.java @@ -29,6 +29,6 @@ public class MyBinaryEchoSocket public void onWebSocketText(Session session, byte[] buf, int offset, int len) { // Echo message back, asynchronously - session.getRemote().sendBytes(ByteBuffer.wrap(buf, offset, len), null); + session.sendBinary(ByteBuffer.wrap(buf, offset, len), null); } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyEchoSocket.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyEchoSocket.java index 45ea5e82b0a..4f89c271173 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyEchoSocket.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/MyEchoSocket.java @@ -27,6 +27,6 @@ public class MyEchoSocket public void onWebSocketText(Session session, String message) { // Echo message back, asynchronously - session.getRemote().sendString(message, null); + session.sendText(message, null); } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/PermessageDeflateBufferTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/PermessageDeflateBufferTest.java index 6b5e706adda..5c20184d797 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/PermessageDeflateBufferTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/PermessageDeflateBufferTest.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.ee10.websocket.tests; -import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.time.Duration; @@ -35,11 +34,12 @@ import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.compression.CompressionPool; import org.eclipse.jetty.util.compression.DeflaterPool; import org.eclipse.jetty.util.compression.InflaterPool; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketOpen; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -48,8 +48,6 @@ import org.eclipse.jetty.websocket.core.WebSocketCoreSession; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -155,7 +153,7 @@ public class PermessageDeflateBufferTest Session session = client.connect(socket, uri, clientUpgradeRequest).get(5, TimeUnit.SECONDS); String s = randomText(); - session.getRemote().sendString(s); + session.sendText(s, Callback.NOOP); assertThat(socket.textMessages.poll(5, TimeUnit.SECONDS), is(s)); session.close(); @@ -175,7 +173,7 @@ public class PermessageDeflateBufferTest ByteBuffer message = randomBytes(1024); session.setMaxFrameSize(64); - session.getRemote().sendBytes(message); + session.sendBinary(message, Callback.NOOP); assertThat(socket.binaryMessages.poll(5, TimeUnit.SECONDS), equalTo(message)); session.close(); @@ -196,7 +194,7 @@ public class PermessageDeflateBufferTest URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/incomingFail"); Session session = client.connect(socket, uri, clientUpgradeRequest).get(5, TimeUnit.SECONDS); - session.getRemote().sendPartialString("partial", false); + session.sendPartialText("partial", false, Callback.NOOP); // Wait for the idle timeout to elapse. assertTrue(incomingFailEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); @@ -219,7 +217,7 @@ public class PermessageDeflateBufferTest URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/incomingFail"); Session session = client.connect(socket, uri, clientUpgradeRequest).get(5, TimeUnit.SECONDS); - session.getRemote().sendPartialString("partial", false); + session.sendPartialText("partial", false, Callback.NOOP); // Wait for the server to process the partial message. assertThat(socket.partialMessages.poll(5, TimeUnit.SECONDS), equalTo("partial" + "last=true")); @@ -250,7 +248,7 @@ public class PermessageDeflateBufferTest URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/outgoingFail"); Session session = client.connect(socket, uri, clientUpgradeRequest).get(5, TimeUnit.SECONDS); - session.getRemote().sendString("hello"); + session.sendText("hello", Callback.NOOP); // Wait for the idle timeout to elapse. assertTrue(outgoingFailEndPoint.closeLatch.await(2 * idleTimeout.toMillis(), TimeUnit.SECONDS)); @@ -273,7 +271,7 @@ public class PermessageDeflateBufferTest URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/outgoingFail"); Session session = client.connect(socket, uri, clientUpgradeRequest).get(5, TimeUnit.SECONDS); - session.getRemote().sendString("hello"); + session.sendText("hello", Callback.NOOP); // Wait for the server to process the message. assertThat(socket.partialMessages.poll(5, TimeUnit.SECONDS), equalTo("hello" + "last=false")); @@ -294,14 +292,12 @@ public class PermessageDeflateBufferTest @WebSocket public static class PartialTextSocket { - private static final Logger LOG = LoggerFactory.getLogger(EventSocket.class); - public Session session; public BlockingQueue partialMessages = new BlockingArrayQueue<>(); public CountDownLatch openLatch = new CountDownLatch(1); public CountDownLatch closeLatch = new CountDownLatch(1); - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { this.session = session; @@ -309,7 +305,7 @@ public class PermessageDeflateBufferTest } @OnWebSocketMessage - public void onMessage(String message, boolean last) throws IOException + public void onMessage(String message, boolean last) { partialMessages.offer(message + "last=" + last); } @@ -327,9 +323,9 @@ public class PermessageDeflateBufferTest public CountDownLatch closeLatch = new CountDownLatch(1); @OnWebSocketMessage - public void onMessage(Session session, String message) throws IOException + public void onMessage(Session session, String message) { - session.getRemote().sendPartialString(message, false); + session.sendPartialText(message, false, Callback.NOOP); } @OnWebSocketClose @@ -345,9 +341,9 @@ public class PermessageDeflateBufferTest public CountDownLatch closeLatch = new CountDownLatch(1); @OnWebSocketMessage - public void onMessage(Session session, String message, boolean last) throws IOException + public void onMessage(Session session, String message, boolean last) { - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } @OnWebSocketClose diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ProgrammaticWebSocketUpgradeTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ProgrammaticWebSocketUpgradeTest.java index ae1a9aebe38..3ca8d1bcabe 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ProgrammaticWebSocketUpgradeTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ProgrammaticWebSocketUpgradeTest.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.jupiter.api.AfterEach; @@ -101,7 +102,7 @@ public class ProgrammaticWebSocketUpgradeTest CompletableFuture connect = client.connect(socket, uri); try (Session session = connect.get(5, TimeUnit.SECONDS)) { - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); } assertTrue(socket.closeLatch.await(10, TimeUnit.SECONDS)); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerConfigTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerConfigTest.java index e29f3b79d89..064b81ccb0a 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerConfigTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/ServerConfigTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.ee10.websocket.tests; import java.net.URI; +import java.nio.ByteBuffer; import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -30,7 +31,7 @@ import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @@ -64,7 +65,6 @@ public class ServerConfigTest private static final int MAX_MESSAGE_SIZE = 20; private static final int IDLE_TIMEOUT = 500; - private final EventSocket annotatedEndpoint = new AnnotatedConfigEndpoint(); private final EventSocket sessionConfigEndpoint = new SessionConfigEndpoint(); private final EventSocket standardEndpoint = new EventSocket(); @@ -73,7 +73,6 @@ public class ServerConfigTest return switch (path) { case "servletConfig", "containerConfig" -> standardEndpoint; - case "annotatedConfig" -> annotatedEndpoint; case "sessionConfig" -> sessionConfigEndpoint; default -> throw new IllegalStateException(); }; @@ -81,12 +80,7 @@ public class ServerConfigTest public static Stream data() { - return Stream.of("servletConfig", "annotatedConfig", "containerConfig", "sessionConfig").map(Arguments::of); - } - - @WebSocket(idleTimeout = IDLE_TIMEOUT, maxTextMessageSize = MAX_MESSAGE_SIZE, maxBinaryMessageSize = MAX_MESSAGE_SIZE, inputBufferSize = INPUT_BUFFER_SIZE, batchMode = BatchMode.ON) - public static class AnnotatedConfigEndpoint extends EventSocket - { + return Stream.of("servletConfig", "containerConfig", "sessionConfig").map(Arguments::of); } @WebSocket @@ -116,15 +110,6 @@ public class ServerConfigTest } } - public class WebSocketAnnotatedConfigServlet extends JettyWebSocketServlet - { - @Override - public void configure(JettyWebSocketServletFactory factory) - { - factory.addMapping("/", (req, resp) -> annotatedEndpoint); - } - } - public class WebSocketSessionConfigServlet extends JettyWebSocketServlet { @Override @@ -171,7 +156,6 @@ public class ServerConfigTest ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); contextHandler.setContextPath("/"); contextHandler.addServlet(new ServletHolder(new WebSocketFactoryConfigServlet()), "/servletConfig"); - contextHandler.addServlet(new ServletHolder(new WebSocketAnnotatedConfigServlet()), "/annotatedConfig"); contextHandler.addServlet(new ServletHolder(new WebSocketSessionConfigServlet()), "/sessionConfig"); server.setHandler(contextHandler); @@ -234,7 +218,8 @@ public class ServerConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendBytes(BufferUtil.toBuffer(MESSAGE)); + ByteBuffer buffer = BufferUtil.toBuffer(MESSAGE); + clientEndpoint.session.sendBinary(buffer, Callback.NOOP); assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(serverEndpoint.error, instanceOf(MessageTooLargeException.class)); @@ -255,7 +240,7 @@ public class ServerConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendString("hello world"); + clientEndpoint.session.sendText("hello world", Callback.NOOP); String msg = serverEndpoint.textMessages.poll(500, TimeUnit.MILLISECONDS); assertThat(msg, is("hello world")); Thread.sleep(IDLE_TIMEOUT + 500); @@ -279,7 +264,7 @@ public class ServerConfigTest CompletableFuture connect = client.connect(clientEndpoint, uri); connect.get(5, TimeUnit.SECONDS); - clientEndpoint.session.getRemote().sendString(MESSAGE); + clientEndpoint.session.sendText(MESSAGE, Callback.NOOP); assertTrue(serverEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); assertThat(serverEndpoint.error, instanceOf(MessageTooLargeException.class)); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java index 9e3cb9c7914..0bab0daac55 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java @@ -58,8 +58,10 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.internal.HttpChannelState; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.exceptions.UpgradeException; @@ -104,7 +106,7 @@ public class WebSocketOverHTTP2Test server.addConnector(connector); SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12"); + sslContextFactory.setKeyStorePath(MavenTestingUtils.getTestResourcePath("keystore.p12").toString()); sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR); @@ -168,13 +170,13 @@ public class WebSocketOverHTTP2Test Session session = wsClient.connect(wsEndPoint, uri).get(5, TimeUnit.SECONDS); String text = "websocket"; - session.getRemote().sendString(text); + session.sendText(text, Callback.NOOP); String message = wsEndPoint.textMessages.poll(5, TimeUnit.SECONDS); assertNotNull(message); assertEquals(text, message); - session.close(StatusCode.NORMAL, null); + session.close(StatusCode.NORMAL, null, Callback.NOOP); assertTrue(wsEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); assertEquals(StatusCode.NORMAL, wsEndPoint.closeCode); assertNull(wsEndPoint.error); @@ -231,13 +233,13 @@ public class WebSocketOverHTTP2Test URI uri = URI.create("wss://localhost:" + tlsConnector.getLocalPort() + "/ws/echo"); Session session = wsClient.connect(wsEndPoint, uri).get(5, TimeUnit.SECONDS); String text = "websocket"; - session.getRemote().sendString(text); + session.sendText(text, Callback.NOOP); String message = wsEndPoint.textMessages.poll(5, TimeUnit.SECONDS); assertNotNull(message); assertEquals(text, message); - session.close(StatusCode.NORMAL, null); + session.close(StatusCode.NORMAL, null, Callback.NOOP); assertTrue(wsEndPoint.closeLatch.await(5, TimeUnit.SECONDS)); } @@ -361,7 +363,7 @@ public class WebSocketOverHTTP2Test EventSocket clientEndpoint = new EventSocket(); URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/specialEcho"); Session session = wsClient.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS); - session.getRemote().sendString("hello world"); + session.sendText("hello world", Callback.NOOP); String received = clientEndpoint.textMessages.poll(5, TimeUnit.SECONDS); assertThat(received, equalTo("hello world")); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketServletExamplesTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketServletExamplesTest.java index 5da3a50ff32..ebfb4ff2861 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketServletExamplesTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketServletExamplesTest.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.security.Credential; +import org.eclipse.jetty.websocket.api.Callback; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -112,7 +113,7 @@ public class WebSocketServletExamplesTest try (Session session = connect.get(5, TimeUnit.SECONDS)) { String message = "hello world"; - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); String response = socket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is(message)); @@ -136,7 +137,7 @@ public class WebSocketServletExamplesTest try (Session session = connect.get(5, TimeUnit.SECONDS)) { String message = "hello world"; - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); String response = socket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is(message)); @@ -162,7 +163,7 @@ public class WebSocketServletExamplesTest try (Session session = connect.get(5, TimeUnit.SECONDS)) { String message = "hello world"; - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); String response = socket.textMessages.poll(5, TimeUnit.SECONDS); assertThat(response, is(message)); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/resources/keystore.p12 b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/resources/keystore.p12 new file mode 100644 index 00000000000..b51c835024b Binary files /dev/null and b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/resources/keystore.p12 differ diff --git a/jetty-ee10/jetty-ee10-websocket/pom.xml b/jetty-ee10/jetty-ee10-websocket/pom.xml index 6ec675fd69f..e0b87510e45 100644 --- a/jetty-ee10/jetty-ee10-websocket/pom.xml +++ b/jetty-ee10/jetty-ee10-websocket/pom.xml @@ -27,17 +27,4 @@ jetty-ee10-websocket-servlet - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - - diff --git a/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-common/pom.xml b/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-common/pom.xml index b8f2c56ba74..063b9b323ca 100644 --- a/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-common/pom.xml +++ b/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-common/pom.xml @@ -47,10 +47,16 @@ org.slf4j slf4j-api + org.eclipse.jetty jetty-slf4j-impl test + + org.awaitility + awaitility + test + diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/pom.xml b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/pom.xml index 8161ea798f5..ef8a91d98bd 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/pom.xml +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/pom.xml @@ -46,10 +46,16 @@ org.slf4j slf4j-api + org.eclipse.jetty jetty-slf4j-impl test + + org.awaitility + awaitility + test + diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandler.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandler.java index 0d913746acf..efea52a59bc 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandler.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandler.java @@ -322,12 +322,6 @@ public class JakartaWebSocketFrameHandler implements FrameHandler } } - @Override - public boolean isAutoDemanding() - { - return false; - } - public Set getMessageHandlers() { return messageHandlerMap.values().stream() @@ -600,6 +594,11 @@ public class JakartaWebSocketFrameHandler implements FrameHandler { callback.succeeded(); coreSession.demand(1); + }, x -> + { + // Ignore failures, as we might be OSHUT but receive a PING. + callback.succeeded(); + coreSession.demand(1); }), false); } @@ -616,14 +615,19 @@ public class JakartaWebSocketFrameHandler implements FrameHandler // Use JSR356 PongMessage interface JakartaWebSocketPongMessage pongMessage = new JakartaWebSocketPongMessage(payload); pongHandle.invoke(pongMessage); + callback.succeeded(); + coreSession.demand(1); } catch (Throwable cause) { - throw new WebSocketException(endpointInstance.getClass().getSimpleName() + " PONG method error: " + cause.getMessage(), cause); + callback.failed(new WebSocketException(endpointInstance.getClass().getSimpleName() + " PONG method error: " + cause.getMessage(), cause)); } } - callback.succeeded(); - coreSession.demand(1); + else + { + callback.succeeded(); + coreSession.demand(1); + } } public void onText(Frame frame, Callback callback) @@ -646,14 +650,9 @@ public class JakartaWebSocketFrameHandler implements FrameHandler { switch (dataType) { - case OpCode.TEXT: - onText(frame, callback); - break; - case OpCode.BINARY: - onBinary(frame, callback); - break; - default: - throw new ProtocolException("Unable to process continuation during dataType " + dataType); + case OpCode.TEXT -> onText(frame, callback); + case OpCode.BINARY -> onBinary(frame, callback); + default -> callback.failed(new ProtocolException("Unable to process continuation during dataType " + dataType)); } } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java index 7c4b05eada8..80e1158f2ab 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketFrameHandlerFactory.java @@ -197,8 +197,8 @@ public abstract class JakartaWebSocketFrameHandlerFactory else { MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(), - MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); - return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle()); + MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, boolean.class)); + return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle(), true); } } catch (NoSuchMethodException e) diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java index 1f29181fdac..1eab5c76a87 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSink.java @@ -45,7 +45,7 @@ public class DecodedBinaryMessageSink extends AbstractDecodedMessageSink.Basi MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(DecodedBinaryMessageSink.class, "onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class)) .bindTo(this); - return new ByteBufferMessageSink(coreSession, methodHandle); + return new ByteBufferMessageSink(coreSession, methodHandle, true); } public void onWholeMessage(ByteBuffer wholeMessage) diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java index 9c1adc2aff1..d7d07ad4859 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSink.java @@ -42,7 +42,7 @@ public class DecodedBinaryStreamMessageSink extends AbstractDecodedMessageSin MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(DecodedBinaryStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, InputStream.class)) .bindTo(this); - return new InputStreamMessageSink(coreSession, methodHandle); + return new InputStreamMessageSink(coreSession, methodHandle, true); } public void onStreamStart(InputStream stream) diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSink.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSink.java index ef4bebfe2ed..e7091d4596e 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSink.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSink.java @@ -44,7 +44,7 @@ public class DecodedTextMessageSink extends AbstractDecodedMessageSink.Basic< MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(getClass(), "onMessage", MethodType.methodType(void.class, String.class)) .bindTo(this); - return new StringMessageSink(coreSession, methodHandle); + return new StringMessageSink(coreSession, methodHandle, true); } public void onMessage(String wholeMessage) diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java index 630c4b0d25d..47993416cf1 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSink.java @@ -42,7 +42,7 @@ public class DecodedTextStreamMessageSink extends AbstractDecodedMessageSink. MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup() .findVirtual(DecodedTextStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, Reader.class)) .bindTo(this); - return new ReaderMessageSink(coreSession, methodHandle); + return new ReaderMessageSink(coreSession, methodHandle, true); } public void onStreamStart(Reader reader) diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java index 0ef16e60493..aaa05b69ccd 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryMessageSinkTest.java @@ -56,7 +56,6 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest data.put((byte)31); data.flip(); sink.accept(new Frame(OpCode.BINARY).setPayload(data).setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Calendar decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("12-31-1999")); @@ -89,11 +88,8 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest data3.flip(); sink.accept(new Frame(OpCode.BINARY).setPayload(data1).setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(data2).setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(data3).setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); finCallback.get(1, TimeUnit.SECONDS); // wait for callback Calendar decoded = copyFuture.get(1, TimeUnit.SECONDS); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSinkTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSinkTest.java index 0b54dd11c4b..47863ff3271 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSinkTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedBinaryStreamMessageSinkTest.java @@ -58,7 +58,6 @@ public class DecodedBinaryStreamMessageSinkTest extends AbstractMessageSinkTest data.put((byte)31); data.flip(); sink.accept(new Frame(OpCode.BINARY).setPayload(data).setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Calendar decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("12-31-1999")); @@ -91,11 +90,8 @@ public class DecodedBinaryStreamMessageSinkTest extends AbstractMessageSinkTest data3.flip(); sink.accept(new Frame(OpCode.BINARY).setPayload(data1).setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(data2).setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(data3).setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Calendar decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("01-01-2000")); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSinkTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSinkTest.java index 9cff849b7a9..f4c08ab3172 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSinkTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextMessageSinkTest.java @@ -51,7 +51,6 @@ public class DecodedTextMessageSinkTest extends AbstractMessageSinkTest FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("2018.02.13").setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Date decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("02-13-2018")); @@ -72,11 +71,8 @@ public class DecodedTextMessageSinkTest extends AbstractMessageSinkTest FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("2023").setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(".08").setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(".22").setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Date decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("08-22-2023")); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSinkTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSinkTest.java index 8a661836365..4dd8cd533ee 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSinkTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/DecodedTextStreamMessageSinkTest.java @@ -54,7 +54,6 @@ public class DecodedTextStreamMessageSinkTest extends AbstractMessageSinkTest FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("2018.02.13").setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Date decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("02-13-2018")); @@ -75,11 +74,8 @@ public class DecodedTextStreamMessageSinkTest extends AbstractMessageSinkTest FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("2023").setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(".08").setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(".22").setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); Date decoded = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Decoded.contents", format(decoded, "MM-dd-yyyy"), is("08-22-2023")); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java index 23e9f2923a5..56ccec6089a 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/InputStreamMessageSinkTest.java @@ -36,6 +36,7 @@ import org.eclipse.jetty.websocket.core.messages.InputStreamMessageSink; import org.junit.jupiter.api.Test; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -46,13 +47,12 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle, true); FutureCallback finCallback = new FutureCallback(); ByteBuffer data = BufferUtil.toBuffer("Hello World", UTF_8); sink.accept(new Frame(OpCode.BINARY).setPayload(data), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); finCallback.get(1, TimeUnit.SECONDS); ByteArrayOutputStream byteStream = copy.poll(1, TimeUnit.SECONDS); assertThat("Writer.contents", byteStream.toString(UTF_8), is("Hello World")); @@ -64,24 +64,23 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle, true); FutureCallback fin1Callback = new FutureCallback(); ByteBuffer data1 = BufferUtil.toBuffer("Hello World", UTF_8); sink.accept(new Frame(OpCode.BINARY).setPayload(data1).setFin(true), fin1Callback); - // wait for demand (can't sent next message until a new frame is demanded) - coreSession.waitForDemand(1, TimeUnit.SECONDS); fin1Callback.get(1, TimeUnit.SECONDS); ByteArrayOutputStream byteStream = copy.poll(1, TimeUnit.SECONDS); assertThat("Writer.contents", byteStream.toString(UTF_8), is("Hello World")); assertThat("FinCallback.done", fin1Callback.isDone(), is(true)); + await().atMost(1, TimeUnit.SECONDS).until(() -> !sink.isDispatched()); + FutureCallback fin2Callback = new FutureCallback(); ByteBuffer data2 = BufferUtil.toBuffer("Greetings Earthling", UTF_8); sink.accept(new Frame(OpCode.BINARY).setPayload(data2).setFin(true), fin2Callback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); fin2Callback.get(1, TimeUnit.SECONDS); byteStream = copy.poll(1, TimeUnit.SECONDS); assertThat("Writer.contents", byteStream.toString(UTF_8), is("Greetings Earthling")); @@ -93,18 +92,15 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle, true); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.BINARY).setPayload("Hello").setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(", ").setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload("World").setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); ByteArrayOutputStream byteStream = copy.poll(1, TimeUnit.SECONDS); assertThat("Writer.contents", byteStream.toString(UTF_8), is("Hello, World")); @@ -118,7 +114,7 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest { InputStreamCopy copy = new InputStreamCopy(); MethodHandle copyHandle = getAcceptHandle(copy, InputStream.class); - InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle); + InputStreamMessageSink sink = new InputStreamMessageSink(AbstractSessionTest.session.getCoreSession(), copyHandle, true); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); @@ -126,13 +122,9 @@ public class InputStreamMessageSinkTest extends AbstractMessageSinkTest FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.BINARY).setPayload("Greetings").setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(", ").setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload("Earthling").setFin(false), callback3); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(new byte[0]).setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); ByteArrayOutputStream byteStream = copy.poll(1, TimeUnit.SECONDS); assertThat("Writer.contents", byteStream.toString(UTF_8), is("Greetings, Earthling")); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/ReaderMessageSinkTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/ReaderMessageSinkTest.java index 828abd72e3d..795422ecc4f 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/ReaderMessageSinkTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/common/messages/ReaderMessageSinkTest.java @@ -41,11 +41,10 @@ public class ReaderMessageSinkTest extends AbstractMessageSinkTest CompletableFuture copyFuture = new CompletableFuture<>(); ReaderCopy copy = new ReaderCopy(copyFuture); MethodHandle copyHandle = getAcceptHandle(copy, Reader.class); - ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle); + ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("Hello World"), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); StringWriter writer = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Writer.contents", writer.getBuffer().toString(), is("Hello World")); @@ -58,18 +57,15 @@ public class ReaderMessageSinkTest extends AbstractMessageSinkTest CompletableFuture copyFuture = new CompletableFuture<>(); ReaderCopy copy = new ReaderCopy(copyFuture); MethodHandle copyHandle = getAcceptHandle(copy, Reader.class); - ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle); + ReaderMessageSink sink = new ReaderMessageSink(session.getCoreSession(), copyHandle, true); FutureCallback callback1 = new FutureCallback(); FutureCallback callback2 = new FutureCallback(); FutureCallback finCallback = new FutureCallback(); sink.accept(new Frame(OpCode.TEXT).setPayload("Hello").setFin(false), callback1); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload(", ").setFin(false), callback2); - coreSession.waitForDemand(1, TimeUnit.SECONDS); sink.accept(new Frame(OpCode.CONTINUATION).setPayload("World").setFin(true), finCallback); - coreSession.waitForDemand(1, TimeUnit.SECONDS); StringWriter writer = copyFuture.get(1, TimeUnit.SECONDS); assertThat("Writer contents", writer.getBuffer().toString(), is("Hello, World")); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/NetworkFuzzer.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/NetworkFuzzer.java index 0e62e36dd1f..201b31b9dda 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/NetworkFuzzer.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/NetworkFuzzer.java @@ -229,6 +229,7 @@ public class NetworkFuzzer extends Fuzzer.Adapter implements Fuzzer, AutoCloseab this.coreSession = coreSession; this.openLatch.countDown(); callback.succeeded(); + coreSession.demand(1); } @Override @@ -236,6 +237,7 @@ public class NetworkFuzzer extends Fuzzer.Adapter implements Fuzzer, AutoCloseab { receivedFrames.offer(Frame.copy(frame)); callback.succeeded(); + coreSession.demand(1); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/framehandlers/FrameEcho.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/framehandlers/FrameEcho.java index 62f2ec69472..79314e3c1cf 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/framehandlers/FrameEcho.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/framehandlers/FrameEcho.java @@ -32,15 +32,22 @@ public class FrameEcho implements FrameHandler { this.coreSession = coreSession; callback.succeeded(); + coreSession.demand(1); } @Override public void onFrame(Frame frame, Callback callback) { - if (frame.isControlFrame()) + Runnable succeedAndDemand = () -> + { callback.succeeded(); + coreSession.demand(1); + }; + + if (frame.isControlFrame()) + succeedAndDemand.run(); else - coreSession.sendFrame(Frame.copy(frame), callback, false); + coreSession.sendFrame(Frame.copy(frame), Callback.from(succeedAndDemand, callback::failed), false); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/CloseInOnOpenTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/CloseInOnOpenTest.java index 9dc383df543..29170635ba9 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/CloseInOnOpenTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/CloseInOnOpenTest.java @@ -73,7 +73,7 @@ public class CloseInOnOpenTest } @Test - public void testCloseInOnWebSocketConnect() throws Exception + public void testCloseInOnWebSocketOpen() throws Exception { URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws"); EventSocket clientEndpoint = new EventSocket(); @@ -89,7 +89,7 @@ public class CloseInOnOpenTest public static class ClosingListener { @OnOpen - public void onWebSocketConnect(Session session) throws Exception + public void onWebSocketOpen(Session session) throws Exception { session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "I am a WS that closes immediately")); } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java index 14664b91896..f3949f875d8 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/autobahn/JakartaAutobahnSocket.java @@ -37,7 +37,7 @@ public class JakartaAutobahnSocket public CountDownLatch closeLatch = new CountDownLatch(1); @OnOpen - public void onConnect(Session session) + public void onOpen(Session session) { this.session = session; session.setMaxTextMessageBufferSize(Integer.MAX_VALUE); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandler.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandler.java index 2c113553c97..7de2dcb48ab 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandler.java @@ -16,7 +16,6 @@ package org.eclipse.jetty.ee9.websocket.common; import java.lang.invoke.MethodHandle; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; -import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.ee9.websocket.api.BatchMode; @@ -82,7 +81,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler private WebSocketSession session; private SuspendState state = SuspendState.DEMANDING; private Runnable delayedOnFrame; - private CoreSession coreSession; public JettyWebSocketFrameHandler(WebSocketContainer container, Object endpointInstance, @@ -151,7 +149,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler try { customizer.customize(coreSession); - this.coreSession = coreSession; session = new WebSocketSession(container, coreSession, this); if (!session.isOpen()) throw new IllegalStateException("Session is not open"); @@ -165,13 +162,11 @@ public class JettyWebSocketFrameHandler implements FrameHandler pingHandle = InvokerUtils.bindTo(pingHandle, session); pongHandle = InvokerUtils.bindTo(pongHandle, session); - Executor executor = container.getExecutor(); - if (textHandle != null) - textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, session); + textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, session); if (binaryHandle != null) - binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, session); + binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, session); if (openHandle != null) openHandle.invoke(); @@ -191,7 +186,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler @Override public void onFrame(Frame frame, Callback callback) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -227,26 +222,13 @@ public class JettyWebSocketFrameHandler implements FrameHandler switch (frame.getOpCode()) { - case OpCode.CLOSE: - onCloseFrame(frame, callback); - break; - case OpCode.PING: - onPingFrame(frame, callback); - break; - case OpCode.PONG: - onPongFrame(frame, callback); - break; - case OpCode.TEXT: - onTextFrame(frame, callback); - break; - case OpCode.BINARY: - onBinaryFrame(frame, callback); - break; - case OpCode.CONTINUATION: - onContinuationFrame(frame, callback); - break; - default: - callback.failed(new IllegalStateException()); + case OpCode.CLOSE -> onCloseFrame(frame, callback); + case OpCode.PING -> onPingFrame(frame, callback); + case OpCode.PONG -> onPongFrame(frame, callback); + case OpCode.TEXT -> onTextFrame(frame, callback); + case OpCode.BINARY -> onBinaryFrame(frame, callback); + case OpCode.CONTINUATION -> onContinuationFrame(frame, callback); + default -> callback.failed(new IllegalStateException()); } } @@ -283,7 +265,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler @Override public void onClosed(CloseStatus closeStatus, Callback callback) { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { // We are now closed and cannot suspend or resume. state = SuspendState.CLOSED; @@ -422,15 +404,9 @@ public class JettyWebSocketFrameHandler implements FrameHandler acceptMessage(frame, callback); } - @Override - public boolean isAutoDemanding() - { - return false; - } - public void suspend() { - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -448,7 +424,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler { boolean needDemand = false; Runnable delayedFrame = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -485,7 +461,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler private void demand() { boolean demand = false; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -517,8 +493,8 @@ public class JettyWebSocketFrameHandler implements FrameHandler if (cause instanceof BadPayloadException) return new org.eclipse.jetty.ee9.websocket.api.exceptions.BadPayloadException(cause.getMessage(), cause); - if (cause instanceof CloseException) - return new org.eclipse.jetty.ee9.websocket.api.exceptions.CloseException(((CloseException)cause).getStatusCode(), cause.getMessage(), cause); + if (cause instanceof CloseException ce) + return new org.eclipse.jetty.ee9.websocket.api.exceptions.CloseException(ce.getStatusCode(), cause.getMessage(), cause); if (cause instanceof WebSocketTimeoutException) return new org.eclipse.jetty.ee9.websocket.api.exceptions.WebSocketTimeoutException(cause.getMessage(), cause); @@ -526,11 +502,8 @@ public class JettyWebSocketFrameHandler implements FrameHandler if (cause instanceof InvalidSignatureException) return new org.eclipse.jetty.ee9.websocket.api.exceptions.InvalidWebSocketException(cause.getMessage(), cause); - if (cause instanceof UpgradeException) - { - UpgradeException ue = (UpgradeException)cause; + if (cause instanceof UpgradeException ue) return new org.eclipse.jetty.ee9.websocket.api.exceptions.UpgradeException(ue.getRequestURI(), ue.getResponseStatusCode(), cause); - } return cause; } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandlerFactory.java index bc57346689d..51040f628a6 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/main/java/org/eclipse/jetty/ee9/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -27,7 +27,6 @@ import java.nio.ByteBuffer; import java.time.Duration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; import org.eclipse.jetty.ee9.websocket.api.BatchMode; import org.eclipse.jetty.ee9.websocket.api.Frame; @@ -118,12 +117,6 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle new InvokerUtils.Arg(boolean.class).required() }; - private static final InvokerUtils.Arg[] binaryPartialArrayCallingArgs = new InvokerUtils.Arg[]{ - new InvokerUtils.Arg(Session.class), - new InvokerUtils.Arg(byte[].class).required(), - new InvokerUtils.Arg(boolean.class).required() - }; - private final WebSocketContainer container; private final WebSocketComponents components; private final Map, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>(); @@ -198,7 +191,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle metadata); } - public static MessageSink createMessageSink(MethodHandle msgHandle, Class sinkClass, Executor executor, WebSocketSession session) + public static MessageSink createMessageSink(MethodHandle msgHandle, Class sinkClass, WebSocketSession session) { if (msgHandle == null) return null; @@ -209,8 +202,8 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); MethodHandle ctorHandle = lookup.findConstructor(sinkClass, - MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); - return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgHandle); + MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, boolean.class)); + return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgHandle, true); } catch (NoSuchMethodException e) { diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/MessageInputStreamTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/MessageInputStreamTest.java index c55a76e665a..be185eae542 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/MessageInputStreamTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/MessageInputStreamTest.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.messages.MessageInputStream; @@ -43,7 +44,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append a single message (simple, short) Frame frame = new Frame(OpCode.TEXT); @@ -64,7 +65,7 @@ public class MessageInputStreamTest @Test public void testMultipleReadsIntoSingleByteArray() throws IOException { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append a single message (simple, short) Frame frame = new Frame(OpCode.TEXT); @@ -96,7 +97,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { final AtomicBoolean hadError = new AtomicBoolean(false); final CountDownLatch startLatch = new CountDownLatch(1); @@ -141,7 +142,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { final AtomicBoolean hadError = new AtomicBoolean(false); @@ -176,7 +177,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { final AtomicBoolean hadError = new AtomicBoolean(false); @@ -222,7 +223,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append parts of message Frame msg1 = new Frame(OpCode.BINARY).setPayload("Hello ").setFin(false); @@ -249,7 +250,7 @@ public class MessageInputStreamTest { assertTimeout(Duration.ofMillis(5000), () -> { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Append parts of message Frame msg1 = new Frame(OpCode.BINARY).setPayload("Hello ").setFin(false); @@ -275,7 +276,7 @@ public class MessageInputStreamTest @Test public void testReadSingleByteIsNotSigned() throws Exception { - try (MessageInputStream stream = new MessageInputStream()) + try (MessageInputStream stream = new MessageInputStream(new CoreSession.Empty())) { // Byte must be greater than 127. int theByte = 200; diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/OutgoingMessageCapture.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/OutgoingMessageCapture.java index 3852244e1f5..86689457b19 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/OutgoingMessageCapture.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common/src/test/java/org/eclipse/jetty/ee9/websocket/common/OutgoingMessageCapture.java @@ -96,7 +96,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes String event = String.format("TEXT:fin=%b:len=%d", frame.isFin(), frame.getPayloadLength()); LOG.debug(event); events.offer(event); - messageSink = new StringMessageSink(this, wholeTextHandle); + messageSink = new StringMessageSink(this, wholeTextHandle, true); break; } case OpCode.BINARY: @@ -104,7 +104,7 @@ public class OutgoingMessageCapture extends CoreSession.Empty implements CoreSes String event = String.format("BINARY:fin=%b:len=%d", frame.isFin(), frame.getPayloadLength()); LOG.debug(event); events.offer(event); - messageSink = new ByteBufferMessageSink(this, wholeBinaryHandle); + messageSink = new ByteBufferMessageSink(this, wholeBinaryHandle, true); break; } case OpCode.CONTINUATION: diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/EventSocket.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/EventSocket.java index 3bb3ac44594..d4ca3bb5577 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/EventSocket.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/EventSocket.java @@ -53,7 +53,7 @@ public class EventSocket this.session = session; behavior = session.getPolicy().getBehavior().name(); if (LOG.isDebugEnabled()) - LOG.debug("{} onOpen(): {}", toString(), session); + LOG.debug("{} onOpen(): {}", this, session); openLatch.countDown(); } @@ -61,7 +61,7 @@ public class EventSocket public void onMessage(String message) throws IOException { if (LOG.isDebugEnabled()) - LOG.debug("{} onMessage(): {}", toString(), message); + LOG.debug("{} onMessage(): {}", this, message); textMessages.offer(message); } @@ -70,7 +70,7 @@ public class EventSocket { ByteBuffer message = ByteBuffer.wrap(buf, offset, len); if (LOG.isDebugEnabled()) - LOG.debug("{} onMessage(): {}", toString(), message); + LOG.debug("{} onMessage(): {}", this, message); binaryMessages.offer(message); } @@ -78,7 +78,7 @@ public class EventSocket public void onClose(int statusCode, String reason) { if (LOG.isDebugEnabled()) - LOG.debug("{} onClose(): {}:{}", toString(), statusCode, reason); + LOG.debug("{} onClose(): {}:{}", this, statusCode, reason); this.closeCode = statusCode; this.closeReason = reason; closeLatch.countDown(); @@ -88,7 +88,7 @@ public class EventSocket public void onError(Throwable cause) { if (LOG.isDebugEnabled()) - LOG.debug("{} onError(): {}", toString(), cause); + LOG.debug("{} onError()", this, cause); error = cause; errorLatch.countDown(); }