Spring Boot WebSockets: Real-time Chat App හදමු | SC Guide

Spring Boot WebSockets: Real-time Chat App හදමු | SC Guide

Real-time Magic with Spring Boot WebSockets: A Chat App SC Guide

ආයුබෝවන් කට්ටියටම! කොහොමද ඉතින්, ඔයාලා හැමෝම හොඳින් ඇති කියලා හිතනවා. අද අපි කතා කරන්න යන්නේ මේ දවස් වල බොහොම ජනප්‍රිය, වගේම අපේ දෛනික ජීවිතයේදී ගොඩක් දකින්න ලැබෙන දෙයක් ගැන – ඒ තමයි Real-time Communication. ඔයාලා දන්නවා නේද, Facebook එකේ notifications, WhatsApp එකේ chat messages, Cricbuzz එකේ live scores, Stock market apps වල update වෙන්නේ කොහොමද කියලා? මේ හැමදේකම පිටිපස්සේ තියෙන ප්‍රධානම තාක්ෂණය තමයි Real-time Communication.

අද අපි බලමු Spring Boot වගේ powerful framework එකක් පාවිච්චි කරලා, WebSocket තාක්ෂණයෙන් කොහොමද මේ වගේ Real-time Communication එකක් implement කරන්නේ කියලා. සරලවම, අපි පුංචි Chat Application එකක් හදලා මේක ප්‍රායෝගිකවම ඉගෙන ගමු. මේක Java developersලාට වගේම, web development ගැන උනන්දුවක් තියෙන ඕනම කෙනෙක්ට ගොඩක් වටිනා දෙයක් වෙයි!

නියමයි! Real-time Communication කියන්නේ මොකක්ද?

හරි, මුලින්ම බලමු මේ Real-time Communication කියන්නේ මොකක්ද කියලා. සාමාන්‍යයෙන් අපේ web applications වැඩ කරන්නේ HTTP Request-Response model එකට. ඒ කියන්නේ, client කෙනෙක් (browser එකක් වගේ) server එකට request එකක් යවනවා, server එක ඒකට response එකක් දෙනවා. ඊට පස්සේ connection එක close වෙනවා. අලුත් data ඕන නම්, ආයෙත් client request එකක් යවන්න ඕන.

ඒත් Real-time Communication වලදී එහෙම නෑ. මෙතනදී clientයි serverයි අතර continuous, persistent connection එකක් maintain වෙනවා. ඒක හරියට phone call එකක් වගේ. Connection එක establish වුණාට පස්සේ, දෙගොල්ලන්ටම එකිනෙකාට data යවන්න පුළුවන්, ඕන වෙලාවක. මේකට අපි කියනවා full-duplex communication කියලා. මේ නිසා තමයි chat apps වලදී messages ක්ෂණිකව යවන්නත්, ගන්නත් පුළුවන් වෙන්නේ.

මේකේ වාසි ගොඩයි. ප්‍රධානම දේ තමයි low latency. ඒ කියන්නේ data ගොඩක් ඉක්මනට යැවෙන්නෙයි, ලැබෙන්නෙයි. අනික, resource usage එකත් අඩුයි, මොකද හැම වෙලාවෙම අලුතෙන් connection එකක් establish කරන්න ඕන නැති නිසා. සාමාන්‍ය HTTP polling වලට වඩා මේක ගොඩක් effective.

WebSocket කියන්නේ මොකක්ද?

Real-time Communication සඳහා පාවිච්චි කරන ප්‍රධානම protocol එක තමයි WebSocket. 2011 දී RFC 6455 විදිහට standardize වුණාට පස්සේ, මේක වෙබ් එකේ ගේම් changer එකක් වුණා කිව්වොත් නිවැරදියි. WebSocket එක HTTP connection එකක් upgrade කරලා තමයි වැඩ කරන්නේ. මුලින්ම HTTP handshake එකක් සිද්ධ වෙනවා, ඊට පස්සේ ඒක WebSocket connection එකකට upgrade වෙනවා. ඊට පස්සේ HTTP headers එහෙම send කරන්න ඕන නෑ, data frames විතරක් යවන්න පුළුවන්.

