WebSockets are added using the javadoc:Router[ws] method:
{
ws("/ws", (ctx, configurer) -> { // (1)
configurer.onConnect(ws -> {
ws.send("Connected"); // (2)
});
configurer.onMessage((ws, message) -> {
ws.send("Got " + message.value()); // (3)
});
configurer.onClose((ws, statusCode) -> {
// Clean up resources (4)
});
configurer.onError((ws, cause) -> {
// Handle exceptions (5)
});
});
}{
ws("/ws") { ctx, configurer -> // (1)
configurer.onConnect { ws ->
ws.send("Connected") // (2)
}
configurer.onMessage { ws, message ->
ws.send("Got " + message.value()) // (3)
}
configurer.onClose { ws, statusCode ->
// Clean up resources (4)
}
configurer.onError { ws, cause ->
// Handle exceptions (5)
}
}
}-
Register a WebSocket handler.
-
On connection (open), send a message back to the client. This is also a good place to initialize resources.
-
On receiving a new message, send a response back to the client.
-
The WebSocket is about to close. You must free/release any acquired resources here.
-
The WebSocket encountered an exception. Useful for logging the error or providing an alternative response if the socket is still open.
You are free to access the HTTP context from the WebSocket configurer or callbacks, but it is forbidden to modify the HTTP context or produce an HTTP response from it.
{
ws("/ws/{key}", (ctx, configurer) -> {
String key = ctx.path("key").value(); // (1)
String foo = ctx.session().get("foo").value(); // (2)
// ...
});
}{
ws("/ws/{key}") { ctx, configurer ->
val key = ctx.path("key").value() // (1)
val foo = ctx.session().get("foo").value() // (2)
// ...
}
}-
Access a path variable (
key). -
Access a session variable (
foo).
Structured data (like JSON) is supported using the Value API and the javadoc:WebSocket[render, java.lang.Object] method.
To use structured messages, you need a registered javadoc:MessageDecoder[] and javadoc:MessageEncoder[]. In the following example, both are provided by the Jackson2Module.
import io.jooby.jackson.Jackson2Module;
{
install(new Jackson2Module()); // (1)
ws("/ws", (ctx, configurer) -> {
configurer.onMessage((ws, message) -> {
MyObject myobject = message.to(MyObject.class); // (2)
ws.render(myobject); // (3)
});
});
}import io.jooby.jackson.Jackson2Module
{
install(Jackson2Module()) // (1)
ws("/ws") { ctx, configurer ->
configurer.onMessage { ws, message ->
val myobject = message.to<MyObject>() // (2)
ws.render(myobject) // (3)
}
}
}-
Install the Jackson module (required for JSON decoding/encoding).
-
Parse and decode the incoming message to a
MyObject. -
Encode
myobjectas JSON and send it to the client.
Alternatively, you can explicitly tell the WebSocket which decoder/encoder to use by specifying the consumes and produces attributes:
import io.jooby.jackson.Jackson2Module;
{
install(new Jackson2Module()); // (1)
ws("/ws", (ctx, configurer) -> {
configurer.onMessage((ws, message) -> {
MyObject myobject = message.to(MyObject.class); // (2)
ws.render(myobject); // (3)
});
})
.consumes(MediaType.json)
.produces(MediaType.json);
}import io.jooby.jackson.Jackson2Module
{
install(Jackson2Module()) // (1)
ws("/ws") { ctx, configurer ->
configurer.onMessage { ws, message ->
val myobject = message.to<MyObject>() // (2)
ws.render(myobject) // (3)
}
}.consumes(MediaType.json)
.produces(MediaType.json)
}You can implement the same WebSocket as above using annotated classes in declarative style.
Ensure that jooby-apt is in the annotation processor path, annotate the class with javadoc:annotation.Path[],
and mark methods with javadoc:annotation.ws.OnConnect[], javadoc:annotation.ws.OnMessage[], javadoc:annotation.ws.OnClose[], and javadoc:annotation.ws.OnError[]. Compile code to generate an extension javadoc:Extension[] and register it by calling javadoc:Jooby[ws, io.jooby.Extension].
When a lifecycle method returns a value, that value is written to the client automatically: plain text or binary for String, byte[], and ByteBuffer, and structured values (for example JSON) using the same encoders as in Structured Data. Alternatively, use a void method and send with ws.send(…) on the javadoc:WebSocket[] argument.
@Path("/chat/{room}") // (1)
public class ChatSocket {
@OnConnect
public String onConnect(WebSocket ws, Context ctx) { // (2)
return "welcome";
}
@OnMessage
public Map<String, String> onMessage(WebSocket ws, Context ctx, WebSocketMessage message) { // (3)
return Map.of("echo", message.value());
// ws.send(message.value()); // (4)
}
@OnClose
public void onClose(WebSocket ws, Context ctx, WebSocketCloseStatus status) {}
@OnError
public void onError(WebSocket ws, Context ctx, Throwable cause) {}
}
// Application startup:
{
ws(new ChatSocketWs_()); // (5)
}@Path("/chat/{room}") // (1)
class ChatSocket {
@OnConnect
fun onConnect(ws: WebSocket, ctx: Context): String { // (2)
return "welcome"
}
@OnMessage
fun onMessage(ws: WebSocket, ctx: Context, message: WebSocketMessage): Map<String, String> { // (3)
return mapOf("echo" to message.value())
// ws.send(message.value()) // (4)
}
@OnClose
fun onClose(ws: WebSocket, ctx: Context, status: WebSocketCloseStatus) {}
@OnError
fun onError(ws: WebSocket, ctx: Context, cause: Throwable) {}
}
// Application startup:
{
ws(ChatSocketWs_()) // (5)
}-
WebSocket route patterns for this handler.
-
Returning a value sends it to the client without calling
send. -
Return a value for automatic encoding (see Structured Data)
-
You still can use
ws.send(…)if method return type isvoid. -
Register the generated extension with javadoc:Jooby[ws, io.jooby.Extension].
@OnMessage handlers also support parsing messages into structured data, similar to MVC methods:
@Path("/chat/{room}")
public class ChatSocket {
record ChatMessage(String username, String message, String type) {}
@OnMessage
public Map<String, ChatMessage> onMessage(ChatMessage message) { // (1)
return Map.of("echo", message);
}
...
}@Path("/chat/{room}")
class ChatSocket {
data class ChatMessage(
val username: String,
val message: String,
val type: String
)
@OnMessage
fun onMessage(message: ChatMessage): Map<String, ChatMessage> { // (1)
return mapOf("echo" to message)
}
...
}-
WebSocket message is automatically decoded into
ChatMessagestructure.
Jooby automatically times out idle connections that have no activity after 5 minutes. You can control this behavior by setting the websocket.idleTimeout property in your configuration file:
websocket.idleTimeout = 1hSee the Typesafe Config documentation for the supported duration format.
The maximum message size is set to 128K by default. You can override it using the websocket.maxSize property:
websocket.maxSize = 128KSee the Typesafe Config documentation for the supported size in bytes format.