diff --git a/docs/src/main/asciidoc/images/websockets-next-architecture.png b/docs/src/main/asciidoc/images/websockets-next-architecture.png new file mode 100644 index 0000000000000..0cd6bca7ca3cd Binary files /dev/null and b/docs/src/main/asciidoc/images/websockets-next-architecture.png differ diff --git a/docs/src/main/asciidoc/images/websockets-next-chat.png b/docs/src/main/asciidoc/images/websockets-next-chat.png new file mode 100644 index 0000000000000..20b05b71803da Binary files /dev/null and b/docs/src/main/asciidoc/images/websockets-next-chat.png differ diff --git a/docs/src/main/asciidoc/websockets-next-reference.adoc b/docs/src/main/asciidoc/websockets-next-reference.adoc new file mode 100644 index 0000000000000..f2b1a92d6960b --- /dev/null +++ b/docs/src/main/asciidoc/websockets-next-reference.adoc @@ -0,0 +1,548 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += WebSockets-Next extension reference guide +:extension-status: preview +include::_attributes.adoc[] +:numbered: +:sectnums: +:categories: web +:topics: web,websockets +:extensions: io.quarkus:quarkus-websockets-next + +IMPORTANT: The `websockets-next` extension is experimental. The proposal API may change in future releases. + +== The WebSocket protocol + +The _WebSocket_ protocol, documented in the https://datatracker.ietf.org/doc/html/rfc6455[RFC6455], establishes a standardized method for creating a bidirectional communication channel between a client and a server through a single TCP connection. +Unlike HTTP, WebSocket operates as a distinct TCP protocol but is designed to function seamlessly alongside HTTP. +For example, it reuses the same ports and is compatible with the same security mechanisms. + +The interaction using WebSocket initiates with an HTTP request employing the 'Upgrade' header to transition to the WebSocket protocol. +Instead of a `200 OK` response, the server replies with a `101 Switching Protocols` response to upgrade the HTTP connection to a WebSocket connection. +Following this successful handshake, the TCP socket utilized in the initial HTTP upgrade request remains open, allowing both client and server to exchange messages in both direction continually. + +== HTTP and WebSocket architecture styles + +Despite WebSocket's compatibility with HTTP and its initiation through an HTTP request, it's crucial to recognize that the two protocols lead to distinctly different architectures and programming models. + +With HTTP/REST, applications are structured around resources/endpoints that handle various HTTP methods and paths. +Client interaction occurs through emitting HTTP requests with appropriate methods and paths, following a request-response pattern. +The server routes incoming requests to corresponding handlers based on path, method, and headers and then replies with a well-defined response. + +Conversely, WebSocket typically involves a single endpoint for the initial HTTP connection, after which all messages utilize the same TCP connection. +It introduces an entirely different interaction model: asynchronous and message-driven. + +WebSocket is a low-level transport protocol, in contrast to HTTP. +Message formats, routing, or processing require prior agreement between the client and server regarding message semantics. + +For WebSocket clients and servers, the `Sec-WebSocket-Protocol` header in the HTTP handshake request allows negotiation of a higher-level messaging protocol. In its absence, the server and client must establish their own conventions. + +== Quarkus WebSockets vs. Quarkus WebSockets Next + +This guide utilizes the `quarkus-websockets-next` extension, an implementation of the WebSocket API boasting enhanced efficiency and usability compared to the legacy `quarkus-websockets` extension. +The original `quarkus-websockets` extension remains accessible, will receive ongoing support, but it's unlikely to receive to feature development. + +Unlike `quarkus-websockets`, the `quarkus-websockets-next` extension does **not** implement the Jakarta WebSocket specification. +Instead, it introduces a modern API, prioritizing simplicity of use. +Additionally, it's tailored to integrate with Quarkus' reactive architecture and networking layer seamlessly. + +The annotations utilized by the Quarkus WebSockets next extension differ from those in JSR 356 despite, sometimes, sharing the same name. +The JSR annotations carry a semantic that the Quarkus WebSockets Next extension does not follow. + +== Use the WebSockets Next extension + +To use the `websockets-next` extension, you need to add the `io.quarkus.quarkus-websockets-next` extension to your project. +In your `pom.xml` file, add: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-websockets-next + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-websockets-next") +---- + + +== Configure the WebSocket server + +The WebSocket handling reuses the _main_ HTTP server. + +Thus, the configuration of the WebSocket server is done in the `quarkus.http.` configuration section. + +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. + +Refer to the xref:http-reference.adoc[HTTP guide] for more details. + +== Declare WebSocket endpoints + +To declare web socket endpoints, you need to create a class annotated with `@io.quarkus.websockets.next.WebSocket` and define the path of the WebSocket endpoint: + +[source,java] +---- +package org.acme.websockets; + +import io.quarkus.websockets.next.WebSocket; +import jakarta.inject.Inject; + +@WebSocket(path = "/chat/{username}") +public class ChatWebSocket { + +} +---- + +Thus, client can connect to this web socket endpoint using `ws://localhost:8080/chat/your-name`. +If TLS is used, the URL is `wss://localhost:8443/chat/your-name`. + +=== Path parameters + +The path of the WebSocket endpoint can contain path parameters. +The syntax is the same as for JAX-RS resources: `{parameterName}`. + +Access to the path parameter values is done through the `io.quarkus.websockets.next.WebSocketConnection` _session_ object: + +[source,java] +---- +@Inject io.quarkus.websockets.next.WebSocketConnection session; +// ... +String value = session.pathParam("parameterName"); +---- + +Path parameter values are always strings. +If the path parameter is not present in the path, the `pathParam` method returns `null`. + +NOTE: Query parameters are not supported. However, you can access the query using `session.handshakeRequest().query()` + +=== Sub-websockets endpoints + +A class annotated with `@WebSocket` can encapsulate static nested classes, which are also annotated with `@WebSocket` and represent _sub-web_ sockets. +The resulting path of these sub-web sockets concatenates the path from the enclosing class and the nested class. +The resulting path is 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 nested classes. +The `consumePrimary` method within the enclosing class can access the `version` parameter in the following example. +Meanwhile, the `consumeNested` method within the nested class can access both `version` and `id` parameters: + +[source, java] +---- +@WebSocket("/ws/v{version}") +public class MyPrimaryWebSocket { + + @OnTextMessage + void consumePrimary(String s) { ... } + + @WebSocket("/products/{id}") + public static class MyNestedWebSocket { + + @OnTextMessage + void consumeNested(String s) { ... } + + } + +} +---- + +=== CDI Scopes for WebSocket Endpoints +Classes annotated with `@WebSocket` are 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: + +[source,java] +---- +@WebSocket("/ws") +public class MyWebSocket { + // Singleton scoped bean +} + +@WebSocket("/ws") +@ApplicationScoped +public class MyRequestScopedWebSocket { + // Application scoped. +} +---- + +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 `@On[Text|Binary]Message` 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 `WebSocketConnection` 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. +It remains active until the connection terminates, regardless of the presence of an `@OnClose` method. + +Methods annotated with `@OnTextMessage,` `@OnBinaryMessage,` `@OnOpen`, and `@OnClose` also have the request scoped activated for the duration of the method execution (until it produced its result). + + +=== WebSocket endpoint methods + +A WebSocket endpoint comprises the following components: + +* Path: This is the URL path where the WebSocket connection is established (e.g., ws://localhost:8080/). +* At most one `@OnTextMessage` method: Handles the connected client's text messages. +* At most one `@OnBinaryMessage` method: Handles the binary messages the connected client sends. +* At most one `@OnOpen` method: Invoked when a client connects to the WebSocket. +* At most one `@OnClose` method: Executed upon the client disconnecting from the WebSocket. + +Only some endpoints need to include all methods. +However, it must contain at least `@On[Text|Binary]Message` or `@OnOpen`. + +An error is thrown at build time if any endpoint violates these rules. +The static nested classes representing sub-websockets adhere to the same guidelines. + +IMPORTANT: Any methods annotated with `@OnTextMessage`, `@OnBinaryMessage`, `@OnOpen`, and `@OnClose` outside a WebSocket endpoint are considered erroneous and will result in the build failing with an appropriate error message. + +== Processing messages + +Method receiving messages from the client are annotated with `@OnTextMessage` or `@OnBinaryMessage`. + +`OnTextMessage` are invoked for every _text_ message received from the client. +`OnBinaryMessage` are invoked for every _binary_ message the client receives. + +=== Invocation Rules + +When invoking these annotated methods, the _session_ scope linked to the WebSocket connection remains active. +In addition, the request scope is active until the completion of the method (or until it produces its result for async and reactive methods). + +Quarkus WebSocket Next supports _blocking_ and _non-blocking_ logic, akin to Quarkus REST, determined by the method signature and additional annotations such as `@Blocking` and `@NonBlocking`. + +Here are the rules governing execution: + +* Non-blocking methods must execute on the connection's event loop. +* Methods annotated with `@RunOnVirtualThread` are considered blocking and should execute on a virtual thread. +* Blocking methods must execute on a worker thread if not annotated with `@RunOnVirtualThread`. +* When `@RunOnVirtualThread` is employed, each invocation spawns a new virtual thread. +* Methods returning `CompletionStage` and `Uni` are considered non-blocking +* Methods returning `Multi` are considered non-blocking and must be subscribed to, except if they return their own `Multi`. +* Methods returning `void` or plain objects are considered blocking. + +=== Parameters + +These methods can accept parameters in two formats: + +* The message object (of any type). +* A `Multi` with X 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`, `JsonObject`, `JsonArray`, `Buffer` or `byte[]`) or deserialized high-level objects, which is the recommended approach. + +When receiving a `Multi`, the method is invoked once per connection, and the provided `Multi` receives the items transmitted by this connection. +The method must subscribe to the `Multi` to receive these items (or return a Multi). +Cancelling this subscription closes the associated connection. + +=== Allowed Returned Types + +Methods annotated with `@OnTextMessage` or `@OnBinaryMessage` 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`: Denotes a non-blocking method where the completion of the returned Uni signifies the end of processing. No explicit response is sent back to the client. +* An object of type `X` represents a blocking method in which the returned object is serialized and sent back to the client as a response. +* `Uni`: Specifies a non-blocking method where the item emitted by the non-null `Uni` is sent to the client as a response. +* `Multi`: Indicates a non-blocking method where the items emitted by the non-null `Multi` are sequentially sent to the client until completion or cancellation. + +Here are some examples of these methods: + +[source, java] +---- +@OnTextMessage +void consume(Message m) { +// Process the incoming message. The method is called on an executor thread for each incoming message. +} + +@OnTextMessage +Uni consumeAsync(Message m) { +// Process the incoming message. The method is called on an event loop thread for each incoming message. +// The method completes when the returned Uni emits its item. +} + +@OnTextMessage +ReponseMessage process(Message m) { +// Process the incoming message and send a response to the client. +// The method is called for each incoming message. +// Note that if the method returns `null`, no response will be sent to the client. +} + +@OnTextMessage +Uni processAsync(Message m) { +// Process the incoming message and send a response to the client. +// The method is called for each incoming message. +// Note that if the method returns `null`, no response will be sent to the client. The method completes when the returned Uni emits its item. +} + +OnTextMessage +Multi stream(Message m) { +// Process the incoming message and send multiple responses to the client. +// The method is called for each incoming message. +// The method completes when the returned Multi emits its completion signal. +// The method cannot return `null` (but an empty multi if no response must be sent) +} +---- + +When returning a Multi, Quarkus subscribes to the returned Multi automatically and writes the emitted items until completion, failure, or cancellation. Failure or cancellation terminates the connection. + +=== Streams + +In addition to individual messages, WebSocket endpoints can handle streams of messages. +In this case, the method receives a `Multi` as a parameter. +Each instance of `X` is deserialized using the same rules listed above. + +The method receiving the `Multi` can either return another `Multi` or `void`. +If the method returns a `Multi`, it does not have to subscribe to the incoming `multi`: + +[source, java] +---- +@OnTextMessage +public Multi stream(Multi incoming) { + return incoming.log(); +} +---- + +This approach allows bi-directional streaming. + +When the method returns `void`, it must subscribe to the incoming `Multi`: + +[source, java] +---- +@OnTextMessage +public void stream(Multi incoming) { + incoming.subscribe().with(item -> log(item)); +} +---- + +=== Skipping reply +When a method is intended to produce a message written to the client, it can emit `null`. +Emitting `null` signifies no response to be sent to the client, allowing for skipping a response when needed. + +=== JsonObject and JsonArray +Vert.x `JSONObject` and `JSONArray` instances bypass the serialization and deserialization mechanisms. +Messages are sent as text messages. + +=== Broadcasting +By default, responses produced by `@On[Text|Binary]Message` methods are sent back to the connected client. +However, using the `broadcast` parameter, responses can be broadcasted to all connected clients. + +[source, java] +---- +@OnTextMessage(broadcast=true) +String emitToAll(String message) { + // Send the response to all connected clients. +} +---- + +The same principle applies to methods returning instances of `Multi` or `Uni`. + +== OnOpen and OnClose methods + +The WebSocket endpoint can also be notified when a client connects or disconnects. + +This is done by annotating a method with `@OnOpen` or `@OnClose`: + +[source,java] +---- +@OnOpen(broadcast = true) +public ChatMessage onOpen() { + return new ChatMessage(MessageType.USER_JOINED, connection.pathParam("username"), null); +} + +@Inject WebSocketConnection connection; + +@OnClose +public void onClose() { + ChatMessage departure = new ChatMessage(MessageType.USER_LEFT, connection.pathParam("username"), null); + connection.broadcast().sendTextAndAwait(departure); +} +---- + +`@OnOpen` is triggered upon client connection, while `@OnClose` is invoked upon disconnection. + +These methods have access to the _session-scoped_ `WebSocketConnection` bean. + +=== Parameters + +Methods annotated with `@OnOpen` and `@OnClose` do not accept any parameters. +If such methods declare parameters, they will be flagged as errors and reported at build time. + +=== Allowed Returned Types + +`@OnOpen` and `@OnClose` methods support different returned types. + +For `@OnOpen` methods, the same rules as `@On[Text|Binary]Message` 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`: Denotes a non-blocking method where the completion of the returned `Uni` signifies the end of processing. No message is sent back to the client. +* An object of type `X`: Represents a blocking method where the returned object is serialized and sent back to the client. +* `Uni`: Specifies a non-blocking method where the item emitted by the non-null `Uni` is sent to the client. +* `Multi`: Indicates a non-blocking method where the items emitted by the non-null `Multi` are sequentially sent to the client until completion or cancellation. + +Items sent to the client are serialized except for the `String`, `JsonObject`, `JsonArray`, `Buffer`, and `byte[]` types. +In the case of `Multi`, Quarkus subscribes to the returned `Multi` and writes the items to the `WebSocket` as they are emitted. +`String`, `JsonObject` and `JsonArray` are sent as text messages. +`Buffers` and byte arrays are sent as binary messages. + +For `@OnClose` methods, the allowed return types are: + +* `void`: The method is considered blocking. +* `Uni`: The method is considered non-blocking. + +`@OnClose` methods cannot send items to the connection client by returning objects. +They can only send messages to the other client by using the `WebSocketConnection` object. + +=== Server-side Streaming + +Methods annotated with `@OnOpen` can utilize server-side streaming by returning a `Multi`: + +[source, java] +---- +@WebSocket("/foo") +@OnOpen +public Multi streaming() { + return Multi.createFrom().ticks().every(Duration.ofSecond(1)) + .onOverflow().ignore(); +} +---- + +=== Broadcasting with @OnOpen + +Similar to `@On[Text|Binary]Message`, items sent to the client from a method annotated with `@OnOpen` can be broadcasted to all clients instead of just the connecting client: + +[source, java] +---- +@OnOpen(broadcast=true) +String onOpen() { + return "We have a new member!"; +} +---- + +== Access to the WebSocketConnection + +The `io.quarkus.websockets.next.WebSocketConnection` object represents the WebSocket connection. +It's _session-scoped_ and is valid for the whole duration of the connection. + +Methods annotated with `@OnOpen`, `@OnTextMessage`, `@OnBinaryMessage`, and `@OnClose` can access the `WebSocketConnection` object: + +[source,java] +---- +@Inject WebSocketConnection connection; +---- + +Note that outside of these methos, the `WebSocketConnection` object is not available. + +The connection can be used to send messages to the client, access the path parameters, and broadcast messages to all connected clients. + +[source, java] +---- +// Send a message: +connection.sendTextAndAwait("Hello!"); + +// Broadcast messages: +connection.broadcast().sendTextAndAwait(departure); + +// Access path parameters: +String param = connection.pathParam("foo"); +---- + +The `WebSocketConnection` provides both a blocking and a non-blocking method to send messages: + +- `sendTextAndAwait(String message)`: Sends a text message to the client and waits for the message to be sent. It's blocking and should only be called from an executor thread. +- `sendText(String message)`: Sends a text message to the client. It returns a `Uni`. It's non-blocking, but you must subscribe to it. + +== Serialization and Deserialization + +The WebSocket Next extension supports automatic serialization and deserialization of messages. + + +Objects of type `String`, `JsonObject`, `JsonArray`, `Buffer`, and `byte[]` are sent as-is and by-pass the serialization and deserialization. +When no codec is provided, the serialization and deserialization uses JSON (Jackson) automatically. + +When you need to customize the serialization and deserialization, you can provide a custom codec. + +=== Custom codec + +To implement a custom codec, you must provides a CDI bean implementing: + +- `io.quarkus.websockets.next.BinaryMessageCodec` for binary messages +- `io.quarkus.websockets.next.TextMessageCodec` for text messages + +The following example shows how to implement a custom codec for a `Item` class: + +[source, java] +---- +@Singleton + public static class ItemBinaryMessageCodec implements BinaryMessageCodec { + + @Override + public boolean supports(Type type) { + // Allows selecting the right codec for the right type + return type.equals(Item.class); + } + + @Override + public Buffer encode(Item value) { + // Serialization + return Buffer.buffer(value.toString()); + } + + @Override + public Item decode(Type type, Buffer value) { + return new Item(value.toString()); + } + + } +---- + +`OnTextMessage` and `OnBinaryMessage` methods can also specify which codec need to be used explicitly: + +[source, java] +---- +@OnTextMessage(codec = MyInputCodec.class) // <1> +Item find(Item item) { + //.... +} +---- +1. Specify the codec to use for both the deserialization and serialization of the message + +When the serialization and deserialization must use a different codec, you can specify the codec to use for the serialization and deserialization separately: + +[source, java] +---- +@OnTextMessage( + codec = MyInputCodec.class, // <1> + outputCodec = MyOutputCodec.class // <2> +Item find(Item item) { + //.... +} +---- +1. Specify the codec to use for both the deserialization of the incoming message +2. Specify the codec to use for the serialization of the outgoing message + +== Handle Pong message + +The `@OnPongMessage` annotation is used to consume pong messages. +A websocket endpoint must declare at most one method annotated with `@OnPongMessage`. + +The method must accept a single parameter of type `Buffer`: + +[source,java] +---- +@OnPongMessage +void pong(Buffer data) { + // .... +} +---- + +[[websocket-next-configuration-reference]] +== Configuration reference + +include::{generated-dir}/config/quarkus-websockets-next.adoc[opts=optional, leveloffset=+1] diff --git a/docs/src/main/asciidoc/websockets-next-tutorial.adoc b/docs/src/main/asciidoc/websockets-next-tutorial.adoc new file mode 100644 index 0000000000000..fb5f2ad82e9a2 --- /dev/null +++ b/docs/src/main/asciidoc/websockets-next-tutorial.adoc @@ -0,0 +1,178 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Getting started with WebSockets-Next +include::_attributes.adoc[] +:categories: web +:diataxis-type: tutorial +:summary: This guide explains how your Quarkus application can utilize web sockets to create interactive web applications. This guide uses the WebSockets Next extension +:topics: web,websockets +:extensions: io.quarkus:quarkus-websockets-next + +This guide explains how your Quarkus application can utilize web sockets to create interactive web applications. +In this guide, we will develop a very simple chat application using web sockets to receive and send messages to the other connected users. + +IMPORTANT: The `websockets-next` extension is experimental. The proposal API may change in future releases. + +== Prerequisites + +include::{includes}/prerequisites.adoc[] + +== Quarkus WebSockets vs. Quarkus WebSockets Next + +This guide uses the `quarkus-websockets-next` extension. +This extension is a new implementation of the WebSocket API that is more efficient and easier to use than the original `quarkus-websockets` extension. The original `quarkus-websockets` extension is still available and will continue to be supported. + +Unlike `quarkus-websockets`, `quarkus-web-socket-next` does NOT implement https://jakarta.ee/specifications/websocket/[Jakarta WebSocket]. +Instead, it provides a simplified and more modern API that is easier to use. +It is also designed to work efficiently with Quarkus' reactive programming model and the Quarkus' networking layer. + +== What you'll learn + +* How to use the `quarkus-websockets-next` extension +* How to declare a web socket endpoint +* How to send and receive messages using web sockets +* How to broadcast messages to all connected users +* How to be notified of new connections and disconnections +* How to use _path parameters_ in web socket URLs + +== Architecture + +In this guide, we create a straightforward chat application using web sockets to receive and send messages to the other connected users. + +image:websockets-next-architecture.png[alt=Architecture] + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can skip right to the completed example. + +Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. + +The solution is located in the `websockets-next-quickstart` link:{quickstarts-tree-url}/websockets-next-quickstart[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +:create-app-artifact-id: websockets-quickstart +:create-app-extensions: websockets +include::{includes}/devtools/create-app.adoc[] + +This command generates the project (without any classes) and imports the `websockets-next` extension. + +If you already have your Quarkus project configured, you can add the `websockets-next` extension +to your project by running the following command in your project base directory: + +:add-extension-extensions: websockets-next +include::{includes}/devtools/extension-add.adoc[] + +This will add the following to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-websockets-next + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-websockets-next") +---- + +== Declaring a WebSocket endpoint + +Our application contains a single class that handles the web sockets. +Create the `org.acme.websockets.ChatWebSocket` class in the `src/main/java` directory. +Copy the following content into the created file: + +[source,java] +---- +package org.acme.websockets; + +import io.quarkus.websockets.next.OnClose; +import io.quarkus.websockets.next.OnOpen; +import io.quarkus.websockets.next.OnTextMessage; +import io.quarkus.websockets.next.WebSocket; +import io.quarkus.websockets.next.WebSocketConnection; +import jakarta.inject.Inject; + +@WebSocket(path = "/chat/{username}") // <1> +public class ChatWebSocket { + + // Declare the type of messages that can be sent and received + public enum MessageType {USER_JOINED, USER_LEFT, CHAT_MESSAGE} + public record ChatMessage(MessageType type, String from, String message) { + } + + @Inject + WebSocketConnection connection; // <2> + + @OnOpen(broadcast = true) // <3> + public ChatMessage onOpen() { + return new ChatMessage(MessageType.USER_JOINED, connection.pathParam("username"), null); + } + + @OnClose // <4> + public void onClose() { + ChatMessage departure = new ChatMessage(MessageType.USER_LEFT, connection.pathParam("username"), null); + connection.broadcast().sendTextAndAwait(departure); + } + + @OnTextMessage(broadcast = true) // <5> + public ChatMessage onMessage(ChatMessage message) { + return message; + } + +} +---- +<1> Declares the web socket endpoint and configure the path. Note that the path can contain a path parameter: `username`. +<2> A _session scoped bean_ that represents the connection to the client. It allows sending messages programmatically and retrieve the path parameters. +<3> This method is called when a new client connects. The `broadcast = true` attribute indicates that the returned message should be sent to all connected clients. +<4> This method is called when a client disconnects. The method uses the `WebSocketConnection` to broadcast a message to all remaining connected clients. +<5> This method is called when a client sends a message. The `broadcast = true` attribute indicates that the returned message should be sent to all connected clients. Here, we just returns the received (text) message. + +As you can see, Quarkus handles the web socket lifecycle and message handling using annotations. +It also serializes and deserializes messages using JSON automatically. + +== A slick web frontend + +All chat applications need a _nice_ UI, well, this one may not be that nice, but does the work. +Quarkus automatically serves static resources contained in the `META-INF/resources` directory. +Create the `src/main/resources/META-INF/resources` directory and copy this link:{quickstarts-blob-url}/websockets-next-quickstart/src/main/resources/META-INF/resources/index.html[index.html] file in it. + +== Run the application + +Now, let's see our application in action. Run it with: + +include::{includes}/devtools/dev.adoc[] + +Then open your 2 browser windows to http://localhost:8080/: + +1. Enter a name in the top text area (use 2 different names). +2. Click on connect +3. Send and receive messages + +image:websockets-next-chat.png[alt=Application] + +As usual, the application can be packaged using: + +include::{includes}/devtools/build.adoc[] + +And executed using `java -jar target/quarkus-app/quarkus-run.jar`. + +You can also build the native executable using: + +include::{includes}/devtools/build-native.adoc[] + + +== Conclusion + +This short getting started guide has shown you how to create a simple chat application using the `quarkus-websockets-next` extension. +Learn more about this extension on the xref:./websockets-next-reference.adoc[dedicated reference guide].