From adf57548363ed39f18e65fdec0a63d0e310286b1 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 2 May 2023 16:42:40 +0200 Subject: [PATCH] Fixes #9552 - Jetty 12 - Rewrite of the Jetty WebSocket APIs. (#9652) * Removed unnecessary classes, among which `BatchMode`, `CloseStatus`, etc. * Coalesced all listener interfaces into `Session.Listener`. * Moved `RemoteEndpoint` functionality to `Session`. * Renamed `WebSocketPolicy` to `Configurable`. * Renamed `WriteCallback` to just `Callback`, as it is now also used for some listener methods. * Renamed `@OnWebSocketConnect` to `@OnWebSocketOpen` * Renamed `Session.Listener.onWebSocketConnect()` to `onWebSocketOpen()`. * Removed `@WebSocket` annotation attributes, because they were just a subset of the configurable ones and they can be configured directly on the Session instance. * Removed `Session.suspend()` and `SuspendToken`, and introduced `Session.demand()`. * Introduced `@WebSocket.autoDemand` and `Session.Listener.AutoDemanding` to support automatic demand upon exit of WebSocket handler methods. * Removed `FrameHandler.isAutoDemanding()` and `CoreSession.isAutoDemanding()`. * Changed the responsibility of demand from `WebSocketCoreSession` to `FrameHandler`, which in turn may delegate to `MessageSink`. * Updated MessageInputStream to fail if an exception is thrown by the application. Signed-off-by: Simone Bordet --- .../client/websocket/client-websocket.adoc | 6 +- .../asciidoc/programming-guide/websocket.adoc | 112 ++--- .../jetty/docs/programming/WebSocketDocs.java | 173 +++----- .../jetty/websocket/core/CoreSession.java | 14 +- .../jetty/websocket/core/FrameHandler.java | 151 +++---- .../jetty/websocket/core/OutgoingFrames.java | 2 +- .../websocket/core/WebSocketCoreSession.java | 83 ++-- .../core/internal/MessageHandler.java | 72 ++-- .../core/messages/AbstractMessageSink.java | 73 +++- .../core/messages/ByteArrayMessageSink.java | 84 ++-- .../core/messages/ByteBufferMessageSink.java | 111 ++--- .../core/messages/DispatchedMessageSink.java | 149 +++---- .../core/messages/InputStreamMessageSink.java | 9 +- .../core/messages/MessageInputStream.java | 191 +++++---- .../core/messages/MessageReader.java | 42 +- .../websocket/core/messages/MessageSink.java | 29 +- .../messages/PartialByteArrayMessageSink.java | 32 +- .../PartialByteBufferMessageSink.java | 36 +- .../messages/PartialStringMessageSink.java | 44 +- .../core/messages/ReaderMessageSink.java | 9 +- .../core/messages/StringMessageSink.java | 39 +- .../websocket/core/AutoFragmentTest.java | 48 +-- .../jetty/websocket/core/DemandTest.java | 7 - .../core/DemandingIncomingFramesCapture.java | 3 +- .../websocket/core/EchoFrameHandler.java | 2 + .../jetty/websocket/core/FrameBufferTest.java | 4 +- .../websocket/core/MessageHandlerTest.java | 11 +- .../websocket/core/TestAsyncFrameHandler.java | 2 + .../websocket/core/TestFrameHandler.java | 7 + .../websocket/core/TestMessageHandler.java | 2 - .../websocket/core/WebSocketCloseTest.java | 8 +- .../core/WebSocketNegotiationTest.java | 2 +- .../websocket/core/WebSocketOpenTest.java | 33 +- .../core/autobahn/CoreAutobahnClient.java | 2 +- .../client/WebSocketClientServerTest.java | 1 + .../core/extensions/ExtensionTool.java | 5 +- .../extensions/FragmentExtensionTest.java | 4 +- .../PerMessageDeflateExtensionTest.java | 6 +- .../PermessageDeflateDemandTest.java | 6 - .../websocket/core/proxy/WebSocketProxy.java | 182 +++++--- .../core/proxy/WebSocketProxyTest.java | 38 +- .../core/server/WebSocketServerTest.java | 40 +- .../core/util/MessageReaderTest.java | 3 +- .../util/PartialStringMessageSinkTest.java | 2 +- .../core/util/StringMessageSinkTest.java | 10 +- .../jetty/websocket/api/BatchMode.java | 42 -- .../eclipse/jetty/websocket/api/Callback.java | 105 +++++ .../jetty/websocket/api/CloseStatus.java | 50 --- ...WebSocketPolicy.java => Configurable.java} | 114 +++--- .../jetty/websocket/api/RemoteEndpoint.java | 196 --------- .../eclipse/jetty/websocket/api/Session.java | 387 +++++++++++++----- .../jetty/websocket/api/SuspendToken.java | 25 -- .../jetty/websocket/api/WebSocketAdapter.java | 77 ---- .../websocket/api/WebSocketBehavior.java | 26 -- .../api/WebSocketConnectionListener.java | 58 --- .../websocket/api/WebSocketFrameListener.java | 27 -- .../websocket/api/WebSocketListener.java | 40 -- .../api/WebSocketPartialListener.java | 53 --- .../api/WebSocketPingPongListener.java | 40 -- .../jetty/websocket/api/WriteCallback.java | 62 --- .../api/annotations/OnWebSocketClose.java | 16 +- .../api/annotations/OnWebSocketError.java | 16 +- .../api/annotations/OnWebSocketFrame.java | 17 +- .../api/annotations/OnWebSocketMessage.java | 45 +- ...ocketConnect.java => OnWebSocketOpen.java} | 16 +- .../websocket/api/annotations/WebSocket.java | 47 +-- .../api/annotations/package-info.java | 18 - .../exceptions/InvalidWebSocketException.java | 7 +- .../api/exceptions/package-info.java | 18 - .../jetty/websocket/api/package-info.java | 18 - .../websocket/client/WebSocketClient.java | 69 ++-- .../src/test/java/examples/ClientDemo.java | 20 +- .../test/java/examples/SimpleEchoSocket.java | 22 +- .../client/WebSocketClientBadUriTest.java | 4 +- .../common/JettyWebSocketFrameHandler.java | 376 +++++++---------- .../JettyWebSocketFrameHandlerFactory.java | 280 +++++-------- .../JettyWebSocketFrameHandlerMetadata.java | 28 +- .../common/JettyWebSocketRemoteEndpoint.java | 235 ----------- .../websocket/common/SessionTracker.java | 3 +- .../websocket/common/WebSocketSession.java | 201 ++++++--- .../internal/ByteBufferMessageSink.java | 40 ++ .../PartialByteBufferMessageSink.java | 40 ++ .../jetty/websocket/common/EndPoints.java | 180 ++++---- .../JettyWebSocketFrameHandlerTest.java | 21 +- .../common/LocalEndpointMetadataTest.java | 33 +- .../common/MessageInputStreamTest.java | 15 +- .../common/OutgoingMessageCapture.java | 28 +- .../endpoints/adapters/AdapterEchoSocket.java | 22 +- .../adapters/AnnotatedEchoSocket.java | 11 +- .../adapters/ListenerEchoSocket.java | 15 +- .../server/ServerWebSocketContainer.java | 30 +- .../tests/AnnoMaxMessageEndpoint.java | 13 +- .../tests/AnnotatedPartialListenerTest.java | 34 +- .../tests/CloseTrackingEndpoint.java | 22 +- .../tests/ConcurrentConnectTest.java | 5 +- .../tests/ConnectMessageEndpoint.java | 11 +- .../websocket/tests/ConnectionHeaderTest.java | 3 +- .../tests/DemandWithBlockingStreamsTest.java | 153 +++++++ .../jetty/websocket/tests/EchoSocket.java | 10 +- .../jetty/websocket/tests/ErrorCloseTest.java | 5 +- .../jetty/websocket/tests/EventSocket.java | 16 +- ...esumeTest.java => ExplicitDemandTest.java} | 94 +---- .../tests/GetAuthHeaderEndpoint.java | 11 +- .../websocket/tests/JettyOnCloseTest.java | 11 +- .../JettyWebSocketExtensionConfigTest.java | 3 +- .../websocket/tests/LargeDeflateTest.java | 5 +- .../tests/MaxOutgoingFramesTest.java | 25 +- .../jetty/websocket/tests/ParamsEndpoint.java | 10 +- .../jetty/websocket/tests/SimpleEchoTest.java | 3 +- .../websocket/tests/SingleOnMessageTest.java | 33 +- .../websocket/tests/WebSocketStatsTest.java | 3 +- .../websocket/tests/WebSocketStopTest.java | 13 +- .../tests/autobahn/JettyAutobahnClient.java | 3 +- .../tests/client/BadNetworkTest.java | 40 +- .../tests/client/ClientCloseTest.java | 71 ++-- .../tests/client/ClientConfigTest.java | 18 +- .../tests/client/ClientConnectTest.java | 2 +- .../tests/client/ClientSessionsTest.java | 7 +- .../tests/client/ClientWriteThread.java | 13 +- .../tests/client/ConnectFutureTest.java | 36 +- .../tests/client/SlowClientTest.java | 4 +- .../tests/client/WebSocketClientTest.java | 47 ++- .../listeners/AbstractAnnotatedListener.java | 26 +- .../tests/listeners/AbstractListener.java | 61 --- .../tests/listeners/BinaryListeners.java | 57 ++- .../tests/listeners/TextListeners.java | 31 +- .../listeners/WebSocketListenerTest.java | 18 +- .../websocket/tests/proxy/WebSocketProxy.java | 143 +++---- .../tests/proxy/WebSocketProxyTest.java | 45 +- .../tests/server/AbstractCloseEndpoint.java | 34 +- .../tests/server/CloseInOnCloseEndpoint.java | 5 +- .../CloseInOnCloseEndpointNewThread.java | 5 +- .../tests/server/ContainerEndpoint.java | 10 +- .../DynamicServerConfigurationTest.java | 6 +- .../tests/server/FastCloseEndpoint.java | 7 +- .../tests/server/FastFailEndpoint.java | 4 +- .../tests/server/FrameAnnotationTest.java | 38 +- .../tests/server/FrameListenerTest.java | 35 +- .../tests/server/PartialListenerTest.java | 64 +-- .../tests/server/ServerCloseTest.java | 15 +- .../tests/server/ServerConfigTest.java | 30 +- .../tests/server/SlowServerEndpoint.java | 13 +- .../tests/server/SlowServerTest.java | 3 +- ...WriteCallback.java => FutureCallback.java} | 13 +- .../jetty/ee10/demos/WebSocketServer.java | 4 +- .../jetty/ee10/demos/WebSocketServerTest.java | 3 +- .../org/example/WebSocketChatServlet.java | 13 +- .../ee10/osgi/test/SimpleEchoSocket.java | 18 +- .../jetty/ee10/servlet/DefaultServlet.java | 54 ++- .../websocket/JettyWebSocketCdiTest.java | 9 +- .../test/websocket/JettySimpleEchoSocket.java | 16 +- .../websocket/WebSocketClientServlet.java | 7 +- .../websocket/WebSocketClientServlet.java | 7 +- .../jetty/ee10/webapp/HugeResourceTest.java | 2 +- .../JakartaWebSocketClientContainer.java | 2 +- .../pom.xml | 6 + .../common/JakartaWebSocketFrameHandler.java | 33 +- .../JakartaWebSocketFrameHandlerFactory.java | 4 +- .../messages/DecodedBinaryMessageSink.java | 2 +- .../DecodedBinaryStreamMessageSink.java | 2 +- .../messages/DecodedTextMessageSink.java | 2 +- .../DecodedTextStreamMessageSink.java | 2 +- .../jakarta/common/AbstractSessionTest.java | 26 +- .../DecodedBinaryMessageSinkTest.java | 5 +- .../messages/InputStreamMessageSinkTest.java | 12 +- .../messages/ReaderMessageSinkTest.java | 4 +- .../websocket/jakarta/tests/EventSocket.java | 11 +- .../jakarta/tests/NetworkFuzzer.java | 2 + .../tests/framehandlers/FrameEcho.java | 1 + .../jakarta/tests/CloseInOnOpenTest.java | 4 +- ...rtaClientShutdownWithServerWebAppTest.java | 9 +- .../tests/autobahn/JakartaAutobahnSocket.java | 2 +- .../server/JettyWebSocketServerContainer.java | 63 +-- .../server/JettyWebSocketServletFactory.java | 11 +- .../server/browser/BrowserSocket.java | 35 +- .../websocket/tests/CloseInOnOpenTest.java | 10 +- .../ee10/websocket/tests/EchoSocket.java | 10 +- .../ee10/websocket/tests/EventSocket.java | 18 +- .../tests/JettyClientClassLoaderTest.java | 13 +- .../tests/JettyWebSocketFilterTest.java | 37 +- .../tests/JettyWebSocketRestartTest.java | 3 +- .../JettyWebSocketServletAttributeTest.java | 6 +- .../tests/JettyWebSocketServletTest.java | 3 +- .../websocket/tests/MyBinaryEchoSocket.java | 2 +- .../ee10/websocket/tests/MyEchoSocket.java | 2 +- .../tests/PermessageDeflateBufferTest.java | 32 +- .../ProgrammaticWebSocketUpgradeTest.java | 3 +- .../websocket/tests/ServerConfigTest.java | 29 +- .../tests/WebSocketOverHTTP2Test.java | 14 +- .../tests/WebSocketServletExamplesTest.java | 7 +- .../src/test/resources/keystore.p12 | Bin 0 -> 2533 bytes jetty-ee10/jetty-ee10-websocket/pom.xml | 13 - .../jetty-ee8-websocket-javax-common/pom.xml | 6 + .../pom.xml | 6 + .../common/JakartaWebSocketFrameHandler.java | 33 +- .../JakartaWebSocketFrameHandlerFactory.java | 4 +- .../messages/DecodedBinaryMessageSink.java | 2 +- .../DecodedBinaryStreamMessageSink.java | 2 +- .../messages/DecodedTextMessageSink.java | 2 +- .../DecodedTextStreamMessageSink.java | 2 +- .../DecodedBinaryMessageSinkTest.java | 4 - .../DecodedBinaryStreamMessageSinkTest.java | 4 - .../messages/DecodedTextMessageSinkTest.java | 4 - .../DecodedTextStreamMessageSinkTest.java | 4 - .../messages/InputStreamMessageSinkTest.java | 22 +- .../messages/ReaderMessageSinkTest.java | 8 +- .../jakarta/tests/NetworkFuzzer.java | 2 + .../tests/framehandlers/FrameEcho.java | 11 +- .../jakarta/tests/CloseInOnOpenTest.java | 4 +- .../tests/autobahn/JakartaAutobahnSocket.java | 2 +- .../common/JettyWebSocketFrameHandler.java | 61 +-- .../JettyWebSocketFrameHandlerFactory.java | 13 +- .../common/MessageInputStreamTest.java | 17 +- .../common/OutgoingMessageCapture.java | 4 +- .../ee9/websocket/tests/EventSocket.java | 10 +- 215 files changed, 3202 insertions(+), 4067 deletions(-) delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/BatchMode.java create mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/Callback.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/CloseStatus.java rename jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/{WebSocketPolicy.java => Configurable.java} (80%) delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/RemoteEndpoint.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/SuspendToken.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketAdapter.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketBehavior.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketConnectionListener.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketFrameListener.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketListener.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPartialListener.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPingPongListener.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/WriteCallback.java rename jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/{OnWebSocketConnect.java => OnWebSocketOpen.java} (68%) delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/package-info.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/exceptions/package-info.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-api/src/main/java/org/eclipse/jetty/websocket/api/package-info.java delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketRemoteEndpoint.java create mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/ByteBufferMessageSink.java create mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/internal/PartialByteBufferMessageSink.java create mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/DemandWithBlockingStreamsTest.java rename jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/{SuspendResumeTest.java => ExplicitDemandTest.java} (51%) delete mode 100644 jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/listeners/AbstractListener.java rename jetty-core/jetty-websocket/jetty-websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/util/{FutureWriteCallback.java => FutureCallback.java} (73%) create mode 100644 jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/resources/keystore.p12 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 153cbb271698..531a7c828b00 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 09b140a57ada..40ce4f6bfc73 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. - -Streaming sends using blocking APIs is quite simple: - -[source,java,indent=0] ----- -include::{doc_code}/org/eclipse/jetty/docs/programming/WebSocketDocs.java[tags=streamSendBlocking] ----- +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 non-blocking APIs is more complicated, as you should wait (without blocking!) for the callbacks to complete. +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. -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 9433a1d49194..3065ffb18867 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 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 void onWebSocketConnect(Session session) 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 void onWebSocketClose(int statusCode, String reason) @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 void onWebSocketText(String message) // 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 void onWebSocketBinary(byte[] payload, int offset, int length) 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 void onWebSocketPartialText(String payload, boolean fin) } @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 AnnotatedEndPoint { 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 void onConnect(Session session) 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 void onClose(int statusCode, String reason) @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 void onTextMessage(Session session, String message) // <3> // 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 void onBinaryMessage(byte[] payload, int offset, int length) 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 void onBinaryMessage(InputStream stream) @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 void onWebSocketConnect(Session session) } // 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 NonBlockingSendEndpoint @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 void writeFailed(Throwable x) } @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 StreamSendNonBlockingEndpoint @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 @@ protected Action process() throws Throwable // <2> 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 @@ protected void onCompleteFailure(Throwable x) @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 CloseEndpoint 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 @@ private static void disposeResources() { } - 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 0b02c2e7e081..51ebf73455ec 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 WebSocketComponents getWebSocketComponents() @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 545fcb6f268f..3db3a5b5b3b8 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 @@ 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 8d3eef067531..2b70c78aec88 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 4715542f7741..da5738982ae3 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 WebSocketCoreSession(FrameHandler handler, Behavior behavior, Negotiated 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 @@ protected void handle(Runnable runnable) } } - /** - * @return True if the sessions handling is demanding. - */ - public boolean isAutoDemanding() - { - return autoDemanding; - } - public ExtensionStack getExtensionStack() { return negotiated.getExtensions(); @@ -382,21 +372,18 @@ public void onOpen() 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 void onOpen() @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 void onFrame(Frame frame, Callback callback) // 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 void onFrame(Frame frame, Callback callback) } else { - closeCallback = Callback.from( - () -> + closeCallback = Callback.from(() -> + { + 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 { - 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)); + callback.succeeded(); + } + }, + x -> processHandlerError(x, callback)); } handler.onFrame(frame, closeCallback); @@ -783,7 +756,7 @@ public WebSocketComponents getWebSocketComponents() @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 326a6c1dadc0..81bf7f7585c0 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.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 void onOpen(CoreSession coreSession, Callback callback) this.coreSession = coreSession; callback.succeeded(); + coreSession.demand(1); } @Override @@ -130,30 +128,26 @@ public void onFrame(Frame frame, Callback callback) 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 @@ protected void onTextFrame(Frame frame, Callback callback) 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 @@ protected void onTextFrame(Frame frame, Callback callback) { if (textBuffer.hasCodingErrors()) throw new BadPayloadException("Invalid UTF-8"); - callback.succeeded(); + else + callback.succeeded(); } + + coreSession.demand(1); } catch (Throwable t) { @@ -232,8 +230,8 @@ protected void onBinaryFrame(Frame frame, Callback callback) 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 @@ protected void onBinaryFrame(Frame frame, Callback callback) { callback.succeeded(); } + + coreSession.demand(1); } catch (Throwable t) { @@ -256,27 +256,21 @@ protected void onContinuationFrame(Frame frame, Callback callback) { 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 void sendText(Callback callback, boolean batch, final String... parts) 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 void sendBinary(Callback callback, boolean batch, final ByteBuffer... par 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 3fbed69422b5..819b1a6154ae 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 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 6724c856ed33..aec69940e339 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.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 void accept(Frame frame, Callback callback) { 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)); + callback.failed(new MessageTooLargeException(String.format("Binary message too large: %,d > %,d", size, maxSize))); + return; } - // If we are fin and no OutputStream has been created we don't need to aggregate. - if (frame.isFin() && (out == null)) + ByteBuffer payload = frame.getPayload(); + if (frame.isFin() && accumulator == null) { - if (frame.hasPayload()) - { - byte[] buf = BufferUtil.toArray(frame.getPayload()); - methodHandle.invoke(buf, 0, buf.length); - } - else - methodHandle.invoke(EMPTY_BUFFER, 0, 0); - + byte[] buf = BufferUtil.toArray(payload); + getMethodHandle().invoke(buf, 0, buf.length); callback.succeeded(); - session.demand(1); + autoDemand(); return; } - // Aggregate the frame payload. - if (frame.hasPayload()) + if (!frame.isFin() && !frame.hasPayload()) { - ByteBuffer payload = frame.getPayload(); - if (out == null) - out = new ByteBufferCallbackAccumulator(); - out.addEntry(payload, callback); + callback.succeeded(); + getCoreSession().demand(1); + return; } - // If the methodHandle throws we don't want to fail callback twice. - callback = Callback.NOOP; + 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 1da91ad09c2d..1fa44b8c924f 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 @@ 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); + } + + protected ByteBufferMessageSink(CoreSession session, MethodHandle methodHandle, boolean autoDemand, boolean validateSignature) + { + super(session, methodHandle, autoDemand); - // Validate onMessageMethod - Objects.requireNonNull(methodHandle, "MethodHandle"); - MethodType onMessageType = MethodType.methodType(Void.TYPE, ByteBuffer.class); - if (methodHandle.type() != onMessageType) + 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 void accept(Frame frame, Callback callback) { 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)); + callback.failed(new MessageTooLargeException(String.format("Binary message too large: %,d > %,d", size, maxSize))); + return; } - // If we are fin and no OutputStream has been created we don't need to aggregate. - if (frame.isFin() && (out == null)) + if (frame.isFin() && accumulator == null) { - if (frame.hasPayload()) - methodHandle.invoke(frame.getPayload()); - else - methodHandle.invoke(BufferUtil.EMPTY_BUFFER); - - callback.succeeded(); - session.demand(1); + invoke(getMethodHandle(), frame.getPayload(), callback); + autoDemand(); return; } - // Aggregate the frame payload. - if (frame.hasPayload()) + if (!frame.isFin() && !frame.hasPayload()) { - ByteBuffer payload = frame.getPayload(); - if (out == null) - out = new ByteBufferCallbackAccumulator(); - out.addEntry(payload, callback); + callback.succeeded(); + getCoreSession().demand(1); + return; } - // If the methodHandle throws we don't want to fail callback twice. - callback = Callback.NOOP; + 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 ec382cd45536..57c0006f57e1 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.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 0bcf0dab2257..72c65802387f 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 @@ 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 6024c22b3377..afd93bde9832 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.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 @@ 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()) + { + callback.succeeded(); + session.demand(1); + return; + } + + Runnable action = null; + try (AutoLock.WithCondition l = lock.lock()) { - // If closed or we have no payload, request the next frame. - if (closed || (!frame.hasPayload() && !frame.isFin())) + 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 int read() throws IOException } @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 int read(ByteBuffer buffer) throws IOException 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 int read(ByteBuffer buffer) throws IOException 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); + } - // Return number of bytes actually copied into buffer. + int length = BufferUtil.append(buffer, payload); + if (!payload.hasRemaining()) + succeedCurrentEntry(); + + // 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 void setTimeout(long timeoutMs) 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) - { - // Wait forever until a buffer is available. - result = buffers.take(); - } - else + while (true) { - // 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; - try (AutoLock l = lock.lock()) - { - currentEntry = result; - return currentEntry; + if (timeout < 0) + l.await(); + else if (!l.await(timeout, TimeUnit.MILLISECONDS)) + throw new IOException(String.format("Read timeout: %,dms expired", timeout)); } + + return currentEntry = result; } catch (InterruptedException e) { @@ -218,21 +265,15 @@ private Entry getCurrentEntry() throws IOException } } - 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; + return failure; } + } - @Override - public String toString() - { - return String.format("Entry[%s,%s]", BufferUtil.toDetailString(buffer), callback.getClass().getSimpleName()); - } + 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 f258473ed178..c84c0fc13122 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 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 @@ */ 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) - break; - if (read < 0) - { - endOfInput = true; + eof = read < 0; + if (eof || read == 0) 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 bf648bc5fadd..896ae3baad23 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 32f47717b52f..fd34a4b7c68f 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.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 void accept(Frame frame, Callback callback) { 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 215640637a70..cde238b56569 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 void accept(Frame frame, Callback callback) 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 37f34fd751ab..9b37e15cf451 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.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 void accept(Frame frame, Callback callback) { 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 c3350549a764..8f8e4f1759a1 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 @@ 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 40ab9d4978c6..87595d20bf4d 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.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 void accept(Frame frame, Callback callback) 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 void accept(Frame frame, Callback callback) { 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 945635824337..cd587a435dee 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 void testOutgoingAutoFragmentToMaxFrameSize() throws Exception // 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 void testOutgoingAutoFragmentToMaxFrameSize() throws Exception 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 void testIncomingAutoFragmentToMaxFrameSize() throws Exception 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 void testIncomingAutoFragmentWithPermessageDeflate() throws Exception 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 void testIncomingAutoFragmentWithPermessageDeflate() throws Exception 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 void testGzipBomb() throws Exception 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 void testGzipBomb() throws Exception 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 void testOutgoingAutoFragmentWithPermessageDeflate() throws Exception 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 597876cd0baf..bdf956869844 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 void onFrame(Frame frame, Callback callback) public void onError(Throwable cause, Callback callback) { callback.succeeded(); - _coreSession.demand(1); } @Override @@ -91,12 +90,6 @@ public void onClosed(CloseStatus closeStatus, Callback callback) { 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 112ed3df2c8f..365021d011f9 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 void onFrame(Frame frame, Callback callback) } 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 abeb5bebb191..0f3ff967f13b 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 void onFrame(Frame frame, Callback callback) { 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 01023ee72691..5c22ddb2fff8 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 void testSendSameFrameMultipleTimes() throws Exception 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 44243ba0dce5..5bf8b4f9e2b9 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 @@ protected void onBinary(ByteBuffer message, Callback callback) 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 b97a0c0011e1..5715268773d4 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 void onOpen(CoreSession coreSession, Callback callback) LOG.debug("[{}] onOpen {}", name, coreSession); this.coreSession = coreSession; callback.succeeded(); + coreSession.demand(1); openLatch.countDown(); } @@ -61,6 +62,7 @@ public void onFrame(Frame frame, Callback callback) 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 12d31e6b7f05..7fb8b865b06e 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 void onOpen(CoreSession coreSession) if (LOG.isDebugEnabled()) LOG.debug("onOpen {}", coreSession); this.coreSession = coreSession; + demand(); open.countDown(); } @@ -65,6 +66,12 @@ public void onFrame(Frame frame) 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 7e5d6c1ed4c2..7b131a3b901d 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 9f7420c5bf8d..8d4d7c954265 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 void onOpen(CoreSession coreSession, Callback callback) LOG.debug("onOpen {}", coreSession); this.coreSession = coreSession; state = this.coreSession.toString(); - opened.countDown(); callback.succeeded(); + opened.countDown(); } @Override @@ -582,11 +582,5 @@ public void onError(Throwable cause, Callback callback) 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 847bd5212c53..ad0a4716e7a1 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 void onHandshakeResponse(Request request, Response response) // 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 32d8363ebd77..8012efe24db9 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 @@ 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 @@ */ 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 void after() throws Exception 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 void testSendFrameInOnOpen() throws Exception 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 void testSendFrameInOnOpen() throws Exception @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 void testCloseInOnOpen() throws Exception { 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 void testAsyncOnOpen() throws Exception { throw new RuntimeException(e); } - return null; }); CoreSession coreSession = sx.exchange(null); @@ -182,11 +174,11 @@ public void testAsyncOnOpen() throws Exception 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 void onOpen(CoreSession coreSession, Callback callback) 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 770330324738..bb2882f64c3b 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 void runCaseByNumber(int caseNumber) throws Exception 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 4453a7ca25f8..0638c6e000a8 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 void onFrame(Frame frame, Callback callback) 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 a632794069f6..e3359d513f89 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 void parseIncomingHex(String... rawhex) byte[] net; // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); for (int i = 0; i < parts; i++) { @@ -102,8 +102,7 @@ public void parseIncomingHex(String... rawhex) 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 804ae990e42c..15110d91c043 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 void testIncomingFrames() throws Exception ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); // Quote List quote = new ArrayList<>(); @@ -131,7 +131,7 @@ public void testIncomingPing() 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 635f012ce70c..6db995869b9b 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 void testIncomingPing() 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 void testIncomingUncompressedFrames() ext.setNextIncomingFrames(capture); // Simulate initial demand from onOpen(). - coreSession.autoDemand(); + coreSession.demand(1); // Quote List quote = new ArrayList<>(); @@ -386,7 +386,7 @@ public void testIncomingFrameNoPayload() 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 7dfd5d1dfe13..5f3284ff0ebf 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 void onClosed(CloseStatus closeStatus, Callback callback) { 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 531d89b7cd24..a5484a9c1d10 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 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 @@ enum State 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 Client2Proxy implements FrameHandler public State getState() { - synchronized (this) + try (AutoLock ignored = lock.lock()) { return state; } @@ -81,16 +82,16 @@ public State getState() 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 @@ public void onOpen(CoreSession coreSession, Callback callback) 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 @@ private void onOpenSuccess(Callback callback) } 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 @@ private void onOpenFail(Callback callback, Throwable t) 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 @@ public void onFrame(Frame frame, Callback callback) 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 @@ public void onFrame(Frame frame, Callback callback) } 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 @@ public void onError(Throwable failure, Callback callback) } if (failServer2Proxy) - server2Proxy.fail(failure, callback); + proxy2Server.fail(failure, callback); else callback.failed(failure); } @@ -243,10 +269,10 @@ public void onError(Throwable failure, Callback callback) 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 @@ public void fail(Throwable failure, Callback callback) } 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 @@ public void fail(Throwable failure, Callback callback) 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 @@ public void onClosed(CloseStatus closeStatus, Callback callback) } if (abnormalClose) - server2Proxy.fail(new ClosedChannelException(), Callback.NOOP); + proxy2Server.fail(new ClosedChannelException(), Callback.NOOP); closed.countDown(); callback.succeeded(); @@ -303,11 +329,11 @@ public void onClosed(CloseStatus closeStatus, Callback callback) 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 @@ public void send(Frame frame, Callback callback) if (failure != null) callback.failed(failure); else - client.sendFrame(frame, sendCallback, false); + client2ProxySession.sendFrame(frame, sendCallback, false); } @Override @@ -347,9 +373,10 @@ public String toString() } } - 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 Server2Proxy implements FrameHandler public State getState() { - synchronized (this) + try (AutoLock ignored = lock.lock()) { return state; } @@ -369,10 +396,10 @@ public State getState() 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 @@ public void connect(Callback callback) 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 @@ public void connect(Callback callback) 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 @@ private void onConnectSuccess(CoreSession coreSession, Callback callback) } 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 @@ private void onConnectFailure(Throwable t, Callback callback) 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 @@ public void onOpen(CoreSession coreSession, Callback callback) 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 @@ public void onFrame(Frame frame, Callback callback) { state = State.ISHUT; closeCallback = callback; - sendCallback = Callback.from(() -> - {}, callback::failed); + sendCallback = Callback.from(() -> {}, callback::failed); + } + else + { + demand = true; } break; @@ -544,19 +580,35 @@ public void onFrame(Frame frame, Callback callback) } 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 @@ public void onError(Throwable failure, Callback callback) 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 @@ public void onClosed(CloseStatus closeStatus, Callback callback) 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 @@ public void fail(Throwable failure, Callback callback) } 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 @@ public void fail(Throwable failure, Callback callback) 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 @@ public void send(Frame frame, Callback callback) 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 780c54ae531f..f17998bdaf16 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 @@ 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 void stop() throws Exception _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 void testEcho() throws Exception { 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 void testEcho() throws Exception 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 void testEcho() throws Exception 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 void testFailServerUpgrade() throws Exception { 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 void onOpen(CoreSession coreSession, Callback callback) } }; 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 void testServerError() throws Exception { 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 void testServerError() throws Exception frame = serverFrameHandler.receivedFrames.poll(); assertNull(frame); - // Server2Proxy + // Proxy2Server frame = proxyServerSide.receivedFrames.poll(); assertNotNull(frame); closeStatus = CloseStatus.getCloseStatus(frame); @@ -328,7 +336,7 @@ public void testServerErrorClientNoResponse() throws Exception { 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 void onFrame(Frame frame, Callback callback) 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 void onFrame(Frame frame, Callback callback) 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 2ee6fa2d9721..d8e9483ade87 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 void testSimpleDemand() throws Exception 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 void testDemandAndRetain() throws Exception @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 void testTcpCloseNoDemand() throws Exception @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 void onFrame(Frame frame, Callback callback) 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 void onClosed(CloseStatus closeStatus) super.onClosed(closeStatus); } - @Override - public boolean isAutoDemanding() - { - return false; - } - @Override public void onFrame(Frame frame, Callback callback) { @@ -392,12 +378,6 @@ public void onOpen(CoreSession coreSession, Callback callback) 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 2cc0366bd2f6..6a24fc653bbe 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.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 @@ 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 4d0985d9159a..22fed88f8705 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 3feffda0e532..a5448b74b45b 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 void testMaxMessageSize() throws Exception @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 void testValidUtf8() throws Exception @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 void testUtf8Continuation() throws Exception @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 void testInvalidSingleFrameUtf8() throws Exception @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 c5607e8651f7..000000000000 --- 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 000000000000..74e90854ecf2 --- /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 4027e2751ce9..000000000000 --- 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 ec343d76f027..acaf306ea507 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 @@ 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. *