WebSocket වල වාසි කිහිපයක් මෙන්න:

  • Full-Duplex Communication: එකවර දෙපැත්තටම data යැවිය හැකි වීම.
  • Low Latency: Connection එක නිතරම විවෘතව තියෙන නිසා data ඉතා ඉක්මනින් හුවමාරු වීම.
  • Reduced Overhead: HTTP request-response cycle එකේ තියෙන overhead එක නැති වීම.
  • Cross-Origin Communication: CORS restrictions නැතුව වැඩ කරන්න පුළුවන්.

දැන් අපි බලමු මේ WebSocket අපේ Spring Boot application එකට කොහොමද එකතු කරගන්නේ කියලා.

Spring Boot එක්ක WebSocket Chat App එකක් හදමු!

අපි දැන් අපේම Chat Application එකක් හදමු. මේකේදී අපි Spring Boot backend එකක් සහ සරල HTML/JavaScript frontend එකක් පාවිච්චි කරනවා.

1. Project Setup

මුලින්ම Spring Initializr (start.spring.io) එකට ගිහින් අලුත් Spring Boot project එකක් හදාගමු. මේකේදී අපි පහත dependencies තෝරගන්න ඕන:

  • Spring Web: RESTful APIs වගේම සාමාන්‍ය Web functionality වලට.
  • Spring WebSocket: WebSocket functionality වලට.
  • Lombok: Boilerplate code අඩු කරගන්න (Optional, but recommended).

Project එක generate කරලා, IDE එකකින් (IntelliJ IDEA, VS Code වගේ) open කරගන්න.

2. Backend Implementation

අපේ Chat App එකේ backend එක තමයි Spring Boot වලින් හදන්නේ. මේකට අපිට ප්‍රධාන Classes දෙකක් ඕන වෙනවා:

  1. WebSocketConfig: WebSocket configuration එක කරන Class එක.
  2. ChatMessage: Chat messages වල data structure එක define කරන DTO (Data Transfer Object) Class එක.
  3. ChatController: Client එකෙන් එන messages handle කරලා, අනිත් clientලාට යවන Controller එක.

2.1. WebSocketConfig.java

මේ Class එක තමයි WebSocket communication එකට අවශ්‍ය configure කිරීම් කරන්නේ. අපි MessageBrokerRegistry එකක් register කරනවා. STOMP (Simple Text Oriented Messaging Protocol) කියන sub-protocol එක තමයි WebSocket එක්ක වැඩ කරන්න අපි පාවිච්චි කරන්නේ. STOMP කියන්නේ WebSocket මත run වෙන lightweight messaging protocol එකක්.

package com.example.chatapp.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // Client එකට WebSocket connection එකක් establish කරන්න පුළුවන් endpoint එක. 
        // අපි 'ws://localhost:8080/ws' වගේ එකකින් connect වෙනවා.
        // '.setAllowedOrigins("*")' දාලා තියෙන්නේ CORS issues නැති කරගන්න, production වලදී හරියට domains දාන්න ඕන.
        registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // මෙතන තමයි server එකේ message broker එක configure කරන්නේ.
        // '/app' prefix එක තියෙන messages controller එකට route වෙනවා.
        registry.setApplicationDestinationPrefixes("/app");
        
        // '/topic' සහ '/queue' prefix තියෙන messages message broker එකට යවලා,
        // subscribe කරලා ඉන්න clientsලාට broadcast කරනවා.
        registry.enableSimpleBroker("/topic", "/queue");
    }
}

2.2. ChatMessage.java

මෙය අපේ Chat Message එකක data structure එක define කරන සරල POJO (Plain Old Java Object) එකක්. Lombok පාවිච්චි කරන නිසා @Data annotation එකෙන් getters, setters, constructors හැමදේම auto generate වෙනවා.

