Towards a new websocket model #38473
Replies: 17 comments 67 replies
-
In theory, we could also support other scopes. If
We could also provide a session scoped bean for the |
Beta Was this translation helpful? Give feedback.
-
What's the exact reasoning for this? |
Beta Was this translation helpful? Give feedback.
-
For those following - I just posted the first ideas about the client side. @maxandersen, I've tried to find a way to avoid the giant switch we generally see. I'm not totally happy, but it should work. |
Beta Was this translation helpful? Give feedback.
-
Kotlin coroutines should be supported out-of-the-box, this means Note: There is still a long way to go before the virtual threads are really usable and there are still some RFCs to be finalized (e.g. structured concurrency). That will take a few more years. Kotlin coroutines, on the other hand, already work today and are in widespread use. For this reason, Kotlin users should not be ignored here 😉. |
Beta Was this translation helpful? Give feedback.
-
FTR the minimal POC (that aims to test the ideas from this proposal) can be found here: https://github.com/mkouba/quarkus/tree/websockets-next So far it's very basic, contains a ton of TODOs, and only covers the functionality used in the |
Beta Was this translation helpful? Give feedback.
-
@brunobat I need your brain for the tracing part. Does OTEL says anything about web socket? |
Beta Was this translation helpful? Give feedback.
-
I have a question about Serialization / Deserialization: The current proposal only implies that only text data is supported, but from what I understand, there are real world cases where a binary representation of objects is used mainly to improve performance. |
Beta Was this translation helpful? Give feedback.
-
In one part we use "AndAwait" in another "Async" - shouldn't it be consistent? And I would say my preference is that its "Async" as that seems to be more correct (linguistically at least), but happy to be shown/explained otherwise |
Beta Was this translation helpful? Give feedback.
-
Intuitively this makes sense, but do we have a strong reason to enforce it? |
Beta Was this translation helpful? Give feedback.
-
Where does authentication and authorization fit into this model? When a server receives a connection request, where can the application consume the endpoint path and headers and add custom authorization checks? |
Beta Was this translation helpful? Give feedback.
-
I would love to see abstraction that allows full control of the handshake. I am currently stuck between a rock and hard place with websockets in Quarkus:
|
Beta Was this translation helpful? Give feedback.
-
Hi @cescoffier :) This is a very nice API! But I see no support for closing WS connection with the code and reason bytes as per websocket spec here https://websockets.spec.whatwg.org/#dom-websocket-close Do you plan to have support for this case? I can imagine API to be something like: public class WebSocketServerConnection {
/**
* Closes the WebSocket connection.
*
* @return a new {@link Uni} that completes when the connection is closed successfully
*/
@CheckReturnValue
Uni<Void> close();
/**
* Closes the WebSocket connection with code (integer equal to 1000 or in the range 3000 to 4999) and reason (max 123 bytes long).
*
* @return a new {@link Uni} that completes when the connection is closed successfully
*/
@CheckReturnValue
Uni<Void> close(int code, String reason);
/**
* Closes the WebSocket connection and waits for the completion.
*/
void closeAndAwait();
/**
* Closes the WebSocket connection with code (integer equal to 1000 or in the range 3000 to 4999) and reason (max 123 bytes long)
* and waits for the completion.
*/
void closeAndAwait(int code, String reason); And in the @WebSocket("my/path")
public class MyWebSocketEndpoint {
@OnClose
void onClose(int code, String reason) {
System.out.println(code); // prints 1000
if (reason != null) {
System.out.println(reason); // prints whatever reason is
}
}
@OnClose
void onClose(int code) {
System.out.println(code); // prints 1000 in case of normal close or a different number according to spec
}
@OnClose
void onClose() {
// ignore the code and reason
} A default WS close code is always References: |
Beta Was this translation helpful? Give feedback.
-
Hi, I was just curious about how this implementation (API Interfaces only I assume) works with java eco system. After some reading., I see that.Specifications
Implementations
Q1. So, now with this new interface change will this not work with these webservers as they are supposed to be according to specifications? |
Beta Was this translation helpful? Give feedback.
-
In Quarkus when using Vertx (as almost all of our stack does), there is no
web server, so most of the points above don't apply.
What was done was to build a declarative API on top of the Vertx Websockets
programmatic API (similarly to what we did with RESTEasy Reactive/Quarkus
REST)
…On Sun, Apr 7, 2024, 18:18 Nithin Bandaru ***@***.***> wrote:
Hi,
I was just curious about how this implementation (API Interfaces only I
assume) works with java eco system.
After some reading., I see that. Specifications
- JSR 356: Part of Java EE
- Jakarta WebSocket: Part of Jakarta EE, just a renaming done for JSR
356.
Implementations
- Apache Tomcat
- GlassFish
- WildFly
- Payara Server
- Tyrus
- Undertow
- Jetty
Interestingly I see that all implementations are actually part of Web
server hosts and not application framework itself.
Q1. So, now with this new interface change will this not work with these
webservers as they are supposed to be according to specifications?
Q2. So, does this mean you guys did a complete implementation of WebSocket
Server and Client and will be part of Application framework itself or new
APIs will act just as a wrapper?
Q3. I see that there is use of Vert.x WebSocket API, is this
implementation of WebSocket and this new API is acting like a wrapper for
Vert.x and this is like having full implementation of WebSocket itself in
Application framework code instead of having it in web server hosts?
Q4. Vert.x does not satisfy Jakarta WebSocket or JSR 356 specification,
right?
Q5. Did I get it all wrong 😀?
—
Reply to this email directly, view it on GitHub
<#38473 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABBMDP73WJDVQXFO3GWKERLY4FPTVAVCNFSM6AAAAABCQ5SURKVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4TAMZWGU4DK>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Hello Quarkus team.
|
Beta Was this translation helpful? Give feedback.
-
The client-side ADR is now ready for review: #40333. It does not contain many details as it's the direct companion of the server-side. |
Beta Was this translation helpful? Give feedback.
-
Is there any plan to implement fallback handling like https://quarkus.io/guides/vertx-reference#bidirectional-communication-with-browsers-by-using-sockjs ? |
Beta Was this translation helpful? Give feedback.
-
Hello,
WIP
For some time now, we have been thinking about improving how to handle web sockets in Quarkus. This discussion presents some ideas for enhancement.
The goal is to simplify the development of application serving (server-side) and consuming (client-side) web sockets. The proposed model aims to be coherent with the Quarkus development model. The proposal also covers some integration points, such as observability and service discovery.
Current situation (<= Quarkus 3.7/3.8)
At the moment, to implement a web socket (on the server side), we use
undertow,
and the code relies on the JSR 356.This model needs to be better integrated with the rest of Quarkus. CDI scopes are a mystery when using a web socket; it does not use a duplicated context (breaking tracing), and metrics are limited.
However, the goal is not to replace this current support but to provide an alternative. Applications using this legacy model must still be supported using the same extension as Today.
Structure of this document
This document distinguishes three parts:
While the server and client sides are covered in this document, they are independent and do not depend on each other.
Serving web sockets (server-side)
NOTE: The annotations utilized in this section differ from those in JSR 356 despite sharing the same name. The JSR annotations carry a semantic that the proposal does not follow. Furthermore, incorporating multiple sets of annotations could have confused the user.
Overall idea
To serve a web socket, the user needs to create a class annotated with
@WebSocket
, indicating the path of the web socket. In addition, the user would have to create methods annotated with:OnMessage
to receive messages and optionally reply to messagesOnOpen
to be notified when a new client connects to the web socketOnClose
to be notified when a client disconnects from the web socketThe following sections will describe the supported signatures.
Furthermore, the method can declare a static inner class representing sub-websockets. A sub-web socket is another web socket endpoint located under the main one. For example, if the main class is annotated with
@WebSocket("my/path")
and the inner class with@WebSocket("sub-ws")
, the framework exposes:my/path
my/path/sub-ws
The following code gives some examples of usage:
Note that both classes are transformed into CDI beans by the framework and thus can access the other beans exposed by the application.
Web Socket endpoint
A WebSocket endpoint comprises the following components:
@OnMessage
method: Handles the messages the connected client sends.@OnOpen
method: Invoked when a client connects to the WebSocket.@OnClose
method: Executed upon the client disconnecting from the WebSocket.Only some endpoints need to include all three methods. However, it must contain at least
@OnMessage
or@OnOpen
.If any endpoint violates these rules, an error must be thrown.
The inner classes representing sub-websockets adhere to the same guidelines.
WebSocketServerConnection
The
WebSocketServerConnection
interface represents a connection to a client. This structure facilitates various actions, such as sending messages to the connected client or broadcasting to all connected clients, closing the current connection, and accessing path parameters.A
WebSocketServerConnection
can be obtained as a CDIsession-scoped
bean, as explained in the [[#CDI Scope]] section.The API offers blocking and non-blocking methods for transmitting messages by utilizing the
WebSocketServerConnection
. The non-blocking methods returnUni
instances, providing flexibility in handling asynchronous operations:Note that this class provides both reactive and imperative methods. Imperative methods are suffixed with
andAwait
and block the caller thread until the operation's completion. Errors are reported using exceptions.Declaring a WebSocket Endpoint with the
@WebSocket
AnnotationThe
@WebSocket
annotation declares a WebSocket endpoint and is exclusive to class-level declarations. Its parameter (value
) denotes the path of the WebSocket.WebSocket Endpoint Declaration
The
@WebSocket
annotation is used to define a WebSocket endpoint. Consequently, methods annotated with@OnMessage
,@OnOpen
, and@OnClose
are considered within its context. When managing the resulting endpoint, following the guidelines outlined in the [[#WebSocket Endpoint]] section is very important.Any methods annotated with
OnMessage
,OnOpen
, andOnClose
outside a WebSocket endpoint are considered erroneous and should be flagged during the build process.Path Parameters
The path specified in the
@WebSocket
annotation can incorporate path parameters, such as/ws/product/{id}
. Methods can retrieve these values utilizing theWebSocketServerConnection#pathParam(id)
method:Note that query parameters are not supported.
Inner Classes and Sub-Web Sockets
A class annotated with
@WebSocket
can encapsulate inner classes also annotated with@WebSocket
, representing sub-web sockets. The resulting path of these sub-web sockets concatenates the path from the enclosing class and the inner class. The resulting path must be normalized, following the HTTP URL rules.Sub-web sockets inherit access to the path parameters declared in the
@WebSocket
annotation of both the enclosing and inner classes. In the example, theconsumePrimary
method within the enclosing class can access theversion
parameter. Meanwhile, theconsumeNested
method within the inner class can access bothversion
andid
.CDI Scopes for WebSocket Endpoints
Classes annotated with
@WebSocket
are intended to be managed as CDI beans, allowing for flexible scope management within the application.By default, WebSocket endpoints are considered in the singleton pseudo-scope. However, developers can specify alternative scopes to suit their specific requirements.
Furthermore, each WebSocket connection is associated with its own session scope. When the
@OnOpen
method is invoked, a session scope corresponding to the WebSocket connection is established. Subsequent calls to@OnMessage
or@OnClose
methods utilize this same session scope. The session scope remains active until the@OnClose
method completes execution, at which point it is terminated.The
WebSocketServerConnection
object, which represents the connection itself, is also a session-scoped bean, allowing developers to access and manage WebSocket-specific data within the context of the session.In cases where a WebSocket endpoint does not declare an
@OnOpen
method, the session scope is still created and remains active until the connection terminates, regardless of the presence of an@OnClose
method.Methods annotated with
@OnMessage
,@OnOpen
and@OnClose
do not have the request scope activated by default. The user can activate it using the@ActivateRequestScope
. In this case, the scope is terminated when the method completes its execution.Duplicated Context
Each WebSocket connection is associated with a duplicated context, providing a segregated execution environment for handling WebSocket interactions.
Upon invocation of the
@OnOpen
method, a duplicated context specific to the WebSocket connection is established. Subsequent invocations of@OnMessage
or@OnClose
methods utilize this duplicated context.The duplicated context may be utilized as a transport mechanism for the session scope, facilitating the management of WebSocket-specific state and data.
Relationship Between
@WebSocket
and@Path
Although there might be an initial inclination to leverage the
@Path
annotation from JAX-RS for WebSocket endpoints, such an approach may not be advisable due to nuanced differences in semantics, particularly concerning sub-resources.WebSocket Path and HTTP Root
WebSocket paths configured within the application are concatenated with the root path defined by
quarkus.http.root
(which defaults to/
). This concatenation ensures that WebSocket endpoints are appropriately positioned within the application's URL structure.Method Annotations to serve WebSocket endpoints
This section delineates the behavior associated with methods annotated with
@OnMessage
,@OnClose
, and@OnOpen
.Invocation Rules
When invoking these annotated methods:
The proposed framework supports blocking and non-blocking logic, akin to RESTEasy Reactive, determined by the method signature and additional annotations such as
@Blocking
and@NonBlocking
.Here are the rules governing execution:
@RunOnVirtualThread
are considered blocking and should execute on a virtual thread.@RunOnVirtualThread
.@RunOnVirtualThread
is employed, each invocation spawns a new virtual thread.For a WebSocket connection, the methods
OnMessage
,OnClose
, andOnOpen
must not be invoked concurrently.Receiving Messages with
@OnMessage
Methods marked with
OnMessage
trigger upon receiving a message from a connected client.Parameters
These methods can accept parameters in two formats:
Multi<X>
withX
as the message type.Any other parameters should be flagged as errors.
The message object represents the data sent and can be accessed as either raw content (
String
orbyte[]
) or deserialized high-level objects, which is the recommended approach.When receiving a
Multi
, the method is invoked once per connection, and the providedMulti
receives items transmitted by this connection. The method must subscribe to theMulti
to receive these items (or return aMulti
). Cancelling this subscription closes the associated connection.Allowed Returned Types
Methods annotated with
@OnMessage
can return various types to handle WebSocket communication efficiently:void
: Indicates a blocking method where no explicit response is sent back to the client.Uni<Void>
: Denotes a non-blocking method where the completion of the returnedUni
signifies the end of processing. no explicit response is sent back to the client.X
: Represents a blocking method where the returned object is serialized and sent back to the client as a response.Uni<X>
: Specifies a non-blocking method where the item emitted by the non-nullUni
is sent to the client as a response.Multi<X>
: Indicates a non-blocking method where the items emitted by the non-nullMulti
are sequentially sent to the client until completion or cancellation.When returning a
Multi
, the framework subscribes to the returnedMulti
and writes the emitted items until completion, failure, or cancellation. Failure or cancellation necessitates the termination of the connection.Raw Payload
Items sent back to the client are serialized except for the
String,
Buffer
, andbyte[]
types, offering flexibility in data transmission.Strings
are sent as text messages.Buffers
andbyte
arrays are sent as binary messages.Skipping reply
When a method is intended to produce a message written to the client, it can emit
null
. Emittingnull
signifies no response to be sent to the client, allowing for skipping a response when needed.Json Object and Json Array
Vert.x `JSON Object and JSON Array instances are also bypassing the serialization and deserialization mechanism. Messages are sent as text message.
Bi-directional Streaming
Methods annotated with
@OnMessage
, handling both incoming and outgoingMulti
, facilitate bi-directional streaming seamlessly.Broadcasting
By default, responses produced by
@OnMessage
methods are sent back to the connected client. However, using thebroadcast
parameter, responses can be broadcasted to all connected clients .The same principle applies to methods returning instances of
Multi
orUni
.Request Scope
When the user explicitly enables the request scope for a method annotated with
@OnMessage
, the duration of the request scope is contingent upon the method's signature:void
).Uni
, the request scope concludes when the returnedUni
emits an item or encounters a failure.Multi
, the request scope concludes when the returnedMulti
emits a completion signal or encounters a failure event.@onopen and @onclose Annotations
These annotations inform the user about client connections and disconnections from the WebSocket.
@OnOpen
is triggered upon client connection, while@OnClose
is invoked upon disconnection.These methods have access to the session-scoped
WebSocketServerConnection
bean.Parameters
Methods annotated with
@OnOpen
and@OnClose
do not accept any parameters. If such methods declare parameters, they should be flagged as errors and reported to the user.Allowed Returned Types
@OnOpen
and@OnClose
methods support different returned types.For
@OnOpen
methods, the same rules as@OnMessage
apply. Thus, a method annotated with@OnOpen
can send messages to the client immediately after connecting. The supported return types for@OnOpen
methods are:void
: Indicates a blocking method where no explicit message is sent back to the connected client.Uni<Void>
: Denotes a non-blocking method where the completion of the returnedUni
signifies the end of processing. No message is sent back to the client.X
: Represents a blocking method where the returned object is serialized and sent back to the client.Uni<X>
: Specifies a non-blocking method where the item emitted by the non-nullUni
is sent to the client.Multi<X>
: Indicates a non-blocking method where the items emitted by the non-nullMulti
are sequentially sent to the client until completion or cancellation.Items sent to the client are serialized except for the
String
and,Buffer
byte[]
types. In the case ofMulti
, the framework must subscribe to the returnedMulti
and write the items to the WebSocket as they are emitted.Strings
are sent as text messages.Buffers
andbyte
arrays are sent as binary messages.For
@OnClose
methods, the allowed return types are:void
: The method is considered blocking.Uni<Void>
: The method is considered non-blocking.@OnClose
methods cannot send items to the connection client.Server-side Streaming
Methods annotated with
@OnOpen
can utilize server-side streaming by returning aMulti<X>
:Broadcasting with @onopen
Similar to
@OnMessage
, items sent to the client from a method annotated with@OnOpen
can be broadcasted to all clients instead of just the connecting client:Serialization and Deserialization
In WebSocket communication, items received and sent undergo automatic serialization and deserialization processes by default, utilizing JSON (Jackson). However, developers can implement custom serializers and deserializers for specific requirements. The
encode
(serialization) anddecode
(deserialization) methods are packaged in a codec class.Codecs implementation must be exposed as CDI beans. When declared within the session scope, they must align with the session scope of the WebSocket connection.
Notably, the types
String,
JsonObject,
JsonArray,
Buffer,
andbyte[]
bypass serialization and deserialization processes. For strings, JSON Object, and JSON Array, the default encoding isUTF-8
, as mandated by the WebSocket specification, and the messages are sent as text messages.Buffer
andbyte[]
are binary messages.The default serialization/deserialization uses JSON (Jackson). Thus, messages would be received and sent as text messages.
Consuming Web Sockets (Client-Side)
The client side has been reworked and proposed as an ADR:
See #40333.
Observability Support
Metrics
Server Side
On the server side, we should expose, per path, the following metrics:
Client Side
On the client side, per client, we should expose the following metrics:
Tracing
Because web sockets do not allow "headers" or "baguages", tracing implementation is complicated.
Beta Was this translation helpful? Give feedback.
All reactions