@@ -80,73 +109,64 @@ 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 + * 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} * - * @param duration the timeout duration (may not be null or negative) + * @param size the maximum allowed size of a text message. */ - void setIdleTimeout(Duration duration); + void setMaxTextMessageSize(long size); /** - * The input (read from network layer) buffer size. + * The maximum payload size of any WebSocket Frame which can be received. * - * @param size the size in bytes + * @return the maximum size of a WebSocket Frame. */ - void setInputBufferSize(int size); + long getMaxFrameSize(); /** - * The output (write to network layer) buffer size. + * The maximum payload size of any WebSocket Frame which can be received. + *

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

* - * @param size the size in bytes + * @param maxFrameSize the maximum allowed size of a WebSocket Frame. */ - void setOutputBufferSize(int size); + void setMaxFrameSize(long maxFrameSize); /** - * 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} - *

+ * If true, frames are automatically fragmented to respect the maximum frame size. * - * @param size the maximum allowed size of a binary message. + * @return whether to automatically fragment incoming WebSocket Frames. */ - void setMaxBinaryMessageSize(long size); + boolean isAutoFragment(); /** - * 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} + * If set to true, frames are automatically fragmented to respect the maximum frame size. * - * @param size the maximum allowed size of a text message. + * @param autoFragment whether to automatically fragment incoming WebSocket Frames. */ - void setMaxTextMessageSize(long size); + void setAutoFragment(boolean autoFragment); /** - * The maximum payload size of any WebSocket Frame which can be received. - *

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

+ * 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. * - * @param maxFrameSize the maximum allowed size of a WebSocket Frame. + * @return the max number of frames. */ - void setMaxFrameSize(long maxFrameSize); + int getMaxOutgoingFrames(); /** - * If set to true, frames are automatically fragmented to respect the maximum frame size. + * 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 autoFragment whether to automatically fragment incoming WebSocket Frames. + * @param maxOutgoingFrames the max number of frames. */ - void setAutoFragment(boolean autoFragment); + 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 5800bc57892e..000000000000 --- 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 3215c13577b8..aba060cb4a3c 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) - * @see #disconnect() + * @throws IllegalStateException if the WebSocket endpoint is auto-demanding */ - @Override - void close(); + void demand(); /** - * 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. + *

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 closeStatus the reason for the closure - * @see #close() - * @see #close(int, String) - * @see #disconnect() + * @param buffer the message bytes to send + * @param callback callback to notify when the send operation is complete */ - void close(CloseStatus closeStatus); + void sendBinary(ByteBuffer buffer, Callback callback); /** - * Send a websocket Close frame, with status code. - *

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

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 statusCode the status code - * @param reason the (optional) reason. (can be null for no reason) - * @see StatusCode - * @see #close() - * @see #close(CloseStatus) - * @see #disconnect() + * @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 close(int statusCode, String reason); + void sendPartialBinary(ByteBuffer buffer, boolean last, Callback callback); /** - * Send a websocket Close frame, with status code. - *

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

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 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() + * @param text the message text to send + * @param callback callback to notify when the send operation is complete */ - default void close(int statusCode, String reason, WriteCallback callback) - { - try - { - close(statusCode, reason); - callback.writeSuccess(); - } - catch (Throwable t) - { - callback.writeFailed(t); - } - } + void sendText(String text, Callback callback); /** - * 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. + *

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}.

* - * @see #close() - * @see #close(CloseStatus) - * @see #close(int, String) + * @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 disconnect(); + 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); /** - * 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} - *

+ *

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

* - * @return the SocketAddress for the local connection, or null if not supported by Session + * @param applicationData the data to be carried in the PONG frame + * @param callback callback to notify when the send operation is complete */ - SocketAddress getLocalAddress(); + void sendPong(ByteBuffer applicationData, Callback callback); /** - * Access the (now read-only) {@link WebSocketPolicy} in use for this connection. + *

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

* - * @return the policy in use + * @see #close(int, String, Callback) + * @see #disconnect() */ - default WebSocketPolicy getPolicy() + @Override + default void close() { - return this; + close(StatusCode.NORMAL, null, Callback.NOOP); } /** - * 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". + *

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.

* - * @return the protocol version + * @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 #disconnect() */ - String getProtocolVersion(); + void close(int statusCode, String reason, Callback callback); /** - * Return a reference to the RemoteEndpoint object representing the other end of this conversation. + *

Abruptly closes the WebSocket connection without sending a CLOSE frame.

* - * @return the remote endpoint + * @see #close(int, String, Callback) */ - RemoteEndpoint getRemote(); + void disconnect(); /** - * 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 + * @return the local SocketAddress for the connection, if available */ - SocketAddress getRemoteAddress(); + SocketAddress getLocalSocketAddress(); /** - * Get the UpgradeRequest used to create this session + * @return the remote SocketAddress for the connection, if available + */ + SocketAddress getRemoteSocketAddress(); + + /** + *

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 WebSocket protocol version + */ + String getProtocolVersion(); + + /** * @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 95d953a888a6..000000000000 --- 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 632b4970b909..000000000000 --- 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 cf6dd8b743f7..000000000000 --- 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 445900db0645..000000000000 --- 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 d61643a2d089..000000000000 --- 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 52869d0030de..000000000000 --- 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 cd317e5aa2a0..000000000000 --- 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 f238c736e1e3..000000000000 --- 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 c66b40bdc210..000000000000 --- 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 214889dee79a..5ffb023ee4b9 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.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 ab59ebd96382..838397db217a 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.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 711e0a40e49e..17a28bd96205 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.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 71bb4a472322..2e7029aa7792 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.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 @@ *
  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 216f9ba8a137..722d0a3e0971 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.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 89bf5af750cc..29f54f0b341c 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 @@ 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 bfd7fb65cdeb..000000000000 --- 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 247ed00f692f..3d9680c4adbf 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 @@ *

* 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 94b0379a261f..000000000000 --- 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 4579e7bf04ff..000000000000 --- 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 0b3071684c11..2d2105e94ac0 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.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.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 void dump(Appendable out, String indent) throws IOException dumpObjects(out, indent, getOpenSessions()); } - @Override - public WebSocketBehavior getBehavior() - { - return WebSocketBehavior.CLIENT; - } - @Override public void addSessionListener(WebSocketSessionListener listener) { @@ -217,12 +210,25 @@ public Duration getIdleTimeout() return configurationCustomizer.getIdleTimeout(); } + @Override + public void setIdleTimeout(Duration duration) + { + configurationCustomizer.setIdleTimeout(duration); + getHttpClient().setIdleTimeout(duration.toMillis()); + } + @Override public int getInputBufferSize() { return configurationCustomizer.getInputBufferSize(); } + @Override + public void setInputBufferSize(int size) + { + configurationCustomizer.setInputBufferSize(size); + } + @Override public int getOutputBufferSize() { @@ -230,70 +236,69 @@ public int getOutputBufferSize() } @Override - public long getMaxBinaryMessageSize() + public void setOutputBufferSize(int size) { - return configurationCustomizer.getMaxBinaryMessageSize(); + configurationCustomizer.setOutputBufferSize(size); } @Override - public long getMaxTextMessageSize() + public long getMaxBinaryMessageSize() { - return configurationCustomizer.getMaxTextMessageSize(); + return configurationCustomizer.getMaxBinaryMessageSize(); } @Override - public long getMaxFrameSize() + public void setMaxBinaryMessageSize(long size) { - return configurationCustomizer.getMaxFrameSize(); + configurationCustomizer.setMaxBinaryMessageSize(size); } @Override - public boolean isAutoFragment() + public long getMaxTextMessageSize() { - return configurationCustomizer.isAutoFragment(); + return configurationCustomizer.getMaxTextMessageSize(); } @Override - public void setIdleTimeout(Duration duration) + public void setMaxTextMessageSize(long size) { - configurationCustomizer.setIdleTimeout(duration); - getHttpClient().setIdleTimeout(duration.toMillis()); + configurationCustomizer.setMaxTextMessageSize(size); } @Override - public void setInputBufferSize(int size) + public long getMaxFrameSize() { - configurationCustomizer.setInputBufferSize(size); + return configurationCustomizer.getMaxFrameSize(); } @Override - public void setOutputBufferSize(int size) + public void setMaxFrameSize(long maxFrameSize) { - configurationCustomizer.setOutputBufferSize(size); + configurationCustomizer.setMaxFrameSize(maxFrameSize); } @Override - public void setMaxBinaryMessageSize(long size) + public boolean isAutoFragment() { - configurationCustomizer.setMaxBinaryMessageSize(size); + return configurationCustomizer.isAutoFragment(); } @Override - public void setMaxTextMessageSize(long size) + public void setAutoFragment(boolean autoFragment) { - configurationCustomizer.setMaxTextMessageSize(size); + configurationCustomizer.setAutoFragment(autoFragment); } @Override - public void setMaxFrameSize(long maxFrameSize) + public int getMaxOutgoingFrames() { - configurationCustomizer.setMaxFrameSize(maxFrameSize); + return configurationCustomizer.getMaxOutgoingFrames(); } @Override - public void setAutoFragment(boolean autoFragment) + public void setMaxOutgoingFrames(int maxOutgoingFrames) { - configurationCustomizer.setAutoFragment(autoFragment); + configurationCustomizer.setMaxOutgoingFrames(maxOutgoingFrames); } public SocketAddress 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 1f35d5174a61..6ff57fc31528 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.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 @@ */ 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 5a843c54dfe9..9c8a40090cf3 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 @@ 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 boolean awaitClose(int duration, TimeUnit unit) throws InterruptedExcepti 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 b6d982161a9f..b15a09a59fea 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 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 static class OpenTrackingSocket { 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 e777fd9778f8..9d9665dd49af 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 @@ 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.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.metadata = metadata; + + 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 UpgradeResponse getUpgradeResponse() return upgradeResponse; } - public BatchMode getBatchMode() - { - return batchMode; - } - public WebSocketSession getSession() { return session; @@ -150,8 +118,7 @@ public void onOpen(CoreSession coreSession, Callback callback) { 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 void onOpen(CoreSession coreSession, Callback callback) 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 void onOpen(CoreSession coreSession, Callback callback) 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(); + } } - @Override - public void onFrame(Frame frame, Callback callback) + private static MessageSink createMessageSink(Class sinkClass, WebSocketSession session, MethodHandle msgHandle, boolean autoDemanding) { - try (AutoLock l = lock.lock()) - { - switch (state) - { - case DEMANDING: - break; - - case SUSPENDING: - delayedOnFrame = () -> onFrame(frame, callback); - state = SuspendState.SUSPENDED; - return; + if (msgHandle == null) + return null; + if (sinkClass == null) + return null; - 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; + 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); + } + } - // Send to raw frame handling on user side (eg: WebSocketFrameListener) + @Override + public void onFrame(Frame frame, Callback coreCallback) + { + 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 void onError(Throwable cause, Callback callback) { 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 void onError(Throwable cause, Callback callback) } } - 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 @@ private void notifyOnClose(CloseStatus closeStatus, Callback callback) { if (closeHandle != null) closeHandle.invoke(closeStatus.getCode(), closeStatus.getReason()); - callback.succeeded(); } catch (Throwable cause) @@ -315,40 +291,6 @@ private void notifyOnClose(CloseStatus closeStatus, Callback callback) } } - 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 @@ private void onPingFrame(Frame frame, Callback callback) 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 @@ private void onPongFrame(Frame frame, Callback callback) 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; - - case SUSPENDING: - state = SuspendState.SUSPENDED; - break; - - default: - throw new IllegalStateException(state.name()); - } - } + return metadata.isAutoDemand(); + } - 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 db85ec467a1c..aa010794a536 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.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 @@ *

