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 |
What's the exact reasoning for this? |
|
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. |
|
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 😉. |
|
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 |
|
@brunobat I need your brain for the tracing part. Does OTEL says anything about web socket? |
|
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. |
|
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 |
Intuitively this makes sense, but do we have a strong reason to enforce it? |
|
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? |
|
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:
|
|
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: |
|
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? |
|
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:
***@***.***>
|
|
Hello Quarkus team.
|
|
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. |
|
Is there any plan to implement fallback handling like https://quarkus.io/guides/vertx-reference#bidirectional-communication-with-browsers-by-using-sockjs ? |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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:OnMessageto receive messages and optionally reply to messagesOnOpento be notified when a new client connects to the web socketOnCloseto 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/pathmy/path/sub-wsThe 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:
@OnMessagemethod: Handles the messages the connected client sends.@OnOpenmethod: Invoked when a client connects to the WebSocket.@OnClosemethod: Executed upon the client disconnecting from the WebSocket.Only some endpoints need to include all three methods. However, it must contain at least
@OnMessageor@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
WebSocketServerConnectioninterface 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
WebSocketServerConnectioncan be obtained as a CDIsession-scopedbean, 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 returnUniinstances, providing flexibility in handling asynchronous operations:Note that this class provides both reactive and imperative methods. Imperative methods are suffixed with
andAwaitand block the caller thread until the operation's completion. Errors are reported using exceptions.Declaring a WebSocket Endpoint with the
@WebSocketAnnotationThe
@WebSocketannotation declares a WebSocket endpoint and is exclusive to class-level declarations. Its parameter (value) denotes the path of the WebSocket.WebSocket Endpoint Declaration
The
@WebSocketannotation is used to define a WebSocket endpoint. Consequently, methods annotated with@OnMessage,@OnOpen, and@OnCloseare 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, andOnCloseoutside a WebSocket endpoint are considered erroneous and should be flagged during the build process.Path Parameters
The path specified in the
@WebSocketannotation 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
@WebSocketcan 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
@WebSocketannotation of both the enclosing and inner classes. In the example, theconsumePrimarymethod within the enclosing class can access theversionparameter. Meanwhile, theconsumeNestedmethod within the inner class can access bothversionandid.CDI Scopes for WebSocket Endpoints
Classes annotated with
@WebSocketare 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
@OnOpenmethod is invoked, a session scope corresponding to the WebSocket connection is established. Subsequent calls to@OnMessageor@OnClosemethods utilize this same session scope. The session scope remains active until the@OnClosemethod completes execution, at which point it is terminated.The
WebSocketServerConnectionobject, 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
@OnOpenmethod, the session scope is still created and remains active until the connection terminates, regardless of the presence of an@OnClosemethod.Methods annotated with
@OnMessage,@OnOpenand@OnClosedo 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
@OnOpenmethod, a duplicated context specific to the WebSocket connection is established. Subsequent invocations of@OnMessageor@OnClosemethods 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
@WebSocketand@PathAlthough there might be an initial inclination to leverage the
@Pathannotation 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
@Blockingand@NonBlocking.Here are the rules governing execution:
@RunOnVirtualThreadare considered blocking and should execute on a virtual thread.@RunOnVirtualThread.@RunOnVirtualThreadis employed, each invocation spawns a new virtual thread.For a WebSocket connection, the methods
OnMessage,OnClose, andOnOpenmust not be invoked concurrently.Receiving Messages with
@OnMessageMethods marked with
OnMessagetrigger upon receiving a message from a connected client.Parameters
These methods can accept parameters in two formats:
Multi<X>withXas 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 (
Stringorbyte[]) or deserialized high-level objects, which is the recommended approach.When receiving a
Multi, the method is invoked once per connection, and the providedMultireceives items transmitted by this connection. The method must subscribe to theMultito receive these items (or return aMulti). Cancelling this subscription closes the associated connection.Allowed Returned Types
Methods annotated with
@OnMessagecan 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 returnedUnisignifies 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-nullUniis sent to the client as a response.Multi<X>: Indicates a non-blocking method where the items emitted by the non-nullMultiare sequentially sent to the client until completion or cancellation.When returning a
Multi, the framework subscribes to the returnedMultiand 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.Stringsare sent as text messages.Buffersandbytearrays are sent as binary messages.Skipping reply
When a method is intended to produce a message written to the client, it can emit
null. Emittingnullsignifies 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
@OnMessagemethods are sent back to the connected client. However, using thebroadcastparameter, responses can be broadcasted to all connected clients .The same principle applies to methods returning instances of
MultiorUni.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 returnedUniemits an item or encounters a failure.Multi, the request scope concludes when the returnedMultiemits 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.
@OnOpenis triggered upon client connection, while@OnCloseis invoked upon disconnection.These methods have access to the session-scoped
WebSocketServerConnectionbean.Parameters
Methods annotated with
@OnOpenand@OnClosedo not accept any parameters. If such methods declare parameters, they should be flagged as errors and reported to the user.Allowed Returned Types
@OnOpenand@OnClosemethods support different returned types.For
@OnOpenmethods, the same rules as@OnMessageapply. Thus, a method annotated with@OnOpencan send messages to the client immediately after connecting. The supported return types for@OnOpenmethods 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 returnedUnisignifies 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-nullUniis sent to the client.Multi<X>: Indicates a non-blocking method where the items emitted by the non-nullMultiare sequentially sent to the client until completion or cancellation.Items sent to the client are serialized except for the
Stringand,Bufferbyte[]types. In the case ofMulti, the framework must subscribe to the returnedMultiand write the items to the WebSocket as they are emitted.Stringsare sent as text messages.Buffersandbytearrays are sent as binary messages.For
@OnClosemethods, the allowed return types are:void: The method is considered blocking.Uni<Void>: The method is considered non-blocking.@OnClosemethods cannot send items to the connection client.Server-side Streaming
Methods annotated with
@OnOpencan utilize server-side streaming by returning aMulti<X>:Broadcasting with @onopen
Similar to
@OnMessage, items sent to the client from a method annotated with@OnOpencan 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.Bufferandbyte[]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.
All reactions