Contents
Overview
Helidon integrates with Tyrus to provide support for the Jakarta WebSocket API. The WebSocket API enables Java applications to participate in WebSocket interactions as both servers and clients. The server API supports two flavors: annotated and programmatic endpoints.
Annotated endpoints, as suggested by their name, use Java annotations to provide the necessary meta-data to define WebSocket handlers; programmatic endpoints implement API interfaces and are annotation free. Annotated endpoints tend to be more flexible since they allow different method signatures depending on the application needs, whereas programmatic endpoints must implement an interface and are, therefore, bounded to its definition.
Helidon SE support is based on the WebSocketRouting class which enables Helidon application to configure routing for both annotated and programmatic WebSocket endpoints.
Maven Coordinates
To enable {feature-name} add the following dependency to your project’s pom.xml (see Managing Dependencies).
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-websocket</artifactId>
</dependency>Example
This section describes the implementation of a simple application that uses a REST resource to push messages into a shared queue and a programmatic WebSocket endpoint to download messages from the queue, one at a time, over a connection. The example will show how REST and WebSocket connections can be seamlessly combined into a Helidon application.
The complete Helidon SE example is available here. Let us start by looking at MessageQueueService:
public class MessageQueueService implements Service {
private final MessageQueue messageQueue = MessageQueue.instance();
@Override
public void update(Routing.Rules routingRules) {
routingRules.post("/board", this::handlePost);
}
private void handlePost(ServerRequest request, ServerResponse response) {
request.content()
.as(String.class)
.thenAccept(it -> {
messageQueue.push(it);
response.status(204).send();
});
}
}This class exposes a REST resource where messages can be posted. Upon receiving a message, it simply pushes it into a shared queue and returns 204 (No Content).
Messages pushed into the queue can be obtained by opening a WebSocket connection served by MessageBoardEndpoint:
public class MessageBoardEndpoint extends Endpoint {
private final MessageQueue messageQueue = MessageQueue.instance();
@Override
public void onOpen(Session session, EndpointConfig endpointConfig) {
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String message) {
try {
// Send all messages in the queue
if (message.equals("SEND")) {
while (!messageQueue.isEmpty()) {
session.getBasicRemote().sendObject(messageQueue.pop());
}
}
} catch (Exception e) {
// handle exception
}
}
});
}
}This is an example of a programmatic endpoint that extends Endpoint. The method onOpen will be invoked for every new connection. In this example, the application registers a message handler for strings, and when the special SEND message is received, it empties the shared queue sending messages one at a time over the WebSocket connection.
In Helidon SE, REST and WebSocket classes need to be manually registered into the web server. This is accomplished via a Routing builder:
List<Class<? extends Encoder>> encoders =
Collections.singletonList(UppercaseEncoder.class);
WebServer server = WebServer.builder()
.port(8080)
.routing(r -> r
.register("/web", StaticContentSupport.builder("/WEB")
.welcomeFileName("index.html")
.build())
.register("/rest", new MessageQueueService())
)
.addRouting(WebSocketRouting.builder()
.endpoint("/websocket", ServerEndpointConfig.Builder.create(MessageBoardEndpoint.class, "/board")
.encoders(encoders)
.build())
.build()
)
.build()This code snippet uses multiple builders for Routing, WebSocketRouting and ServerEndpointConfig. In particular, it registers MessageBoardEndpoint.class at "/websocket/board" and associates with it a message encoder. For more information on message encoders and decoders the reader see the websocket specification; in this example, UppercaseEncoder.class simply uppercases every message sent from the server.
Endpoint methods in Helidon SE are executed in Netty’s worker thread pool. Threads in this pool are intended to be non-blocking, thus it is recommended for any blocking or long-running operation triggered by an endpoint method to be executed using a separate thread pool. See the documentation for io.helidon.common.configurable.ThreadPoolSupplier.