6.24.1 Using @ServerWebSocket
The @ServerWebSocket annotation can be applied to any class that should map to a WebSocket URI. The following example is a simple chat WebSocket implementation:
WebSocket Chat Example
import io.micronaut.websocket.WebSocketBroadcaster;
import io.micronaut.websocket.WebSocketSession;
import io.micronaut.websocket.annotation.OnClose;
import io.micronaut.websocket.annotation.OnMessage;
import io.micronaut.websocket.annotation.OnOpen;
import io.micronaut.websocket.annotation.ServerWebSocket;
import java.util.function.Predicate;
@ServerWebSocket("/chat/{topic}/{username}") (1)
public class ChatServerWebSocket {
private WebSocketBroadcaster broadcaster;
public ChatServerWebSocket(WebSocketBroadcaster broadcaster) {
this.broadcaster = broadcaster;
}
@OnOpen (2)
public void onOpen(String topic, String username, WebSocketSession session) {
String msg = "[" + username + "] Joined!";
broadcaster.broadcastSync(msg, isValid(topic, session));
}
@OnMessage (3)
public void onMessage(
String topic,
String username,
String message,
WebSocketSession session) {
String msg = "[" + username + "] " + message;
broadcaster.broadcastSync(msg, isValid(topic, session)); (4)
}
@OnClose (5)
public void onClose(
String topic,
String username,
WebSocketSession session) {
String msg = "[" + username + "] Disconnected!";
broadcaster.broadcastSync(msg, isValid(topic, session));
}
private Predicate<WebSocketSession> isValid(String topic, WebSocketSession session) {
return s -> s != session && topic.equalsIgnoreCase(s.getUriVariables().get("topic", String.class, null));
}
}
WebSocket Chat Example
import io.micronaut.websocket.WebSocketBroadcaster
import io.micronaut.websocket.WebSocketSession
import io.micronaut.websocket.annotation.OnClose
import io.micronaut.websocket.annotation.OnMessage
import io.micronaut.websocket.annotation.OnOpen
import io.micronaut.websocket.annotation.ServerWebSocket
import java.util.function.Predicate
@ServerWebSocket("/chat/{topic}/{username}") (1)
class ChatServerWebSocket {
private WebSocketBroadcaster broadcaster
ChatServerWebSocket(WebSocketBroadcaster broadcaster) {
this.broadcaster = broadcaster
}
@OnOpen (2)
void onOpen(String topic, String username, WebSocketSession session) {
String msg = "[" + username + "] Joined!"
broadcaster.broadcastSync(msg, isValid(topic, session))
}
@OnMessage (3)
void onMessage(
String topic,
String username,
String message,
WebSocketSession session) {
String msg = "[" + username + "] " + message
broadcaster.broadcastSync(msg, isValid(topic, session)) (4)
}
@OnClose (5)
void onClose(
String topic,
String username,
WebSocketSession session) {
String msg = "[" + username + "] Disconnected!"
broadcaster.broadcastSync(msg, isValid(topic, session))
}
private Predicate<WebSocketSession> isValid(String topic, WebSocketSession session) {
return { s -> s != session && topic.equalsIgnoreCase(s.getUriVariables().get("topic", String.class, null)) }
}
}
WebSocket Chat Example
import io.micronaut.websocket.WebSocketBroadcaster
import io.micronaut.websocket.WebSocketSession
import io.micronaut.websocket.annotation.OnClose
import io.micronaut.websocket.annotation.OnMessage
import io.micronaut.websocket.annotation.OnOpen
import io.micronaut.websocket.annotation.ServerWebSocket
import java.util.function.Predicate
@ServerWebSocket("/chat/{topic}/{username}") (1)
class ChatServerWebSocket(private val broadcaster: WebSocketBroadcaster) {
@OnOpen (2)
fun onOpen(topic: String, username: String, session: WebSocketSession) {
val msg = "[$username] Joined!"
broadcaster.broadcastSync(msg, isValid(topic, session))
}
@OnMessage (3)
fun onMessage(
topic: String,
username: String,
message: String,
session: WebSocketSession) {
val msg = "[$username] $message"
broadcaster.broadcastSync(msg, isValid(topic, session)) (4)
}
@OnClose (5)
fun onClose(
topic: String,
username: String,
session: WebSocketSession) {
val msg = "[$username] Disconnected!"
broadcaster.broadcastSync(msg, isValid(topic, session))
}
private fun isValid(topic: String, session: WebSocketSession): Predicate<WebSocketSession> {
return Predicate<WebSocketSession>{ s -> (s !== session && topic.equals(s.getUriVariables().get("topic", String::class.java, null), ignoreCase = true)) }
}
}
1 | The @ServerWebSocket annotation is used to define the path the WebSocket is mapped under. The URI can be a URI template. |
2 | The @OnOpen annotation is used to declare a method that is invoked when the WebSocket is opened. |
3 | The @OnMessage annotation is used to declare a method that is invoked when a message is received. |
4 | You can use a WebSocketBroadcaster to broadcast messages to every WebSocket session. You can filter which sessions to communicate with a Predicate . Also, you could use the passed WebSocketSession instance to send a message to it with WebSocketSession::send . |
5 | The @OnClose annotation is used to declare a method that is invoked when the WebSocket is closed. |
A working example of WebSockets in action can be found in the Micronaut Examples GitHub repository. |
In terms of binding the method arguments to each WebSocket method can be:
A variable from the URI template (in the above example
topic
andusername
are variables in the URI template)An instance of WebSocketSession
The @OnClose Method
The @OnClose method can also optionally receive a CloseReason. The @OnClose
method is invoked prior to the session closing.
The @OnMessage Method
The @OnMessage method can define a parameter that is the message body. The parameter can be one of the following:
A Netty
WebSocketFrame
Any Java primitive or simple type (such as
String
). In fact any type that can be converted fromByteBuf
(you can register additional TypeConverter beans if you wish to support a custom type).A
byte[]
, aByteBuf
or a Java NIOByteBuffer
.A Plain Old Java Object (POJO). In the case of a POJO the POJO will be decoded by default as JSON using JsonMediaTypeCodec. You can register a custom codec if necessary and define the content type of the handler using the @Consumes annotation.
The @OnError Method
A method annotated with @OnError can be added to implement custom error handling. The @OnError
method can optionally define a parameter that receives the exception type that is to be handled. If no @OnError
handling is present and a unrecoverable exception occurs the WebSocket is automatically closed.
Non-Blocking Message Handling
The previous example uses the broadcastSync
method of the WebSocketBroadcaster interface which blocks until the broadcast is complete. A similar sendSync
method exists in WebSocketSession to send a message to a single receiver in a blocking manner. You can however implement non-blocking WebSocket servers by instead returning a Publisher or a Future from each WebSocket handler method. For example:
WebSocket Chat Example
@OnMessage
public Publisher<Message> onMessage(
String topic,
String username,
Message message,
WebSocketSession session) {
String text = "[" + username + "] " + message.getText();
Message newMessage = new Message(text);
return broadcaster.broadcast(newMessage, isValid(topic, session));
}
WebSocket Chat Example
@OnMessage
Publisher<Message> onMessage(
String topic,
String username,
Message message,
WebSocketSession session) {
String text = "[" + username + "] " + message.getText()
Message newMessage = new Message(text)
broadcaster.broadcast(newMessage, isValid(topic, session))
}
WebSocket Chat Example
@OnMessage
fun onMessage(
topic: String,
username: String,
message: Message,
session: WebSocketSession): Publisher<Message> {
val text = "[" + username + "] " + message.text
val newMessage = Message(text)
return broadcaster.broadcast(newMessage, isValid(topic, session))
}
The example above uses broadcast
, which creates an instance of Publisher and returns the value to Micronaut. Micronaut will then take care of sending the message asynchronously based on the Publisher interface. The similar send
method can be used to send a single message asynchronously via Micronaut return value.
For sending messages asynchronously outside Micronaut annotated handler methods, you can use broadcastAsync
and sendAsync
methods in their respective WebSocketBroadcaster and WebSocketSession interfaces. For blocking sends, the broadcastSync
and sendSync
methods can be used.
@ServerWebSocket and Scopes
By default a unique @ServerWebSocket
instance is created for each WebSocket connection. This allows you to retrieve the WebSocketSession from the @OnOpen
handler and assign it to a field of the @ServerWebSocket
instance.
If you define the @ServerWebSocket
as @Singleton
it should be noted that extra care will need to be taken to synchronize local state to avoid thread safety issues.
Sharing Sessions with the HTTP Session
The WebSocketSession is by default backed by an in-memory map. If you add the the session
module you can however share sessions between the HTTP server and the WebSocket server.
When sessions are backed by a persistent store such as Redis then after each message is processed the session is updated to the backing store. |
Using the CLI If you have created your project using the Micronaut CLI and the default (
|
Connection Timeouts
By default Micronaut will timeout idle connections that have no activity after 5 minutes. Normally this is not a problem as browsers will automatically reconnect WebSocket sessions, however you can control this behaviour by setting the micronaut.server.idle-timeout
setting (a negative value will result no timeout):
Setting the Connection Timeout for the Server
micronaut:
server:
idle-timeout: 30m # 30 minutes
If you are using Micronaut’s WebSocket client then you may also wish to set the timeout on the client:
Setting the Connection Timeout for the Client
micronaut:
http:
client:
read-idle-timeout: 30m # 30 minutes