diff --git a/.vitepress/sidebars/javalin.js b/.vitepress/sidebars/javalin.js index 84599d7..d1ba2f3 100644 --- a/.vitepress/sidebars/javalin.js +++ b/.vitepress/sidebars/javalin.js @@ -54,5 +54,22 @@ export const javalin = [ { text: 'CSS and assets', link: '/javalin/css-and-assets' }, { text: 'User input', link: '/javalin/user-input' } ] + }, + { + text: 'Web sockets', + items: [ + { + text: 'Creating a web socket server', + link: '/javalin/web-socket-server' + }, + { + text: 'Syncing clients', + link: '/javalin/syncing-clients' + }, + { + text: 'Creating a web socket client', + link: '/javalin/web-socket-client' + } + ] } ] diff --git a/src/javalin/syncing-clients.md b/src/javalin/syncing-clients.md new file mode 100644 index 0000000..7e23d87 --- /dev/null +++ b/src/javalin/syncing-clients.md @@ -0,0 +1,112 @@ +# Syncing clients + +## Setting up the server + +As a quick reminder, let's see again the basic server set up: + +```java +import io.javalin.Javalin; + +public class WebSocketSync { + public static void main(String[] args) { + Javalin app = Javalin.create().start(5001); + + app.ws("/websocket", ws -> { + ws.onConnect(ctx -> { + // Handle new connections + System.out.println("New connection: " + ctx.getSessionId()); + }); + }); + } +} +``` + +## Tracking sockets + +To update all clients, we need to keep track of the WebSocket sessions. +Fortunately, Javalin provides a `WsContext` object for each connection, which we +can store in a `List` or `Set` for easy tracking. + +```java +import io.javalin.Javalin; +import io.javalin.websocket.WsContext; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class WebSocketSync { + private static final Set connectedClients = + Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public static void main(String[] args) { + Javalin app = Javalin.create().start(5001); + + app.ws("/websocket", ws -> { + ws.onConnect(ctx -> { + connectedClients.add(ctx); + System.out.println("Connected clients: " + connectedClients.size()); + }); + + ws.onClose(ctx -> { + connectedClients.remove(ctx); + System.out.println("Connection closed. Remaining clients: " + connectedClients.size()); + }); + }); + } +} +``` + +- The `connectedClients` set holds references to all currently active WebSocket + sessions. + +- When a client connects, it is added to the `connectedClients` set. + +- When a client disconnects, it is removed from the set. + +## Forwarding messages + +To broadcast messages received from one client to all other connected clients, +we iterate over the `connectedClients` set and call the `send` method for each +session. + +```java +import io.javalin.Javalin; +import io.javalin.websocket.WsContext; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class WebSocketSync { + private static final Set connectedClients = + Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public static void main(String[] args) { + Javalin app = Javalin.create().start(5001); + + app.ws("/websocket", ws -> { + ws.onConnect(ctx -> connectedClients.add(ctx)); + + ws.onClose(ctx -> connectedClients.remove(ctx)); + + ws.onMessage((ctx, message) -> { + for (WsContext client : connectedClients) { + if (client != ctx) { // Avoid echoing the message back to the sender + client.send(message); + } + } + }); + }); + } +} + +``` + +- The `onMessage` handler receives messages from a client. + +- The message is forwarded to all other connected clients using the `send` + method. + +And that's it! Now all connected users will receive any message sent by any +other user. diff --git a/src/javalin/web-socket-client.md b/src/javalin/web-socket-client.md new file mode 100644 index 0000000..238311a --- /dev/null +++ b/src/javalin/web-socket-client.md @@ -0,0 +1,97 @@ +# Creating a web socket client + +This is a front-end task, but it's important to complete the picture on working +with web sockets, so we'll demonstrate here how we send and receive messages on +the client. + +::: code-group + +```html + + + + + + + + Document + + + +

Simple chat

+ +
+ + +
+ +

Messages

