This technology is considered experimental. In experimental mode, early feedback is requested to mature the idea. There is no guarantee of stability nor long term presence in the platform until the solution matures. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker. For a full list of possible statuses, check our FAQ entry. |
The quarkus-websockets-next
extension provides a modern declarative API to define WebSocket server and client endpoints.
- 1. The WebSocket protocol
- 2. HTTP and WebSocket architecture styles
- 3. Quarkus WebSockets vs. Quarkus WebSockets Next
- 4. Use the WebSockets Next extension
- 5. Configure the WebSocket server
- 6. Declare WebSocket endpoints
- 7. Processing messages
- 8. OnOpen and OnClose methods
- 9. Error Handling
- 10. Access to the WebSocketConnection
- 11. Serialization and Deserialization
- 12. Ping/pong messages
- 13. Security
- 14. Secure HTTP upgrade
- 15. Inspect and/or reject HTTP upgrade
- 16. Traffic logging
- 17. Configuration reference
1. The WebSocket protocol
The WebSocket protocol, documented in the 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.
2. 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.
3. 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.
4. 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:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-websockets-next</artifactId>
</dependency>
implementation("io.quarkus:quarkus-websockets-next")
5. 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 HTTP guide for more details.
6. 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:
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
.
6.1. 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:
@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
.
Query parameters are not supported. However, you can access the query using session.handshakeRequest().query()
|
6.2. 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:
@WebSocket(path = "/ws/v{version}")
public class MyPrimaryWebSocket {
@OnTextMessage
void consumePrimary(String s) { ... }
@WebSocket(path = "/products/{id}")
public static class MyNestedWebSocket {
@OnTextMessage
void consumeNested(String s) { ... }
}
}
6.3. 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:
@WebSocket(path = "/ws")
public class MyWebSocket {
// Singleton scoped bean
}
@WebSocket(path = "/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).
6.4. 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. -
Any number of
@OnError
methods: Invoked when an error occurs; that is when an endpoint callback throws a runtime error, or when a conversion errors occurs, or when a returnedio.smallrye.mutiny.Uni
/io.smallrye.mutiny.Multi
receives a failure.
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.
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.
|
7. 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.
7.1. 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
,Uni
andMulti
are considered non-blocking. -
Methods returning
void
or plain objects are considered blocking.
7.2. Method Parameters
The method must accept exactly one message parameter:
-
The message object (of any type).
-
A
Multi<X>
with X as the message type.
However, it may also accept the following parameters:
-
WebSocketConnection
-
HandshakeRequest
-
String
parameters annotated with@PathParam
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).
7.3. 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<Void>
: 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<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.
Here are some examples of these methods:
@OnTextMessage
void consume(Message m) {
// Process the incoming message. The method is called on an executor thread for each incoming message.
}
@OnTextMessage
Uni<Void> 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
ResponseMessage 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<ResponseMessage> 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<ResponseMessage> 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.
7.4. Streams
In addition to individual messages, WebSocket endpoints can handle streams of messages.
In this case, the method receives a Multi<X>
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
:
@OnTextMessage
public Multi<ChatMessage> stream(Multi<ChatMessage> incoming) {
return incoming.log();
}
This approach allows bi-directional streaming.
When the method returns void
, it must subscribe to the incoming Multi
:
@OnTextMessage
public void stream(Multi<ChatMessage> incoming) {
incoming.subscribe().with(item -> log(item));
}
7.5. 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.
7.6. JsonObject and JsonArray
Vert.x JsonObject
and JsonArray
instances bypass the serialization and deserialization mechanisms.
Messages are sent as text messages.
7.7. 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.
@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
.
If you need to select the connected clients that should receive the message, you can use WebSocketConnection.broadcast().filter().sendText() .
|
8. 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
:
@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.
8.1. Parameters
Methods annotated with @OnOpen
and @OnClose
may accept the following parameters:
-
WebSocketConnection
-
HandshakeRequest
-
String
parameters annotated with@PathParam
An endpoint method annotated with @OnClose
may also accept the io.quarkus.websockets.next.CloseReason
parameter that may indicate a reason for closing a connection.
8.2. 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<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. -
An object of type
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
, 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<Void>
: 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.
9. Error Handling
The WebSocket endpoint can also be notified when an error occurs.
A WebSocket endpoint method annotated with @io.quarkus.websockets.next.OnError
is invoked when an endpoint callback throws a runtime error, or when a conversion errors occurs,
or when a returned io.smallrye.mutiny.Uni
/io.smallrye.mutiny.Multi
receives a failure.
The method must accept exactly one "error" parameter, i.e. a parameter that is assignable from java.lang.Throwable
.
The method may also accept the following parameters:
-
WebSocketConnection
-
HandshakeRequest
-
String
parameters annotated with@PathParam
An endpoint may declare multiple methods annotated with @io.quarkus.websockets.next.OnError
.
However, each method must declare a different error parameter.
The method that declares a most-specific supertype of the actual exception is selected.
The @io.quarkus.websockets.next.OnError annotation can be also used to declare a global error handler, i.e. a method that is not declared on a WebSocket endpoint. Such a method may not accept @PathParam paremeters. Error handlers declared on an endpoint take precedence over the global error handlers.
|
When an error occurs but no error handler can handle the failure, Quarkus uses the strategy specified by quarkus.websockets-next.server.unhandled-failure-strategy
and quarkus.websockets-next.client.unhandled-failure-strategy
, respectively.
By default, the connection is closed.
Alternatively, an error message can be logged or no operation performed.
10. 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:
@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.
// 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 aUni
. It’s non-blocking, but you must subscribe to it.
10.1. List open connections
It is also possible to list all open connections.
Quarkus provides a CDI bean of type io.quarkus.websockets.next.OpenConnections
that declares convenient methods to access the connections.
import io.quarkus.logging.Log;
import io.quarkus.websockets.next.OpenConnections;
class MyBean {
@Inject
OpenConnections connections;
void logAllOpenConnections() {
Log.infof("Open connections: %s", connections.listAll()); (1)
}
}
1 | OpenConnections#listAll() returns an immutable snapshot of all open connections at the given time. |
There are also other convenient methods.
For example, OpenConnections#findByEndpointId(String)
makes it easy to find connections for a specific endpoint.
10.2. CDI events
Quarkus fires a CDI event of type io.quarkus.websockets.next.WebSocketConnection
with qualifier @io.quarkus.websockets.next.Open
asynchronously when a new connection is opened.
Moreover, a CDI event of type WebSocketConnection
with qualifier @io.quarkus.websockets.next.Closed
is fired asynchronously when a connection is closed.
import jakarta.enterprise.event.ObservesAsync;
import io.quarkus.websockets.next.Open;
import io.quarkus.websockets.next.WebSocketConnection;
class MyBean {
void connectionOpened(@ObservesAsync @Open WebSocketConnection connection) { (1)
// This observer method is called when a connection is opened...
}
}
1 | An asynchronous observer method is executed using the default blocking executor service. |
11. 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.
11.1. 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:
@Singleton
public static class ItemBinaryMessageCodec implements BinaryMessageCodec<Item> {
@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:
@OnTextMessage(codec = MyInputCodec.class) (1)
Item find(Item item) {
//....
}
-
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:
@OnTextMessage(
codec = MyInputCodec.class, (1)
outputCodec = MyOutputCodec.class (2)
Item find(Item item) {
//....
}
-
Specify the codec to use for both the deserialization of the incoming message
-
Specify the codec to use for the serialization of the outgoing message
12. Ping/pong messages
A ping message may serve as a keepalive or to verify the remote endpoint. A pong message is sent in response to a ping message and it must have an identical payload.
The server automatically responds to a ping message sent from the client.
In other words, there is no need for @OnPingMessage
callback declared on an endpoint.
The server can send ping messages to a connected client.
The WebSocketConnection
declares methods to send ping messages; there is a non-blocking variant: WebSocketConnection#sendPing(Buffer)
and a blocking variant: WebSocketConnection#sendPingAndAwait(Buffer)
.
By default, the ping messages are not sent automatically.
However, the configuration property quarkus.websockets-next.server.auto-ping-interval
can be used to set the interval after which, the server sends a ping message to a connected client automatically.
quarkus.websockets-next.server.auto-ping-interval=2 (1)
1 | Sends a ping message to a connected client every 2 seconds. |
The @OnPongMessage
annotation is used to define a callback that consumes pong messages sent from the client.
An endpoint must declare at most one method annotated with @OnPongMessage
.
The callback method must return either void
or Uni<Void>
, and it must accept a single parameter of type Buffer
.
@OnPongMessage
void pong(Buffer data) {
// ....
}
The server can also send unsolicited pong messages that may serve as a unidirectional heartbeat. There is a non-blocking variant: WebSocketConnection#sendPong(Buffer) and also a blocking variant: WebSocketConnection#sendPongAndAwait(Buffer) .
|
13. Security
WebSocket endpoint callback methods can be secured with security annotations such as io.quarkus.security.Authenticated
,
jakarta.annotation.security.RolesAllowed
and other annotations listed in the Supported security annotations documentation.
For example:
package io.quarkus.websockets.next.test.security;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import io.quarkus.security.ForbiddenException;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.websockets.next.OnError;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@WebSocket(path = "/end")
public class Endpoint {
@Inject
SecurityIdentity currentIdentity;
@OnOpen
String open() {
return "ready";
}
@RolesAllowed("admin")
@OnTextMessage
String echo(String message) { (1)
return message;
}
@OnError
String error(ForbiddenException t) { (2)
return "forbidden:" + currentIdentity.getPrincipal().getName();
}
}
1 | The echo callback method can only be invoked if the current security identity has an admin role. |
2 | The error handler is invoked in case of the authorization failure. |
SecurityIdentity
is initially created during a secure HTTP upgrade and associated with the websocket connection.
When OpenID Connect extension is used and token expires, Quarkus automatically closes connection. |
14. Secure HTTP upgrade
An HTTP upgrade is secured when standard security annotation is placed on an endpoint class or an HTTP Security policy is defined. The advantage of securing HTTP upgrade is less processing, the authorization is performed early and only once. You should always prefer HTTP upgrade security unless, like in th example above, you need to perform action on error.
package io.quarkus.websockets.next.test.security;
import io.quarkus.security.Authenticated;
import jakarta.inject.Inject;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnTextMessage;
import io.quarkus.websockets.next.WebSocket;
@Authenticated (1)
@WebSocket(path = "/end")
public class Endpoint {
@Inject
SecurityIdentity currentIdentity;
@OnOpen
String open() {
return "ready";
}
@OnTextMessage
String echo(String message) {
return message;
}
}
1 | Initial HTTP handshake ends with the 401 status for anonymous users.
You can also redirect the handshake request on authorization failure with the quarkus.websockets-next.server.security.auth-failure-redirect-url configuration property. |
HTTP upgrade is only secured when a security annotation is declared on an endpoint class next to the @WebSocket annotation.
Placing a security annotation on an endpoint bean will not secure bean methods, only the HTTP upgrade.
You must always verify that your endpoint is secured as intended.
|
quarkus.http.auth.permission.http-upgrade.paths=/end
quarkus.http.auth.permission.http-upgrade.policy=authenticated
15. Inspect and/or reject HTTP upgrade
To inspect an HTTP upgrade, you must provide a CDI bean implementing the io.quarkus.websockets.next.HttpUpgradeCheck
interface.
Quarkus calls the HttpUpgradeCheck#perform
method on every HTTP request that should be upgraded to a WebSocket connection.
Inside this method, you can perform any business logic and/or reject the HTTP upgrade.
package io.quarkus.websockets.next.test;
import io.quarkus.websockets.next.HttpUpgradeCheck;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped (1)
public class ExampleHttpUpgradeCheck implements HttpUpgradeCheck {
@Override
public Uni<CheckResult> perform(HttpUpgradeContext ctx) {
if (rejectUpgrade(ctx)) {
return CheckResult.rejectUpgrade(400); (2)
}
return CheckResult.permitUpgrade();
}
private boolean rejectUpgrade(HttpUpgradeContext ctx) {
var headers = ctx.httpRequest().headers();
// implement your business logic in here
}
}
1 | The CDI beans implementing HttpUpgradeCheck interface can be either @ApplicationScoped , @Singleton or @Dependent beans, but never the @RequestScoped beans. |
2 | Reject the HTTP upgrade. Initial HTTP handshake ends with the 400 Bad Request response status code. |
You can choose WebSocket endpoints to which the HttpUpgradeCheck is applied with the HttpUpgradeCheck#appliesTo method.
|
16. Traffic logging
Quarkus can log the messages sent and received for debugging purposes.
To enable traffic logging, set the quarkus.websockets-next.server.traffic-logging.enabled
configuration property to true
.
The payload of text messages is logged as well.
However, the number of logged characters is limited.
The default limit is 100, but you can change this limit with the quarkus.websockets-next.server.traffic-logging.text-payload-limit
configuration property.
The messages are only logged if the DEBUG level is enabled for the logger io.quarkus.websockets.next.traffic .
|
quarkus.websockets-next.server.traffic-logging.enabled=true (1)
quarkus.websockets-next.server.traffic-logging.text-payload-limit=50 (2)
quarkus.log.category."io.quarkus.websockets.next.traffic".level=DEBUG (3)
1 | Enables traffic logging. |
2 | Set the number of characters of a text message payload which will be logged. |
3 | Enable DEBUG level is for the logger io.quarkus.websockets.next.traffic . |
17. Configuration reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Type |
Default |
|
---|---|---|
Compression Extensions for WebSocket are supported by default. See also RFC 7692 Environment variable: |
boolean |
|
The compression level must be a value between 0 and 9. The default value is Environment variable: |
int |
|
The maximum size of a message in bytes. The default values is Environment variable: |
int |
|
The interval after which, when set, the client sends a ping message to a connected server automatically. Ping messages are not sent automatically by default. Environment variable: |
||
The strategy used when an error occurs but no error handler can handle the failure. By default, the connection is closed when an unhandled failure occurs. Environment variable: |
|
|
The name of the TLS configuration to use. If a name is configured, it uses the configuration from The default TLS configuration is not used by default. Environment variable: |
string |
|
If set to true then binary/text messages received/sent are logged if the Environment variable: |
boolean |
|
The number of characters of a text message which will be logged if traffic logging is enabled. The payload of a binary message is never logged. Environment variable: |
int |
|
Environment variable: |
list of string |
|
Compression Extensions for WebSocket are supported by default. See also RFC 7692 Environment variable: |
boolean |
|
The compression level must be a value between 0 and 9. The default value is Environment variable: |
int |
|
The maximum size of a message in bytes. The default values is Environment variable: |
int |
|
The interval after which, when set, the server sends a ping message to a connected client automatically. Ping messages are not sent automatically by default. Environment variable: |
||
The strategy used when an error occurs but no error handler can handle the failure. By default, the connection is closed when an unhandled failure occurs. Environment variable: |
|
|
Quarkus redirects HTTP handshake request to this URL if an HTTP upgrade is rejected due to the authorization failure. This configuration property takes effect when you secure endpoint with a standard security annotation. For example, the HTTP upgrade is secured if an endpoint class is annotated with the Environment variable: |
string |
|
If set to true then binary/text messages received/sent are logged if the Environment variable: |
boolean |
|
The number of characters of a text message which will be logged if traffic logging is enabled. The payload of a binary message is never logged. Environment variable: |
int |
|
About the Duration format
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|