WebSocket Chat Example
WebSocket Chat Example
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 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.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,
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)) }
}
}
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
The @OnClose method can also optionally receive a . The @OnClose
method is invoked prior to the session closing.
The @OnMessage Method
The method can define a parameter that is the message body. The parameter can be one of the following:
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[]
, a 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 . You can register a custom codec if necessary and define the content type of the handler using the @Consumes annotation.
A method annotated with 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 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 or a Future from each WebSocket handler method. For example:
WebSocket Chat Example
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))
}
For sending messages asynchronously outside Micronaut annotated handler methods, you can use broadcastAsync
and sendAsync
methods in their respective and WebSocketSession interfaces. For blocking sends, the broadcastSync
and sendSync
methods can be used.
By default a unique @ServerWebSocket
instance is created for each WebSocket connection. This allows you to retrieve the 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 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.
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:
micronaut:
http: