WebSocket
Overview
Helidon provides support for WebSocket. The WebSocket API enables Java applications to participate in WebSocket interactions as both servers and clients. The server API supports annotated and programmatic endpoints.
Annotated endpoints use Java annotations to define WebSocket handlers;
programmatic endpoints implement API interfaces and are annotation-free.
Helidon uses WebSocketRouting to configure routing for both styles.
Maven Coordinates
To enable WebSocket, 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 example is available here. Let us start by
looking at MessageQueueService:
record MessageQueueService(Queue<String> messageQueue) implements HttpService {
@Override
public void routing(HttpRules routingRules) {
routingRules.post("/board", (req, res) -> {
messageQueue.add(req.content().as(String.class));
res.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:
record MessageBoardEndpoint(Queue<String> messageQueue) implements WsListener {
@Override
public void onMessage(WsSession session, String text, boolean last) {
// Send all messages in the queue
if (text.equals("send")) {
while (!messageQueue.isEmpty()) {
session.send(messageQueue.poll(), last);
}
}
}
}
This is an example of a programmatic endpoint that extends WsListener. The
method onMessage will be invoked for every message. In this example, when the
special send message is received, it empties the shared queue sending messages
one at a time over the WebSocket connection.
In Helidon, REST and WebSocket classes need to be manually registered into
the web server. This is accomplished via a Routing builder:
HttpService staticContent = StaticContentFeature.createService(
ClasspathHandlerConfig.builder()
.location("/WEB")
.welcome("index.html")
.build());
Queue<String> messageQueue = new ConcurrentLinkedQueue<>();
server.routing(it -> it
.register("/web", staticContent)
.register("/rest", new MessageQueueService(messageQueue)))
.addRouting(WsRouting.builder()
.endpoint("/websocket/board", new MessageBoardEndpoint(messageQueue)));
This code snippet registers MessageBoardEndpoint at /websocket/board and
associates.
Buffered Message Size
When Helidon combines WebSocket fragments before delivering a whole message to
an endpoint, the server and client each use a default buffering threshold of 1
MiB. Configure the server limit with
server.protocols.websocket.max-buffered-message-size, and the client limit
with protocol-config.max-buffered-message-size in WsClient configuration.
If a combined message exceeds the applicable limit, that side closes the
connection with WebSocket close code 1009.
The server limit is independent of max-frame-length, which applies to each
individual frame. Binary messages are measured exactly in bytes. Text message
size is approximated using the number of UTF-16 code units in each decoded
string fragment and may be smaller than the UTF-8 payload size. Listener
methods that accept the trailing boolean fragment indicator, Reader, or
InputStream consume fragments without whole-message buffering and are not
limited by max-buffered-message-size.
Security
When a WebSocket upgrade request includes an Origin header, Helidon validates
it. By default, Helidon requires the Origin authority to match the request
Host header and rejects cross-host requests. If you configure
server.protocols.websocket.origins, the configured values act as an explicit
allowlist instead. Requests without an Origin header are allowed so
non-browser clients continue to work.