package com.example.chatapp.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatMessage {
    private String sender;
    private String content;
    private MessageType type;

    public enum MessageType {
        CHAT,
        JOIN,
        LEAVE
    }
}

2.3. ChatController.java

මේ Controller එකෙන් තමයි client එකෙන් එන messages handle කරලා, අනිත් clientලාට යවන්නේ. @MessageMapping සහ @SendTo annotations තමයි මෙතන ප්‍රධාන වෙන්නේ.

  • @MessageMapping("/chat.sendMessage"): Client එකක් /app/chat.sendMessage කියන destination එකට message එකක් එව්වොත්, මේ method එක run වෙනවා.
  • @SendTo("/topic/public"): මේ method එකෙන් return වෙන message එක /topic/public කියන topic එකට subscribe කරලා ඉන්න හැම client කෙනෙක්ටම යවනවා.
package com.example.chatapp.controller;

import com.example.chatapp.model.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

    // Client එකකින් message එකක් send කලොත් මේ method එක run වෙනවා.
    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        // Payload එකෙන් message එක අරගෙන, අනිත් clientලාට publish කරනවා.
        return chatMessage;
    }

    // අලුත් user කෙනෙක් join වුණොත් මේ method එක run වෙනවා.
    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage, 
                               SimpMessageHeaderAccessor headerAccessor) {
        // WebSocket session එකට user name එක add කරනවා.
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        // Join message එක publish කරනවා.
        return chatMessage;
    }
}

මේ තමයි අපේ Spring Boot backend එක. දැන් අපි මේක run කරලා, frontend එක හදමු.

3. Frontend Implementation (Basic HTML/JS)

දැන් අපි සරල HTML file එකක් සහ JavaScript ටිකක් පාවිච්චි කරලා chat interface එක හදාගමු. මේකට අපි sockjs.min.js සහ stomp.min.js කියන libraries දෙක පාවිච්චි කරනවා. SockJS කියන්නේ WebSocket fallback mechanism එකක් සපයන library එකක්, ඒ කියන්නේ browser එකට WebSocket support නැති වුණොත්, වෙන විදිහකට connection එක establish කරනවා. STOMP client library එක තමයි අපිට STOMP protocol එකෙන් messages යවන්නයි, ගන්නයි උදව් කරන්නේ.

ඔයාලගේ Spring Boot project එකේ src/main/resources/static folder එක ඇතුලේ index.html කියලා file එකක් හදලා මේ code එක දාන්න.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Spring Boot WebSocket Chat App</title>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        #chat-container { max-width: 600px; margin: 0 auto; border: 1px solid #ccc; padding: 20px; box-shadow: 2px 2px 8px rgba(0,0,0,0.1); }
        #messages { border: 1px solid #eee; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
        .message { margin-bottom: 5px; }
        .message strong { color: blue; }
        .join-leave { color: gray; font-style: italic; }
        #message-input-area { display: flex; }
        #messageInput { flex-grow: 1; padding: 8px; border: 1px solid #ccc; }
        #sendButton { padding: 8px 15px; background-color: #007bff; color: white; border: none; cursor: pointer; }
        #username-input-area { margin-bottom: 15px; }
        #usernameInput { padding: 8px; border: 1px solid #ccc; }
        #connectButton, #disconnectButton { padding: 8px 15px; margin-right: 5px; background-color: #28a745; color: white; border: none; cursor: pointer; }
        #disconnectButton { background-color: #dc3545; }
    </style>
