Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #9552 - Jetty 12 - Rewrite of the Jetty WebSocket APIs. #9652

Merged
merged 26 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6ae9ad9
Fixes #9552 - Jetty 12 - Rewrite of the Jetty WebSocket APIs.
sbordet Apr 14, 2023
504f01a
* Removed @WebSocket annotation attributes, because they were just a …
sbordet Apr 15, 2023
5e84a81
* Removed Session.suspend() and SuspendToken, and introduced Session.…
sbordet Apr 15, 2023
a7e1b61
* Fixed introspection of listener and annotated methods for binary fr…
sbordet Apr 17, 2023
768bc30
A few test fixes.
sbordet Apr 17, 2023
1d946a8
Modified ee9 and ee10 implementations according to the new semantic f…
sbordet Apr 21, 2023
1e5814d
Removed FrameHandler.isAutoDemanding() and CoreSession.isAutoDemandin…
sbordet Apr 21, 2023
dc285f5
Fixed flaky test, where the DispatchedMessageSink
sbordet Apr 24, 2023
4a3bd0c
* Renamed @OnWebSocketConnect to @OnWebSocketOpen
sbordet Apr 24, 2023
363064b
Updated tests to avoid statics.
sbordet Apr 24, 2023
1e5f2fd
* Explicitly throwing from Session.demand() if endpoint is auto-deman…
sbordet Apr 26, 2023
690e765
Updated javadocs from review.
sbordet Apr 26, 2023
766e9a7
Improved test failure report.
sbordet Apr 26, 2023
b7eec59
Merged branch 'jetty-12.0.x' into 'fix/jetty-12-9552-rewrite-websocke…
sbordet Apr 27, 2023
dcd8738
Updates from review.
sbordet Apr 27, 2023
f0e6df0
Javadocs updates from review.
sbordet Apr 28, 2023
4a276b6
Trying to isolate JakartaClientShutdownWithServerWebAppTest failures.
sbordet Apr 28, 2023
7044e65
Another attempt at isolating JakartaClientShutdownWithServerWebAppTes…
sbordet Apr 28, 2023
b6a18b5
No dice at isolating JakartaClientShutdownWithServerWebAppTest failur…
sbordet Apr 28, 2023
ab57f08
Adding @Isolated to try to fix JakartaClientShutdownWithServerWebAppT…
sbordet Apr 29, 2023
7922689
Javadoc updates from review.
sbordet Apr 29, 2023
1b6a894
Javadoc updates from review.
sbordet Apr 29, 2023
a6d1cd5
Fixed flaky test.
sbordet Apr 29, 2023
8d54103
Updated wording in javadoc.
sbordet Apr 30, 2023
4a3f437
Updated wording in javadoc.
sbordet May 1, 2023
de74809
Merged branch 'jetty-12.0.x' into 'fix/jetty-12-9552-rewrite-websocke…
sbordet May 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -84,15 +88,15 @@ 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]
----
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.
Expand All @@ -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) { ... }
----
====

Expand Down Expand Up @@ -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]
----
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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:
joakime marked this conversation as resolved.
Show resolved Hide resolved

[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
Expand Down
Loading