+ + + +``` + +```js +document.addEventListener('DOMContentLoaded', main) + +function main() { + // make a client-side socket + const socket = new WebSocket('ws://localhost:5001/websocket') + + // get a reference to the elemets we need + const form = document.getElementById('form') + const chat = document.getElementById('chat') + + // handle submit events + form.addEventListener('submit', e => { + // stop the page from reloading + e.preventDefault() + + // get the message from the textarea + const data = new FormData(form) + + // send it to the server + socket.send(data.get('content')) + + // clear the form + form.reset() + }) + + // handle new messages + socket.onmessage = async message => { + // create a new
  • element + const li = document.createElement('li') + + // insert the text from the incoming message + li.innerText = await message.data.text() + + // add the
  • to the chat area + chat.appendChild(li) + } +} +``` + +```css +form { + width: 100%; + max-width: 250px; + display: flex; + flex-direction: column; + gap: 1rem; +} + +form > * { + display: block; + padding: 0.5rem; +} + +li { + margin-bottom: 1rem; +} +``` + +::: diff --git a/src/javalin/web-socket-server.md b/src/javalin/web-socket-server.md new file mode 100644 index 0000000..ec744d7 --- /dev/null +++ b/src/javalin/web-socket-server.md @@ -0,0 +1,108 @@ +# Web socket servers + +::: info + +Although web sockets aren't specific to Javalin, this part of the docs focuses +heavily on servers, so we felt these notes belonged here. + +::: + +## Creating a web socket server + +To create a WebSocket server in Javalin, we need the Javalin WebSocket plugin, +which is included in the core Javalin library. + +Then, create the Javalin app and enable WebSocket functionality: + +```java +import io.javalin.Javalin; + +public class WebSocketServer { + public static void main(String[] args) { + Javalin app = Javalin.create(config -> { + config.wsFactoryConfig(ws -> { + // WebSocket settings can be configured here + }); + }).start(5001); + } +} +``` + +## Listening for connections + +Listening for new WebSocket connections is straightforward. Use the `ws` method +to define WebSocket endpoints. + +```java +import io.javalin.Javalin; +import io.javalin.websocket.WsConnectContext; + +public class WebSocketServer { + public static void main(String[] args) { + Javalin app = Javalin.create().start(5001); + + app.ws("/websocket", ws -> { + ws.onConnect(ctx -> { + System.out.println("New connection: " + ctx.getSessionId()); + // do what we want with the connection + }); + }); + } +} + +``` + +When a client connects to the WebSocket endpoint (`/websocket`), the `onConnect` +handler is triggered, passing a `WsConnectContext` object (`ctx`) representing +the connection. + +## Listening for socket events + +We can listen for messages from the client using the `onMessage` event and +handle connection closures using the `onClose` event. + +```java +import io.javalin.Javalin; +import io.javalin.websocket.WsContext; + +public class WebSocketServer { + public static void main(String[] args) { + Javalin app = Javalin.create().start(5001); + + app.ws("/websocket", ws -> { + ws.onConnect(ctx -> System.out.println("Connected: " + ctx.getSessionId())); + ws.onMessage((ctx, message) -> handleMessage(ctx, message)); + ws.onClose((ctx, status, reason) -> cleanUp(ctx)); + }); + } + + private static void handleMessage(WsContext ctx, String message) { + System.out.println("Received message: " + message); + } + + private static void cleanUp(WsContext ctx) { + System.out.println("Connection closed: " + ctx.getSessionId()); + } +} +``` + +- `onMessage`: Triggered when a message is received from the client. + +- `onClose`: Triggered when the client closes the connection, allowing us to + perform cleanup. + +## Sending a message back + +At any point, we can send a message to the client using the `send` method on the +`WsContext`. + +```java +app.ws("/websocket", ws -> { + ws.onConnect(ctx -> { + ctx.send("Hello from the server!"); + }); +}); +``` + +The client will receive the message immediately, provided the connection is +still open.