- WebSocket Introduction
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
TyrusSupportclass which is akin toJerseySupport, and enables Helidon application to defined both annotated and programmatic WebSocket endpoints.
Helidon SE 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(messageQueue::push);
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 {
if (message.equals("SEND")) {
while (!messageQueue.isEmpty()) {
session.getBasicRemote().sendObject(messageQueue.pop());
}
}
} catch (Exception e) {
// ...
}
}
});
}
}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);
Routing.builder()
.register("/rest", new MessageQueueService())
.register("/websocket",
TyrusSupport.builder().register(
ServerEndpointConfig.Builder.create(
MessageBoardEndpoint.class, "/board").encoders(
encoders).build()).build())
.build();This code snippet uses multiple builders for Routing, TyrusSupport 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.