*
    *
  • 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 JettyWebSocketFrameHandlerMetadata getMetadata(Class endpointClass) 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 JettyWebSocketFrameHandler newJettyFrameHandler(Object endpointInstance) { 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 @@ private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method) private JettyWebSocketFrameHandlerMetadata createListenerMetadata(Class endpointClass) { JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata(); - MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); + metadata.setAutoDemand(Session.Listener.AutoDemanding.class.isAssignableFrom(endpointClass)); - if (!WebSocketConnectionListener.class.isAssignableFrom(endpointClass)) - throw new IllegalArgumentException("Class " + endpointClass + " does not implement " + WebSocketConnectionListener.class); + MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); - Method openMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketConnect", Session.class); - MethodHandle open = toMethodHandle(lookup, openMethod); - metadata.setOpenHandler(open, openMethod); + Method openMethod = findMethod(endpointClass, "onWebSocketOpen", Session.class); + if (openMethod != null) + { + MethodHandle connectHandle = toMethodHandle(lookup, openMethod); + metadata.setOpenHandle(connectHandle, openMethod); + } - Method closeMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketClose", int.class, String.class); - MethodHandle close = toMethodHandle(lookup, closeMethod); - metadata.setCloseHandler(close, closeMethod); + Method frameMethod = findMethod(endpointClass, "onWebSocketFrame", Frame.class, Callback.class); + if (frameMethod != null) + { + MethodHandle frameHandle = toMethodHandle(lookup, frameMethod); + metadata.setFrameHandle(frameHandle, frameMethod); + } - Method errorMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketError", Throwable.class); - MethodHandle error = toMethodHandle(lookup, errorMethod); - metadata.setErrorHandler(error, errorMethod); + Method pingMethod = findMethod(endpointClass, "onWebSocketPing", ByteBuffer.class); + if (pingMethod != null) + { + MethodHandle pingHandle = toMethodHandle(lookup, pingMethod); + metadata.setPingHandle(pingHandle, pingMethod); + } - // Simple Data Listener - if (WebSocketListener.class.isAssignableFrom(endpointClass)) + Method pongMethod = findMethod(endpointClass, "onWebSocketPong", ByteBuffer.class); + if (pongMethod != null) { - Method textMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketText", String.class); - MethodHandle text = toMethodHandle(lookup, textMethod); - metadata.setTextHandler(StringMessageSink.class, text, textMethod); + MethodHandle pongHandle = toMethodHandle(lookup, pongMethod); + metadata.setPongHandle(pongHandle, pongMethod); + } - Method binaryMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketBinary", byte[].class, int.class, int.class); - MethodHandle binary = toMethodHandle(lookup, binaryMethod); - metadata.setBinaryHandle(ByteArrayMessageSink.class, binary, binaryMethod); + Method partialTextMethod = findMethod(endpointClass, "onWebSocketPartialText", String.class, boolean.class); + if (partialTextMethod != null) + { + MethodHandle partialTextHandle = toMethodHandle(lookup, partialTextMethod); + metadata.setTextHandle(PartialStringMessageSink.class, partialTextHandle, partialTextMethod); } - // Ping/Pong Listener - if (WebSocketPingPongListener.class.isAssignableFrom(endpointClass)) + Method partialBinaryMethod = findMethod(endpointClass, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class, Callback.class); + if (partialBinaryMethod != null) { - Method pongMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPong", ByteBuffer.class); - MethodHandle pong = toMethodHandle(lookup, pongMethod); - metadata.setPongHandle(pong, pongMethod); + MethodHandle partialBinaryHandle = toMethodHandle(lookup, partialBinaryMethod); + metadata.setBinaryHandle(PartialByteBufferMessageSink.class, partialBinaryHandle, partialBinaryMethod); + } - Method pingMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPing", ByteBuffer.class); - MethodHandle ping = toMethodHandle(lookup, pingMethod); - metadata.setPingHandle(ping, pingMethod); + Method textMethod = findMethod(endpointClass, "onWebSocketText", String.class); + if (textMethod != null) + { + MethodHandle textHandle = toMethodHandle(lookup, textMethod); + metadata.setTextHandle(StringMessageSink.class, textHandle, textMethod); } - // Partial Data / Message Listener - if (WebSocketPartialListener.class.isAssignableFrom(endpointClass)) + Method binaryMethod = findMethod(endpointClass, "onWebSocketBinary", ByteBuffer.class, Callback.class); + if (binaryMethod != null) { - Method textMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialText", String.class, boolean.class); - MethodHandle text = toMethodHandle(lookup, textMethod); - metadata.setTextHandler(PartialStringMessageSink.class, text, textMethod); + MethodHandle binaryHandle = toMethodHandle(lookup, binaryMethod); + metadata.setBinaryHandle(ByteBufferMessageSink.class, binaryHandle, binaryMethod); + } - Method binaryMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class); - MethodHandle binary = toMethodHandle(lookup, binaryMethod); - metadata.setBinaryHandle(PartialByteBufferMessageSink.class, binary, binaryMethod); + Method errorMethod = findMethod(endpointClass, "onWebSocketError", Throwable.class); + if (errorMethod != null) + { + MethodHandle errorHandle = toMethodHandle(lookup, errorMethod); + metadata.setErrorHandle(errorHandle, errorMethod); } - // Frame Listener - if (WebSocketFrameListener.class.isAssignableFrom(endpointClass)) + Method closeMethod = findMethod(endpointClass, "onWebSocketClose", int.class, String.class); + if (closeMethod != null) { - Method frameMethod = ReflectUtils.findMethod(WebSocketFrameListener.class, "onWebSocketFrame", Frame.class); - MethodHandle frame = toMethodHandle(lookup, frameMethod); - metadata.setFrameHandler(frame, frameMethod); + 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann { // 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann 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 @@ private JettyWebSocketFrameHandlerMetadata createAnnotatedMetadata(WebSocket ann { // 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 db2f617971e9..0a5009558f8a 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 @@ 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 getBinarySink() 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 MethodHandle getCloseHandle() 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 MethodHandle getErrorHandle() 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 MethodHandle getFrameHandle() 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 MethodHandle getPongHandle() 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 3db93a71fcb6..000000000000 --- 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 2868c656c82f..23447fc5c099 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.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 CompletableFuture shutdown() 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 f3bf94fc4b27..8999602a90d1 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 @@ 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,106 +47,159 @@ public WebSocketSession(WebSocketContainer container, CoreSession coreSession, J 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); + callback = Objects.requireNonNullElse(callback, Callback.NOOP); + Frame frame = switch (messageType) + { + 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 close(int statusCode, String reason, WriteCallback callback) + public void sendText(String text, Callback callback) { - coreSession.close(statusCode, reason, Callback.from(callback::writeSuccess, callback::writeFailed)); + 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 WebSocketBehavior getBehavior() + public void sendPartialText(String text, boolean last, Callback callback) { - switch (coreSession.getBehavior()) + 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) { - case CLIENT: - return WebSocketBehavior.CLIENT; - case SERVER: - return WebSocketBehavior.SERVER; - default: - return 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 Duration getIdleTimeout() + public void sendPing(ByteBuffer applicationData, Callback callback) { - return coreSession.getIdleTimeout(); + coreSession.sendFrame(new Frame(OpCode.PING).setPayload(applicationData), + org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail), false); } @Override - public int getInputBufferSize() + public void sendPong(ByteBuffer applicationData, Callback callback) { - return coreSession.getInputBufferSize(); + coreSession.sendFrame(new Frame(OpCode.PONG).setPayload(applicationData), + org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail), false); } @Override - public int getOutputBufferSize() + public void close(int statusCode, String reason, Callback callback) { - return coreSession.getOutputBufferSize(); + coreSession.close(statusCode, reason, org.eclipse.jetty.util.Callback.from(callback::succeed, callback::fail)); } @Override - public long getMaxBinaryMessageSize() + public Duration getIdleTimeout() { - return coreSession.getMaxBinaryMessageSize(); + return coreSession.getIdleTimeout(); } @Override - public long getMaxTextMessageSize() + public void setIdleTimeout(Duration duration) { - return coreSession.getMaxTextMessageSize(); + coreSession.setIdleTimeout(duration); } @Override - public long getMaxFrameSize() + public int getInputBufferSize() { - return coreSession.getMaxFrameSize(); + return coreSession.getInputBufferSize(); } @Override - public boolean isAutoFragment() + public void setInputBufferSize(int size) { - return coreSession.isAutoFragment(); + coreSession.setInputBufferSize(size); } @Override - public void setIdleTimeout(Duration duration) + public int getOutputBufferSize() { - coreSession.setIdleTimeout(duration); + return coreSession.getOutputBufferSize(); } @Override - public void setInputBufferSize(int size) + public void setOutputBufferSize(int size) { - coreSession.setInputBufferSize(size); + coreSession.setOutputBufferSize(size); } @Override - public void setOutputBufferSize(int size) + public long getMaxBinaryMessageSize() { - coreSession.setOutputBufferSize(size); + return coreSession.getMaxBinaryMessageSize(); } @Override @@ -153,18 +208,36 @@ 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 void setAutoFragment(boolean autoFragment) } @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 void disconnect() } @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 UpgradeResponse getUpgradeResponse() 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 CoreSession getCoreSession() @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 000000000000..4d9b6b3ef267 --- /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 000000000000..479e90cb7797 --- /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 6cc0c3d91ce3..91552bd21dc3 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 @@ private 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 void onWebSocketClose(int statusCode, String reason) } @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 void onWebSocketText(String message) } } - public static class ListenerFrameSocket implements WebSocketFrameListener + public static class ListenerFrameSocket implements Session.Listener.AutoDemanding { public EventQueue events = new EventQueue(); @@ -90,9 +86,9 @@ public void onWebSocketClose(int statusCode, String reason) } @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 void onWebSocketError(Throwable cause) } @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 void onWebSocketClose(int statusCode, String reason) } @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 void onWebSocketPartialText(String payload, boolean fin) } @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 void onWebSocketClose(int statusCode, String reason) } @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 void onWebSocketPong(ByteBuffer payload) @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 void onClose(int statusCode, String reason) 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 void onClose(int statusCode, String reason) 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 void onClose(int statusCode, String reason) 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 static void onText(Session session, String text) } } + @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 void frameMe(Frame frame) 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 void echoBin(byte[] buf, int offset, int length) public static class MyEchoSocket { private Session session; - private RemoteEndpoint remote; - public RemoteEndpoint getRemote() + @OnWebSocketOpen + public void onOpen(Session session) { - return remote; - } - - @OnWebSocketClose - public void onClose(int statusCode, String reason) - { - this.session = null; + this.session = session; } - @OnWebSocketConnect - public void onConnect(Session session) + public Session getSession() { - this.session = session; - this.remote = session.getRemote(); + return session; } @OnWebSocketMessage @@ -453,14 +412,13 @@ public void onText(String message) 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 static class MyStatelessEchoSocket @OnWebSocketMessage public void onText(Session session, String text) { - session.getRemote().sendString(text, null); + session.sendText(text, null); } } @@ -498,8 +456,8 @@ public static class NoopSocket */ 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 e1801ca9a391..b4ccd135e94a 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.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 @@ private JettyWebSocketFrameHandler newLocalFrameHandler(Object wsEndpoint) 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 void onWebSocketClose(int statusCode, String reason) } @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 void testConnectionListener() // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketClose\\([^\\)]*\\)"); } @@ -222,7 +221,7 @@ public void testListenerPartialSocket() // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketPartialText\\(\"Hello\", false\\)", "onWebSocketPartialText\\(\" \", false\\)", "onWebSocketPartialText\\(\"World\", true\\)", @@ -252,9 +251,9 @@ public void testListenerBasicSocket() // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketText\\(\"Hello World\"\\)", - "onWebSocketBinary\\(\\[12\\], 0, 12\\)", + "onWebSocketBinary\\(\\[12\\]\\)", "onWebSocketClose\\(NORMAL, \"Normal\"\\)" ); } @@ -274,7 +273,7 @@ public void testListenerBasicSocketError() // Validate Events socket.events.assertEvents( - "onWebSocketConnect\\([^\\)]*\\)", + "onWebSocketOpen\\([^\\)]*\\)", "onWebSocketError\\(\\(RuntimeException\\) \"Nothing to see here\"\\)" ); } @@ -298,7 +297,7 @@ public void testListenerFrameSocket() // 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 void testListenerPingPongSocket() // 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 701c74c5b2f9..98cf8a877a74 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 void testAnnotatedBadSignatureStatic() throws Exception 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); - - 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()); + assertThrows(InvalidWebSocketException.class, () -> createMetadata(EndPoints.BadAutoDemandWithInputStream.class)); + } - 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 void testAnnotatedMyEchoBinarySocket() throws Exception 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 void testListenerBasicSocket() 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 fe233f30aa3e..025e1e8ee354 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.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 void testBasicAppendRead() throws IOException { 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 void testBasicAppendRead() throws IOException @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 void testBlockOnRead() throws Exception { 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 void testBlockOnReadInitial() throws IOException { 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 void testReadByteNoBuffersClosed() throws IOException { 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 void testAppendEmptyPayloadRead() throws IOException { 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 void testAppendNullPayloadRead() throws IOException { 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 8a15dcaa0da6..c8970e284a1c 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 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 OutgoingMessageCapture() 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 OutgoingMessageCapture() } @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 void sendFrame(Frame frame, Callback callback, boolean batch) 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 void sendFrame(Frame frame, Callback callback, boolean batch) 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 void sendFrame(Frame frame, Callback callback, boolean batch) 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 void onWholeText(String msg) } @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 8ac340d22ef1..f0fa7c3e0feb 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 cf84ed378b86..037e060d04ee 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 @@ 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 void onText(Session session, String message) { 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 e7166867c278..42ae6e7112e8 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 void onWebSocketClose(int statusCode, String reason) } @Override - public void onWebSocketConnect(Session session) + public void onWebSocketOpen(Session session) { this.outbound = session; } @@ -54,7 +57,7 @@ public void onWebSocketText(String message) { 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 71af709fa06c..475f7910c9d9 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.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 @@ * 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 void notifySessionListeners(Consumer consumer) } } - @Override - public WebSocketBehavior getBehavior() - { - return configuration.getBehavior(); - } - @Override public Duration getIdleTimeout() { @@ -203,6 +196,18 @@ public void setAutoFragment(boolean autoFragment) 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 void setInvocationType(InvocationType invocationType) 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 7b3cf15a9afc..2ddeb1ca3442 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 @@ 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 94305bfe218e..eb12483e66d9 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.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 @@ 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 static class MessageSegment } @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 void onMessage(ByteBuffer buffer, boolean last) 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 void testAnnotatedPartialString() throws Exception 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 void testAnnotatedPartialByteBuffer() throws Exception 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 fe50e8497763..ebc6453f6130 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.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.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 void onWebSocketClose(int statusCode, String reason) } @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 void onWebSocketText(String message) } @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 61f0166487cc..7852522711d6 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 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 void testConcurrentConnect() throws Exception 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 f741f464c5de..fefd22873261 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 fd93c0546eaf..0c90af8522a2 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.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 void onHandshakeRequest(Request request) { // 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 000000000000..7173bcc54526 --- /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 235c606b1227..e0fef023d45f 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 @@ 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 0df60aed83a4..b817e395b3ab 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.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 void testOnMessageThrows() throws Exception 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 void testWebSocketThrowsAfterOnMessageError(String methodToThrow) throws 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 44c6db9d512d..81f4d8b67430 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.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 void onMessage(String message) throws IOException } @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 void onError(Throwable cause) @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 e5d37107b788..263ecd5840d1 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 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 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 void stop() throws Exception } @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 void testSuspendWhenProcessingFrame() throws Exception 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 fdf15fc562d5..9c8a4f1677d9 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 ac6d18fa9266..6d2a2d04760e 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 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 void changeStatusCodeInOnClose() throws Exception 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 void secondCloseFromOnCloseFails() throws Exception 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 void abnormalStatusDoesNotChange() throws Exception 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 933765e41156..71e8dfd80e9b 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.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 void onHandshakeResponse(Request request, Response response) 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 235f729386b3..e42d19ee679f 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.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 @@ void testDeflate() throws Exception 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 0d280eecf36b..0e55a17a7930 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 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 String getName() } @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 void sendFrame(Frame frame, Callback callback, boolean batch) } } - public static class CountingCallback implements WriteCallback + public static class CountingCallback implements Callback { private final CountDownLatch successes; @@ -120,13 +118,13 @@ public CountingCallback(int count) } @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 void testMaxOutgoingFrames() throws Exception 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 090ca4818829..11415b63462f 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 void onConnect(Session session) throws IOException 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 4030ae37ca13..b203f14e0c1a 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 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 void testEcho() throws Exception 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 98e87039aaa6..ae759521335e 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.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 void testTextHandler() throws Exception 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 void testBinaryHandler() throws Exception 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 static class BinaryOnlyHandler extends AbstractHandler 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 void onClose(int statusCode, String reason) 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 054f01936035..a10fcd14d369 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.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 void echoStatsTest() throws Exception { 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 fde36bc70efc..7ae24bedda45 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 void testWriteAfterStop() throws Exception 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 void testWriteAfterStop() throws Exception 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 519e1c3f2f17..acbc20e61afc 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 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 int getCaseCount() throws IOException, InterruptedException 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 0585add746e3..8d2e538f280d 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 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 void testAbruptServerClose() throws Exception 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 void testAbruptServerClose() throws Exception 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 ad13d3eee5ca..ecf61d5e5af6 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.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 @@ private Session confirmConnection(CloseTrackingEndpoint clientSocket, Future @@ -375,7 +381,8 @@ public void testWriteException() throws Exception 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 void testWriteException() throws Exception // 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 void testWriteException() throws Exception } } - 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 void onWebSocketText(String message) 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 @@ else if (message.equals("block")) else { // simple echo - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } } catch (Throwable t) @@ -448,17 +457,6 @@ else if (message.equals("block")) } } - @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 void onWebSocketError(Throwable cause) } @Override - public void onWebSocketFrame(Frame frame) + public void onWebSocketFrame(Frame frame, Callback callback) { if (frame.getOpCode() == OpCode.CLOSE) { @@ -478,12 +476,12 @@ public void onWebSocketFrame(Frame frame) { 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 @@ else if (reason.equals("abort")) 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 @@ else if (reason.startsWith("sleep|")) 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 e0391f2c43c6..2c7547e58400 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.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 void stop() throws Exception 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 EventSocket getClientSocket(String param) 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 void testMaxBinaryMessageSize(String param) throws Exception 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 void testIdleTimeout(String param) throws Exception 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 void testMaxTextMessageSize(String param) throws Exception 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 89028aca0488..3a570c1acc38 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 @@ private E assertExpectedError(ExecutionException e, CloseT 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 8252280be0be..d666fda82b76 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 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 void onWebSocketSessionClosed(Session session) 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 void onWebSocketSessionClosed(Session session) 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 c8636e22c504..710ddc02f56f 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 @@ 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 void run() { 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 void run() 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 c29cdcc7639b..1ecf68ef5fd1 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.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 void testAbortDuringCreator() throws Exception 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 void onWebSocketSessionCreated(Session session) 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 void onHandshakeResponse(Request request, Response response) 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 void testAbortOnOpened() throws Exception 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 void onWebSocketConnect(Session session) // 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 void testAbortAfterCompletion() throws Exception 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 void testAbortWithExceptionAfterUpgrade() throws Exception 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 void onWebSocketConnect(Session session) // 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 f54636e62cd0..326a4781dabe 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 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 void testClientSlowToSend() throws Exception 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 9744137da867..0806c4c09814 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.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.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 void testBasicEchoFromClient() throws Exception 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 void testBasicEchoPartialUsageFromClient() throws Exception 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 void testBasicEchoPartialTextWithPartialBinaryFromClient() throws Excepti 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 void testBasicEchoPartialTextWithPartialBinaryFromClient() throws Excepti "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 void testBasicEchoUsingCallback() throws Exception 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 void testBasicEchoFromServer() throws Exception // 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 void testLocalRemoteAddress() throws Exception 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 void testMaxMessageSize() throws Exception 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 8d7d2253b70d..e7e0ccb374ed 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 void onWebSocketError(Throwable thr) 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 856b94ed51a1..000000000000 --- 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 e1071e6de32b..06999f10c0e7 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 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,58 +34,58 @@ public static Stream getBinaryListeners() 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(); } - } - public static class OffsetByteBufferPartialListener extends AbstractListener implements WebSocketPartialListener - { @Override - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketBinary(ByteBuffer payload, Callback callback) { - sendBinary(payload, fin); + getSession().sendPartialBinary(payload, true, Callback.from(() -> + { + callback.succeed(); + getSession().demand(); + }, callback::fail)); } } - @WebSocket - public static class AnnotatedByteBufferWholeListener extends AbstractAnnotatedListener + public static class OffsetByteBufferPartialListener extends Session.Listener.Abstract { - @OnWebSocketMessage - public void onMessage(ByteBuffer message) + @Override + public void onWebSocketOpen(Session session) { - sendBinary(message, true); + super.onWebSocketOpen(session); + session.demand(); } - } - @WebSocket - public static class AnnotatedByteArrayWholeListener extends AbstractAnnotatedListener - { - @OnWebSocketMessage - public void onMessage(byte[] message) + @Override + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { - sendBinary(BufferUtil.toBuffer(message), true); + getSession().sendPartialBinary(payload, fin, Callback.from(() -> + { + callback.succeed(); + getSession().demand(); + }, callback::fail)); } } @WebSocket - public static class AnnotatedOffsetByteArrayWholeListener extends AbstractAnnotatedListener + public static class AnnotatedByteBufferWholeListener extends AbstractAnnotatedListener { @OnWebSocketMessage - public void onMessage(byte[] message, int offset, int length) + public void onMessage(ByteBuffer message, Callback callback) { - sendBinary(BufferUtil.toBuffer(message, offset, length), true); + _session.sendPartialBinary(message, true, callback); } } @@ -104,9 +103,9 @@ public void onMessage(InputStream stream) 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 873a89761b1d..7c4b8129baf7 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.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 static Stream getTextListeners() ).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 0470c8d10991..e552dfb4e5c3 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.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 void testTextListeners(Class clazz) throws Exception // 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 void testBinaryListeners(Class clazz) throws Exception // 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 void testAnonymousListener() throws Exception 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 void onWebSocketClose(int statusCode, String reason) // 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 f046e34884f2..e7b21021f17b 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 WebSocketProxy(WebSocketClient webSocketClient, URI serverUri) this.serverUri = serverUri; } - public WebSocketConnectionListener getWebSocketConnectionListener() + public Session.Listener getSessionListener() { return clientToProxy; } @@ -68,7 +66,7 @@ public boolean awaitClose(long timeout) } } - 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 Session getSession() 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 void onWebSocketPartialText(String payload, boolean fin) 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 void onWebSocketPing(ByteBuffer payload) 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 void onWebSocketPong(ByteBuffer payload) 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 void onWebSocketClose(int statusCode, String reason) 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 void fail(Throwable failure) // 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 void onWebSocketPartialText(String payload, boolean fin) 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 void onWebSocketPing(ByteBuffer payload) 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 void onWebSocketPong(ByteBuffer payload) 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 void onWebSocketClose(int statusCode, String reason) 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 f906b682e250..95ad1df8a930 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.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 void before() throws Exception 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 void testEcho() throws Exception 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 void testServerThrowsOnMessage() throws Exception 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 void timeoutTest() throws Exception 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 void testPingPong() throws Exception 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 void testPingPong() throws Exception // 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 void testPingPong() throws Exception 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 static class PingPongSocket extends EventSocket 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 75316e35db30..1d65952727cf 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.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.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 AbstractCloseEndpoint() } @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 void onWebSocketClose(int statusCode, String reason) 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 4a35dc0bae36..52c9c59afb0a 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 c4323d418d09..5cd8a2980f04 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 @@ 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 void onWebSocketClose(int statusCode, String reason) 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 9f1b750d64e2..8ee793c2dd47 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 @@ 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 void onWebSocketText(String message) { 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 2cbafe14a3cd..032673a76a19 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.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 void testDynamicConfiguration() throws Exception 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 boolean handle(Request request, Response response, Callback callback) thr 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 a25f57e7be4e..337e93feb37c 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 @@ 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 f95ba16bdaa6..3e9776afbc25 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 @@ 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 30a8a17af80f..e606ffb97aff 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.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 void testPartialText() throws Exception { 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,16 +140,21 @@ public static class FrameEndpoint public CountDownLatch closeLatch = new CountDownLatch(1); public LinkedBlockingQueue frameEvents = new LinkedBlockingQueue<>(); - @OnWebSocketClose - public void onWebSocketClose(int statusCode, String reason) + @OnWebSocketOpen + public void onWebSocketOpen(Session session) { - closeLatch.countDown(); + this.session = session; } - @OnWebSocketConnect - public void onWebSocketConnect(Session session) + @OnWebSocketFrame + 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(); } @OnWebSocketError @@ -159,14 +163,10 @@ 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 f124f5a5bd34..682f2c9dcd0e 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.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 void testPartialText() throws Exception { 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 void testPartialText() throws Exception } } - 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 void onWebSocketError(Throwable cause) } @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 629be35e8941..9fc6d6f589bd 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.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 void testPartialText() throws Exception { 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 void testPartialBinary() throws Exception { 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 void testPartialTextBinaryText() throws Exception { 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,41 +216,45 @@ public void testPartialTextBinaryText() throws Exception } } - 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 onWebSocketError(Throwable cause) + public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin, Callback callback) { - cause.printStackTrace(System.err); + // 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 - public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin) + public void onWebSocketError(Throwable cause) { - // our testcases always send bytes limited in the US-ASCII range. - partialEvents.offer(String.format("BINARY[payload=<<<%s>>>, fin=%b]", BufferUtil.toUTF8String(payload), fin)); + cause.printStackTrace(System.err); } @Override - public void onWebSocketPartialText(String payload, boolean fin) + public void onWebSocketClose(int statusCode, String reason) { - 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 e454be331a35..18f093a5ecd3 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.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 void fastFail() throws Exception 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 void testOpenSessionCleanup() throws Exception { 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 void testSecondCloseFromOnClosed() throws Exception // 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 void testSecondCloseFromOnClosedInNewThread() throws Exception // 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 165ae27bba57..5fb63b6acc77 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.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 @@ private EventSocket getServerEndpoint(String path) return switch (path) { case "servletConfig", "containerConfig" -> standardEndpoint; - case "annotatedConfig" -> annotatedEndpoint; case "sessionConfig" -> sessionConfigEndpoint; default -> throw new IllegalStateException(); }; @@ -81,12 +80,7 @@ private EventSocket getServerEndpoint(String path) 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 static WebSocketUpgradeHandler from(Server server, ContextHandler context } } - 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 void start() throws Exception 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 void testMaxBinaryMessageSize(String path) throws Exception 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 void testIdleTimeout(String path) throws Exception 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 void testMaxTextMessageSize(String path) throws Exception 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 b60bfcbafd7f..47bda829ded5 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 void onMessage(Session session, String msg) { 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 void onMessage(Session session, String msg) 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 1df42ff23dee..082f9a3b80e7 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 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 void testServerSlowToSend() throws Exception 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 670ba0d98c3f..27fbe4f5cdee 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 @@ 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 void writeFailed(Throwable cause) } @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 30a1aec38ca5..f0d5e4295207 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.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 static class EchoSocket @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 3749a4919618..c15db6da22f9 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 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 void testGetEcho() throws Exception 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 41436dcf6871..b5b43620c6bc 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.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 void configure(JettyWebSocketServletFactory factory) 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 void onMessage(String data) } // 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 a230af843814..5090a98dcb0b 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 @@ 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 boolean awaitClose(int duration, TimeUnit unit) throws InterruptedExcepti @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 86127afa9d43..a6f0f6c0816f 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 @@ 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 ResourceService getResourceService() @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 void init() throws ServletException 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 void init() throws ServletException 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 void init() throws ServletException } _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 void init() throws ServletException 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 @@ private String getInitParameter(String name, String... deprecated) 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 @@ protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws S 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 ca24b2e423cc..a283cf7ad06c 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.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 void testBasicEcho() throws Exception 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 static class CdiEchoSocket private Session session; - @OnWebSocketConnect + @OnWebSocketOpen public void onOpen(Session session) { logger.info("onOpen() session:" + session); @@ -136,7 +137,7 @@ public void onOpen(Session session) @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 f2aa9387a8f1..7639a677e146 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 @@ 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 @@ /** * 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 void onClose(int statusCode, String reason) 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 8fc6f60ce111..c1422eb48791 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.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 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) 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 static class ClientSocket 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 ab9d3a420062..2296e5243726 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.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 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) 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 static class ClientSocket 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 6f894aefc220..fda0e4dd1511 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 void demand(Runnable demandCallback) 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 5e17f9311a9e..e9773e4a9153 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 @@ protected void doClientStart() { 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 0acf34f1d0de..08990ec01a1c 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 6893eb98b28c..8527b3a6951e 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 void onError(Throwable cause, Callback callback) } } - @Override - public boolean isAutoDemanding() - { - return false; - } - public Set getMessageHandlers() { return messageHandlerMap.values().stream() @@ -600,6 +594,11 @@ public void onPing(Frame frame, Callback callback) { 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 void onPong(Frame frame, Callback callback) // 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 void onContinuation(Frame frame, Callback callback) { 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 3d0174cb5904..dd90ab38a138 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 static MessageSink createMessageSink(JakartaWebSocketSession session, Jak 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 aa18950a2f0d..d3715baf8940 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws Exception 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 5de362c1e96b..6e7b34c3ce0a 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws Exception 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 1f480a0f8562..6b9f98b6b134 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws NoSuchMethodException 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 36c901988ee9..59bed6e8b854 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws Exception 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 f8a26ac940a1..d759362e8363 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 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 static void initSession() throws Exception .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 static void stopContainer() throws Exception 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 f399c870f607..4e21e76d317d 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 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 void testCalendar1Frame() throws Exception 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 void testCalendar3Frames() throws Exception 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 9a20624855fd..b91f0e28702e 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.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.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 void testInputStream1Message1Frame() throws InterruptedException, Executi { 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 void testInputStream2Messages2Frames() throws InterruptedException, Execu { 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 void testInputStream2Messages2Frames() throws InterruptedException, Execu 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 void testInputStream1Message3Frames() throws InterruptedException, Execut { 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 void testInputStream1Message4FramesEmptyFin() throws InterruptedException { 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 57c2f9c12394..af3264e8d91d 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 void testReader1Frame() throws InterruptedException, ExecutionException, 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 void testReader3Frames() throws InterruptedException, ExecutionException, 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 8568d8c0d24a..b91a1d654fbe 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 void onOpen(Session session, EndpointConfig endpointConfig) 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 void onOpen(Session session, EndpointConfig endpointConfig) 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 void onMessage(String message) throws IOException 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 void onMessage(ByteBuffer message) throws IOException 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 void onClose(CloseReason reason) 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 b93d8ffcf060..bb127c34cbc0 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 void onOpen(CoreSession coreSession, Callback callback) this.coreSession = coreSession; this.openLatch.countDown(); callback.succeeded(); + coreSession.demand(1); } @Override @@ -236,6 +237,7 @@ public void onFrame(Frame frame, Callback callback) { 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 58d838fbf02a..0a8de6276e29 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 void onOpen(CoreSession coreSession, Callback callback) { 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 08262a4c35c6..eb8a4ed5348a 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 void afterEach() throws Exception } @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 void testCloseInOnWebSocketConnect() throws Exception 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 03d7436a576b..0a9df6bd8563 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.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 void websocketProvidedByWebApp() throws Exception 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 6241d7986c65..f40c49749bf4 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 2c0f1683f5c9..90391e090304 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.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.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(); @@ -285,15 +284,15 @@ public Collection getOpenSessions() } @Override - public WebSocketBehavior getBehavior() + public Duration getIdleTimeout() { - return WebSocketBehavior.SERVER; + return customizer.getIdleTimeout(); } @Override - public Duration getIdleTimeout() + public void setIdleTimeout(Duration duration) { - return customizer.getIdleTimeout(); + customizer.setIdleTimeout(duration); } @Override @@ -302,6 +301,12 @@ public int getInputBufferSize() return customizer.getInputBufferSize(); } + @Override + public void setInputBufferSize(int size) + { + customizer.setInputBufferSize(size); + } + @Override public int getOutputBufferSize() { @@ -309,69 +314,69 @@ public int getOutputBufferSize() } @Override - public long getMaxBinaryMessageSize() + public void setOutputBufferSize(int size) { - return customizer.getMaxBinaryMessageSize(); + customizer.setOutputBufferSize(size); } @Override - public long getMaxTextMessageSize() + public long getMaxBinaryMessageSize() { - return customizer.getMaxTextMessageSize(); + return customizer.getMaxBinaryMessageSize(); } @Override - public long getMaxFrameSize() + public void setMaxBinaryMessageSize(long size) { - return customizer.getMaxFrameSize(); + customizer.setMaxBinaryMessageSize(size); } @Override - public boolean isAutoFragment() + public long getMaxTextMessageSize() { - return customizer.isAutoFragment(); + return customizer.getMaxTextMessageSize(); } @Override - public void setIdleTimeout(Duration duration) + public void setMaxTextMessageSize(long size) { - customizer.setIdleTimeout(duration); + customizer.setMaxTextMessageSize(size); } @Override - public void setInputBufferSize(int size) + public long getMaxFrameSize() { - customizer.setInputBufferSize(size); + return customizer.getMaxFrameSize(); } @Override - public void setOutputBufferSize(int size) + public void setMaxFrameSize(long maxFrameSize) { - customizer.setOutputBufferSize(size); + customizer.setMaxFrameSize(maxFrameSize); } @Override - public void setMaxBinaryMessageSize(long size) + public boolean isAutoFragment() { - customizer.setMaxBinaryMessageSize(size); + return customizer.isAutoFragment(); } @Override - public void setMaxTextMessageSize(long size) + public void setAutoFragment(boolean autoFragment) { - customizer.setMaxTextMessageSize(size); + customizer.setAutoFragment(autoFragment); } @Override - public void setMaxFrameSize(long maxFrameSize) + public int getMaxOutgoingFrames() { - customizer.setMaxFrameSize(maxFrameSize); + return customizer.getMaxOutgoingFrames(); } @Override - public void setAutoFragment(boolean autoFragment) + public void setMaxOutgoingFrames(int maxOutgoingFrames) { - customizer.setAutoFragment(autoFragment); + customizer.setMaxOutgoingFrames(maxOutgoingFrames); } @Override 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 5700481e3b41..39f5886ac347 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 @@ 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 d2260c4597ee..63a34ae8a76c 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 @@ 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.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 void run() 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 BrowserSocket(String ua, String reqExts) 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 void onTextMessage(String message) } 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 void onTextMessage(String message) // 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 @@ private void writeMessage(String message) } // 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 e1810f05fd75..c8b810199c66 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.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 void afterEach() throws Exception } @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 void testCloseInOnWebSocketConnect() throws Exception 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 a7f759c0e1ca..d3eea015991f 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 @@ 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 866e92564175..9fa65cd008b4 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.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 void onMessage(String message) throws IOException } @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 void onClose(int statusCode, String reason) 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 void onError(Throwable cause) @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 bfd400a7b47d..2b4cd4e104b9 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.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 static class ClientSocket { 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 static class EchoSocket @OnWebSocketMessage public void onMessage(Session session, String message) throws Exception { - session.getRemote().sendString(message); + session.sendText(message, Callback.NOOP); } } @@ -162,7 +163,7 @@ public WebAppTester.WebApp createWebSocketWebapp(String contextName) throws Exce // 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 6a12637b51a1..9f64616172c6 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.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 void testWebSocketUpgradeFilter() throws Exception 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 void testLazyWebSocketUpgradeFilter() throws Exception 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 void init() 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 void testMultipleWebSocketUpgradeFilter() throws Exception 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 void testMultipleWebSocketUpgradeFilter() throws Exception 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 void testCustomUpgradeFilter() throws Exception 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 void testDefaultWebSocketUpgradeFilterOrdering() throws Exception 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 void testWebSocketUpgradeFilterOrdering() throws Exception 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 static class EchoSocket 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 static class LowerCaseEchoSocket 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 static class UpperCaseEchoSocket 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 6da5e7393c8b..05bfdecffab3 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.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 @@ private void testEchoMessage() throws Exception 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 a351742b8cdd..1aa1f8589ac8 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.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 void testPathSpecAttribute() throws Exception 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 ParamWebSocketEndPoint(String param) @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 c1802eb8169c..988fe64e4143 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.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 void echoTest() throws Exception 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 40d8bcb864aa..2bb9c620f9d0 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 45ea5e82b0a3..4f89c271173b 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 6b5e706addac..5c20184d7972 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.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.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 void testPermessageDeflateAggregation() throws Exception 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 void testPermessageDeflateFragmentedBinaryMessage() throws Exception 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 void testClientPartialMessageThenServerIdleTimeout() throws Exception 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 void testClientPartialMessageThenClientClose() throws Exception 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 void testServerPartialMessageThenServerIdleTimeout() throws Exception 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 void testServerPartialMessageThenClientClose() throws Exception 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 void testServerPartialMessageThenClientClose() throws Exception @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 void onOpen(Session session) } @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 static class FailEndPointOutgoing 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 static class FailEndPointIncoming 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 ae1a9aebe388..3ca8d1bcabe3 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.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 void testProgrammaticWebSocketUpgrade() throws Exception 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 e29f3b79d897..064b81ccb0a5 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.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 @@ private EventSocket getServerEndpoint(String path) return switch (path) { case "servletConfig", "containerConfig" -> standardEndpoint; - case "annotatedConfig" -> annotatedEndpoint; case "sessionConfig" -> sessionConfigEndpoint; default -> throw new IllegalStateException(); }; @@ -81,12 +80,7 @@ private EventSocket getServerEndpoint(String path) 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 void configure(JettyWebSocketServletFactory factory) } } - 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 void start() throws Exception 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 void testMaxBinaryMessageSize(String path) throws Exception 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 void testIdleTimeout(String path) throws Exception 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 void testMaxTextMessageSize(String path) throws Exception 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 9e3cb9c7914f..0bab0daac550 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.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 @@ private void startServer(TestJettyWebSocketServlet servlet) throws Exception 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 @@ private void testWebSocketOverDynamicTransport(FunctionA4^01zC_SOsB}OGT#cu>cqVrD(=N zdNgDHS&W6F=}rHsAf;$}^|MGB2%tNA^8YjdxFCe--wQAR1kOqiO-#}Ck&VG8))Pn#Bz(?eKUb&Nha~qzhIBNt+>-sRp}puUUj#DoLPg6w z$9*AC!tg2TJfxqEIXVx~GaMElN9g{ra85qK81%a+3LuH^W@IJ{vv$qf9~KIzHGOXk9|N@8wv2Gj^q}-M#VL zUOsguE;bXJ2Ev6JwLD3Q@wpQ%wDtWotFDN-K(N*Ql3a5X%`i<(MZayaR$BwL? zP-YMX$uU!_t?GHzjc(E~uKRlG@E5T@gXVmdvyV2zLOd3VKbebhsziQpt4yv(Ay_+_ z-Dl{a#F5RF<|%cng&owG#p~I6<-ckow-* zfRA>4MKD7e54m-VCK$^#?cB)V0^y#&QF~{#kM#c9j**Vvg#y>e-F2o6Cx>mlY2e$P zD;ZqJy?gbf5Cii+BT-IY&l@{wVbc`{dfwvITFfTcnCLiYA}Z_S&c(0I^HUUnP*o0h z%1P6+_;)H(=J(7m2VYqT6dF<0KdLeeNWHadhsgL0JvZOHq#;*UtU2*w9d2c{PY`Z# z7q^Ja=rfUF4d1aO<9{5tGjpZSio2}OL)f{ejb@<#924H8(TT8|t zs9d$Uy5GsrP;wns1l6_dh3zPOT~lIf$Vs<(L^Q~aeoj5ml4N4QQC}|u+fdg9tAwHui@^(PC58#pg&qswvlnG!Li*Fl2Qe}&tmRR=tVCS z2ekssWW!+wefY;9FMRH;OJAZ!-Z;weSg6;Of6NKOOiMz&)8vCScoAv5$42#FLj2u7U~eUEhcns#iO&vmQ>xK< zHUJT9WdP9Lvw#gV!6b^r)(1~p?`Mv=+U`e^u_4WluUurgJDD=YJmN)DJp>@{ru!%b zW68!oxh`{DB5H)Lk)ST(Wb&tBQOwgg1a%vl{Fk(rZ^BHF;Ca1aI2x?~KVX!i!HV=~ z@a3~u?(C!>%>Uto2>?8E=EoT@EB~ho=(j4;wT&bvZ`iBfs?ek9-@hnW3mJ+=HVZC` z>UY)UJ={AQ96c19+GLz00Qjh8H;3S40!Lp6Wp*-<4st_|I5uB5U~gLROF;3J{~fB< z)G)&NHjRbX;I2yG^E|$cp#{FKT3zEJ$@OVLLL3x{^?JbJ@aSlOM4S&2&Y{RIHMQ5X zjDKNwHUm>=*?TbN(?lfu1-4+!|Wwa(9+)xEu zajP`HP(IQouchNTcME4f)L)UZiYdNc=PD0FVyFgO2%=;n8H0U)sA8#uwOI=tU54Jk z+k0g=!7dg)o>8c{`*?xbUMz70%;6rMfMapq-&m2k7YvwDR&8Ha`P?q36&}f#aQN4F zARR`9q6p$!OlA5&D9ylWCXYKHaM{%oEZGeu8^H*HednNX{9!rn2X+9qpqf!NmC zx-q|M>D55C3{LNy^69Jmo&NEwNTTg_m4q%?2Hs^YPZhT|bGL3zbLavO$ee>Gxr*Rp zU6x7*Xe)CdqJFfWqJ3D5Es# zM0|`OaIdby3s+4vT%|Sq@b8n)aHXiiudw1{Y%O9DZ^)>B5|7wV%#CW$BBL zfP_AJO#yFF+zMP1VvU&`=v)ZXO?=ZP$Z`5(GME=LO9)DIi5T_n&TL+h+U)XQ20g_+ zJY_Pmb7?wdi7Kt!m^1MtXD@%3uE^+=nGG|zKr-g11cdj2s>3NXTUUmJJZS^las!ij zEYzbmoxKK{e|HvzihU9ssMFUpPiT3bbx%{nY3M=SPHBR2tzD(AehdEQ{$ISGPlAtp zY5LWiWrxVM0{SNfXlbm~hss+EweiUN4JIVbmrUm?pUx3IH+7Q|`ZD`|nR)n+&6wnm zg*26s6uA7fab$Ptz#`DtQD5-R9nLRC_+0A+cD1b{Y63emkiqSmS9~g+7A307aXM10 z<%$u#@I$e5L2gI9hjPsn(+HU18p$q#JH5=vEnVmGv8%cNNpMRoPbL`lvqDwc4#F^G z<9Ae*G+xd9Qe7%l{F@^GjJsckttA1q73}(}!ZtkwyAw2W$bxZNE_glxt0n)@O*>#6 zsBiU`$;G5-^C(Ed#s8}#BaZ?vJvU!zk?rBuCz2C$1)VjyhTOMR@ENWE7lkuIz!zCT qKz=YCJ@+QG$EQu(N&pRTtMA>#eKmjz#n3~YrzrDW%~|ummHZb7Y^aO? literal 0 HcmV?d00001 diff --git a/jetty-ee10/jetty-ee10-websocket/pom.xml b/jetty-ee10/jetty-ee10-websocket/pom.xml index 6ec675fd69f8..e0b87510e458 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 b8f2c56ba740..063b9b323ca4 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 8161ea798f5d..ef8a91d98bd3 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 0d913746acf9..efea52a59bce 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 void onError(Throwable cause, Callback callback) } } - @Override - public boolean isAutoDemanding() - { - return false; - } - public Set getMessageHandlers() { return messageHandlerMap.values().stream() @@ -600,6 +594,11 @@ public void onPing(Frame frame, Callback callback) { 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 void onPong(Frame frame, Callback callback) // 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 void onContinuation(Frame frame, Callback callback) { 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 7c4b05eada81..80e1158f2abd 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 static MessageSink createMessageSink(JakartaWebSocketSession session, Jak 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 1f29181fdac0..1eab5c76a87b 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws Exception 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 9c1adc2aff1d..d7d07ad4859f 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws Exception 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 ef4bebfe2edb..e7091d4596ef 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws NoSuchMethodException 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 630c4b0d25d3..47993416cf15 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 @@ MessageSink newMessageSink(CoreSession coreSession) throws Exception 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 0ef16e60493b..aaa05b69ccdc 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 void testCalendar1Frame() throws Exception 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 void testCalendar3Frames() throws Exception 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 0b54dd11c4be..47863ff32713 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 void testCalendar1Frame() throws Exception 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 void testCalendar3Frames() throws Exception 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 9cff849b7a93..f4c08ab3172e 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 void testDate1Frame() throws Exception 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 void testDate3Frames() throws Exception 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 8a661836365b..4dd8cd533ee6 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 void testDate1Frame() throws Exception 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 void testDate3Frames() throws Exception 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 23e9f2923a50..56ccec6089ac 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.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 void testInputStream1Message1Frame() throws InterruptedException, Executi { 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 void testInputStream2Messages2Frames() throws InterruptedException, Execu { 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 void testInputStream1Message3Frames() throws InterruptedException, Execut { 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 void testInputStream1Message4FramesEmptyFin() throws InterruptedException { 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 void testInputStream1Message4FramesEmptyFin() throws InterruptedException 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 828abd72e3dc..795422ecc4f3 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 void testReader1Frame() throws InterruptedException, ExecutionException, 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 void testReader3Frames() throws InterruptedException, ExecutionException, 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 0e62e36dd1fe..201b31b9ddae 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 void onOpen(CoreSession coreSession, Callback callback) this.coreSession = coreSession; this.openLatch.countDown(); callback.succeeded(); + coreSession.demand(1); } @Override @@ -236,6 +237,7 @@ public void onFrame(Frame frame, Callback callback) { 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 62f2ec694728..79314e3c1cfc 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 void onOpen(CoreSession coreSession, Callback callback) { 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 9dc383df5437..29170635ba99 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 void afterEach() throws Exception } @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 void testCloseInOnWebSocketConnect() throws Exception 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 14664b918962..f3949f875d82 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 2c113553c977..7de2dcb48ab3 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 @@ 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 @@ private enum SuspendState 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 void onOpen(CoreSession coreSession, Callback callback) 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 void onOpen(CoreSession coreSession, Callback callback) 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 void onOpen(CoreSession coreSession, Callback callback) @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 void onFrame(Frame frame, Callback callback) 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 @@ private void onCloseFrame(Frame frame, Callback callback) @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 @@ private void onTextFrame(Frame frame, Callback callback) 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 void resume() { boolean needDemand = false; Runnable delayedFrame = null; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -485,7 +461,7 @@ public void resume() private void demand() { boolean demand = false; - try (AutoLock l = lock.lock()) + try (AutoLock ignored = lock.lock()) { switch (state) { @@ -517,8 +493,8 @@ public static Throwable convertCause(Throwable cause) 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 static Throwable convertCause(Throwable cause) 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 bc57346689da..51040f628a6d 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.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 JettyWebSocketFrameHandler newJettyFrameHandler(Object endpointInstance) 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 static MessageSink createMessageSink(MethodHandle msgHandle, Class { - 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 void testBasicAppendRead() throws IOException @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 void testBlockOnRead() throws Exception { 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 void testBlockOnReadInitial() throws IOException { 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 void testReadByteNoBuffersClosed() throws IOException { 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 void testAppendEmptyPayloadRead() throws IOException { 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 void testAppendNullPayloadRead() throws IOException { 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 void testAppendNullPayloadRead() throws IOException @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 3852244e1f5e..86689457b196 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 void sendFrame(Frame frame, Callback callback, boolean batch) 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 void sendFrame(Frame frame, Callback callback, boolean batch) 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 3bb3ac445949..d4ca3bb55771 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 void onOpen(Session session) 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 void onOpen(Session session) 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 void onMessage(byte[] buf, int offset, int len) throws IOException { 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 void onMessage(byte[] buf, int offset, int len) throws IOException 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 void onClose(int statusCode, String reason) public void onError(Throwable cause) { if (LOG.isDebugEnabled()) - LOG.debug("{} onError(): {}", toString(), cause); + LOG.debug("{} onError()", this, cause); error = cause; errorLatch.countDown(); }