Ứng dụng Javalin và Web Socket để tạo ứng dụng Chat đơn giản.

1. Giới thiệu

The Code Root đã giới thiệu đến với các bạn về Framework Javalin và các bài hướng dẫn ứng dụng. Hôm nay xin tiếp tục gửi đến các bạn một ứng dụng nữa trên nền tảng Framework Javalin: một ứng dụng Chat đơn giản.

Chắc hẳn đối với nhiều bạn công nghệ Web Socket cũng không phải là lạ lẫm gì. Tuy nhiên với Framework Javalin, các cấu hình với Web Socket trở nên đơn gỉản bẳng các cú pháp tích hợp của Javalin dành cho Web Socket.

 

2. Hướng dẫn code

Thư viện Maven

<dependencies>
    <dependency>
        <groupId>io.javalin</groupId>
        <artifactId>javalin</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <version>20160810</version>
    </dependency>
    <dependency>
        <groupId>com.j2html</groupId>
        <artifactId>j2html</artifactId>
        <version>1.2.0</version>
    </dependency>
</dependencies>

 

Backend Server 
public class Chat {

    private static Map<WsSession, String> userUsernameMap = new ConcurrentHashMap<>();
    private static int nextUserNumber = 1; // Assign to username for next connecting user

    public static void main(String[] args) {
        Javalin.create()
            .enableStaticFiles("/public")
            .ws("/chat", ws -> {
                ws.onConnect(session -> {
                    String username = "User" + nextUserNumber++;
                    userUsernameMap.put(session, username);
                    broadcastMessage("Server", (username + " joined the chat"));
                });
                ws.onClose((session, status, message) -> {
                    String username = userUsernameMap.get(session);
                    userUsernameMap.remove(session);
                    broadcastMessage("Server", (username + " left the chat"));
                });
                ws.onMessage((session, message) -> {
                    broadcastMessage(userUsernameMap.get(session), message);
                });
            })
            .start(7070);
    }

    // Sends a message from one user to all users, along with a list of current usernames
    private static void broadcastMessage(String sender, String message) {
        userUsernameMap.keySet().stream().filter(Session::isOpen).forEach(session -> {
            session.send(
                new JSONObject()
                    .put("userMessage", createHtmlMessageFromSender(sender, message))
                    .put("userlist", userUsernameMap.values()).toString()
        });
    }

    // Builds a HTML element with a sender-name, a message, and a timestamp
    private static String createHtmlMessageFromSender(String sender, String message) {
        return article(
            b(sender + " says:"),
            span(attrs(".timestamp"), new SimpleDateFormat("HH:mm:ss").format(new Date())),
            p(message)
        ).render();
    }

}

Toàn bộ xử lý Backedn cho ứng dụng Chat của chúng ta hoàn toàn gói gọn trong class này. 

  • Tạo 1 một Collection Map chứa các session tương ứng với username
  • Tự động tăng số đếm của user lên sau mỗi session
  • Dùng web socket để handle các connection từ Server đến Browser Client.
    .ws("/chat", ws -> {
                    ws.onConnect(session -> {
                        String username = "User" + nextUserNumber++;
                        userUsernameMap.put(session, username);
                        broadcastMessage("Server", (username + " joined the chat"));
                    });
                    ws.onClose((session, status, message) -> {
                        String username = userUsernameMap.get(session);
                        userUsernameMap.remove(session);
                        broadcastMessage("Server", (username + " left the chat"));
                    });
                    ws.onMessage((session, message) -> {
                        broadcastMessage(userUsernameMap.get(session), message);
                    });
                })

    Chúng ta sẽ dùng cú pháp .ws của Javalin để tạo ra 1 đường dẫn xử lý cho Web Socket. Mỗi path Web Socket sẽ có 3 state sau: 

    • On connect: khi Server và Client vừa hoàn thành connection

    • On message: xử lý khi Client gửi message đến Server.

    • On close: xử lý khi Client kết thúc connection.

  • broadcastMessage: Chức năng broadcast message đến tất cả user trong toàn bộ session.

 

Front -end HTML

<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>WebsSockets</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="chatControls">
        <input id="message" placeholder="Type your message">
        <button id="send">Send</button>
    </div>
    <ul id="userlist"> <!-- Built by JS --> </ul>
    <div id="chat">    <!-- Built by JS --> </div>
    <script src="websocketDemo.js"></script>
</body>
</html>

File HTML trên dùng để tạo giao diện hiển thị các đoạn chat và khung để nhập đoạn chat.

 

websocketDemo.js

// small helper function for selecting element by id
let id = id => document.getElementById(id);

//Establish the WebSocket connection and set up event handlers
let ws = new WebSocket("ws://" + location.hostname + ":" + location.port + "/chat");
ws.onmessage = msg => updateChat(msg);
ws.onclose = () => alert("WebSocket connection closed");

// Add event listeners to button and input field
id("send").addEventListener("click", () => sendAndClear(id("message").value));
id("message").addEventListener("keypress", function (e) {
    if (e.keyCode === 13) { // Send message if enter is pressed in input field
        sendAndClear(e.target.value);
    }
});

function sendAndClear(message) {
    if (message !== "") {
        ws.send(message);
        id("message").value = "";
    }
}

function updateChat(msg) { // Update chat-panel and list of connected users
    let data = JSON.parse(msg.data);
    id("chat").insertAdjacentHTML("afterbegin", data.userMessage);
    id("userlist").innerHTML = data.userlist.map(user => "<li>" + user + "</li>").join("");
}

File JS handle các chức năng của web socket gửi message từ Client đến Server.

 

3. Testing

Như đã giới thiệu ở trên ứng dụng Chat của chúng ta sử dụng mỗi session như một user khác nhau. Do đó chúng ta sẽ giả lập bằng cửa sổ ẩn danh của trình duyệt và 2 trình duyệt khác nhau để giả lập có nhiều user đang chat cùng lúc trong Hệ Thống.

User đầu tiên:

User tiếp theo bằng cửa sổ ẩn danh:

 

Message từ một user được broadcast đến toàn bộ user:

From User 2

 

From User 1

Các bạn có thể download source code tại đây

Chúc các bạn thành công.

AutoCode.VN

minhnhatict@gmail.com Javalin