6.26.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 final 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 final 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.uriVariables.get("topic", String, 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> {
(it !== session && topic.equals(it.uriVariables.get("topic", String::class.java, null), ignoreCase = true))
}
}
}
1 | The @ServerWebSocket annotation defines the path the WebSocket is mapped under. The URI can be a URI template. |
2 | The @OnOpen annotation declares the method to invoke when the WebSocket is opened. |
3 | The @OnMessage annotation declares the method to invoke when a message is received. |
4 | You can use a WebSocketBroadcaster to broadcast messages to every WebSocket session. You can filter which sessions to send to with a Predicate . Also, you could use the WebSocketSession instance to send a message to it with WebSocketSession::send . |
5 | The @OnClose annotation declares the method to invoke when the WebSocket is closed. |
A working example of WebSockets in action can be found in the Micronaut Examples GitHub repository. |
For binding, method arguments to each WebSocket method can be:
A variable from the URI template (in the above example
topic
andusername
are URI template variables)An instance of WebSocketSession
The @OnClose Method
The @OnClose method can optionally receive a CloseReason. The @OnClose
method is invoked prior to the session closing.
The @OnMessage Method
The @OnMessage method can define a parameter for 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 to support a custom type).A
byte[]
, aByteBuf
or a Java NIOByteBuffer
.A POJO. In this case, it will be decoded by default as JSON using JsonMediaTypeCodec. You can register a custom codec 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 define a parameter that receives the exception type to be handled. If no @OnError
handling is present and an 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.text"
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 sends the message asynchronously based on the Publisher interface. The similar send
method sends 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 lets you retrieve the WebSocketSession from the @OnOpen
handler and assign it to a field of the @ServerWebSocket
instance.
If you define the @ServerWebSocket
as @Singleton
, extra care must 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 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, after each message is processed the session is updated to the backing store. |
Using the CLI If you created your project using Application Type
|
Connection Timeouts
By default, Micronaut times out idle connections with no activity after five minutes. Normally this is not a problem as browsers automatically reconnect WebSocket sessions, however you can control this behaviour by setting the micronaut.server.idle-timeout
setting (a negative value results in no timeout):
Setting the Connection Timeout for the Server
micronaut:
server:
idle-timeout: 30m # 30 minutes
If you use Micronaut’s WebSocket client 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