</head>
<body>
    <div id="chat-container">
        <h2>Spring Boot WebSocket Chat</h2>

        <div id="username-input-area">
            <input type="text" id="usernameInput" placeholder="ඔබේ නම ඇතුලත් කරන්න..." />
            <button id="connectButton">Connect</button>
            <button id="disconnectButton" disabled>Disconnect</button>
        </div>

        <div id="messages"></div>

        <div id="message-input-area">
            <input type="text" id="messageInput" placeholder="පණිවිඩයක් ටයිප් කරන්න..." disabled />
            <button id="sendButton" disabled>Send</button>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

    <script>
        let stompClient = null;
        let username = null;

        const messagesDiv = document.getElementById('messages');
        const usernameInput = document.getElementById('usernameInput');
        const connectButton = document.getElementById('connectButton');
        const disconnectButton = document.getElementById('disconnectButton');
        const messageInput = document.getElementById('messageInput');
        const sendButton = document.getElementById('sendButton');

        connectButton.addEventListener('click', connect);
        disconnectButton.addEventListener('click', disconnect);
        sendButton.addEventListener('click', sendMessage);
        messageInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') { sendMessage(); }
        });

        function connect() {
            username = usernameInput.value.trim();

            if (username) {
                usernameInput.disabled = true;
                connectButton.disabled = true;
                disconnectButton.disabled = false;
                messageInput.disabled = false;
                sendButton.disabled = false;

                // SockJS endpoint එකට connect වෙනවා (මේක තමයි backend එකේ /ws).
                const socket = new SockJS('/ws'); 
                stompClient = Stomp.over(socket);

                stompClient.connect({}, onConnected, onError);
            } else {
                alert('කරුණාකර ඔබේ නම ඇතුලත් කරන්න.');
            }
        }

        function onConnected() {
            // Public topic එකට subscribe කරනවා. මේකෙන් තමයි messages ලැබෙන්නේ.
            stompClient.subscribe('/topic/public', onMessageReceived);

            // Join message එක server එකට යවනවා.
            stompClient.send("/app/chat.addUser", {}, 
                JSON.stringify({sender: username, type: 'JOIN'}));
            
            messagesDiv.innerHTML += `<div class="join-leave">${username} චැට් එකට සම්බන්ධ වුණා!</div>`;
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        function onError(error) {
            messagesDiv.innerHTML += `<div style="color: red;">WebSocket සම්බන්ධතාවයේ දෝෂයක්: ${error}</div>`;
            console.error('Could not connect to WebSocket server. Please refresh this page to try again!', error);
            resetUI();
        }

        function sendMessage() {
            const messageContent = messageInput.value.trim();
            if (messageContent && stompClient) {
                const chatMessage = {
                    sender: username,
                    content: messageContent,
                    type: 'CHAT'
                };
                // Message එක server එකට යවනවා.
                stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
                messageInput.value = '';
            }
        }

        function onMessageReceived(payload) {
            const message = JSON.parse(payload.body);

            let messageElement = document.createElement('div');
            messageElement.classList.add('message');

            if (message.type === 'JOIN') {
                messageElement.classList.add('join-leave');
                messageElement.innerHTML = `<div><strong>${message.sender}</strong> චැට් එකට සම්බන්ධ විය.</div>`;
            } else if (message.type === 'LEAVE') {
                messageElement.classList.add('join-leave');
                messageElement.innerHTML = `<div><strong>${message.sender}</strong> චැට් එකෙන් ඉවත් විය.</div>`;
            } else {
                messageElement.innerHTML = `<div><strong>${message.sender}:</strong> ${message.content}</div>`;
            }

            messagesDiv.appendChild(messageElement);
            messagesDiv.scrollTop = messagesDiv.scrollHeight; // Scroll to bottom
        }

        function disconnect() {
            if (stompClient) {
                // Leave message එක server එකට යවනවා.
                stompClient.send("/app/chat.sendMessage", {}, 
                    JSON.stringify({sender: username, type: 'LEAVE'}));
                stompClient.disconnect();
                console.log("Disconnected");
            }
            resetUI();
            messagesDiv.innerHTML += `<div class="join-leave">ඔබ චැට් එකෙන් ඉවත් විය.</div>`;
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        function resetUI() {
            usernameInput.disabled = false;
            connectButton.disabled = false;
            disconnectButton.disabled = true;
            messageInput.disabled = true;
            sendButton.disabled = true;
            username = null;
        }
    </script>
</body>
</html>

දැන් ඔයාලට පුළුවන් Spring Boot application එක run කරලා, browser එකේ http://localhost:8080/ කියලා ගහලා Chat App එක බලන්න. ඔයාලට පුළුවන් මේ page එක tab දෙක තුනක open කරලා, විවිධ names වලින් connect වෙලා messages යවලා බලන්න. වැඩේ නියමෙටම සිද්ධ වෙනවා නේද?

පොඩි Tips & Tricks

ඔන්න දැන් අපි සරල Chat App එකක් හැදුවා. මේක තව දුරටත් දියුණු කරගන්න පුළුවන් විදිය ගැන පොඩි Tips ටිකක් දෙන්නම්:

  • Security: මේ වෙලාවේ අපි setAllowedOrigins("*") දාලා තියෙන්නේ development වලට පහසුවට. ඒත් production වලදී මේක අනිවාර්යයෙන්ම ඔයාලගේ frontend domain එකට විතරක් සීමා කරන්න ඕන. වගේම, user authentication, authorization වගේ දේවල් implement කරන්න ඕන. STOMP headers වලට JWT token එකක් වගේ දෙයක් දාලා security add කරන්න පුළුවන්.
  • Error Handling: Connection lost, message send fail වුණොත් වගේ අවස්ථාවලදී error handling add කරන්න. Frontend එකේදී onError function එකට error message display කරන්න පුළුවන්.
  • User Presence: Online ඉන්න usersලා කවුද කියලා පෙන්නන්න පුළුවන්. මේකට server එකේ WebSocket sessions maintain කරලා, user join/leave වුණාම list එක update කරන්න පුළුවන්.
  • Message Persistence: Chat history එක database එකක save කරලා, අලුතෙන් connect වෙන user කෙනෙක්ට කලින් messages load කරන්න පුළුවන්.
  • Scalability: Chat app එකට user ගොඩක් එනවා නම්, Spring Boot application instance එකක් විතරක් මදි වෙන්න පුළුවන්. එතකොට external message brokers (e.g., RabbitMQ, Kafka) වගේ දේවල් use කරන්න පුළුවන්. Spring Boot එකට මේවා integrate කරන්න පුළුවන්.
  • Different Message Types: අපි දැම්මේ CHAT, JOIN, LEAVE types. Typing indicators, read receipts, file sharing වගේ දේවල් වලටත් තව message types add කරන්න පුළුවන්.

ඉස්සරහට මොනවද කරන්න පුළුවන්?

මේ Chat App එක ඔයාලට හොඳ base එකක් වෙයි Real-time applications ගැන තව දුරටත් explore කරන්න. ඔයාලට පුළුවන් මේක තව දුරටත් දියුණු කරන්න:

  • Group Chats හදන්න.
  • Private messaging implement කරන්න.
  • Usersලාට profile pictures දාන්න.
  • Emojis, file uploads වගේ දේවල් add කරන්න.
  • Mobile app එකක් හදලා මේ backend එකට connect කරන්න.

මේ හැමදේටම WebSocket, Spring Boot එකෙන් හදපු backend එක හොඳටම ගැළපෙනවා.

අවසාන වශයෙන්

ඉතින්, අද අපි Spring Boot සහ WebSocket පාවිච්චි කරලා කොහොමද සරල Real-time Chat Application එකක් හදන්නේ කියලා බැලුවා. මේක ඔයාලගේ programming journey එකේදී ගොඩක් වටිනා skill එකක් වෙයි කියලා මම විශ්වාස කරනවා. Real-time features කියන්නේ අද කාලේ applications වලට අත්‍යවශ්‍ය දෙයක් බවට පත් වෙලා තියෙනවා.

මතක තියාගන්න, coding කියන්නේ කරලාම ඉගෙන ගන්න ඕන දෙයක්. මේ project එකේ code එක ඔයාලගේම IDE එකක type කරලා, run කරලා, වෙනස්කම් කරලා බලන්න. එතකොට තමයි හරියටම තේරෙන්නේ. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, පහළින් comment එකක් දාගෙන යන්න. මම පුළුවන් විදියට උදව් කරන්නම්. Happy coding!