[Spring][Websocket] Simple send notifications

Overview Trong bài viết sẽ tạo mộ ứng dụng web thực thi gửi tin nhắn sử dụng tính năng Websocket với Spring Framework 5.0. Websocket là kết nối 2 chiều, song công, liên tục giữa máy chủ và trình duyệt. Sau khi Websocket được thiết lập kết nối, kết nối vẫn mở cho đến khi

Overview

Trong bài viết sẽ tạo mộ ứng dụng web thực thi gửi tin nhắn sử dụng tính năng Websocket với Spring Framework 5.0. Websocket là kết nối 2 chiều, song công, liên tục giữa máy chủ và trình duyệt. Sau khi Websocket được thiết lập kết nối, kết nối vẫn mở cho đến khi client hoặc máy chủ quyết định đóng kết nối này.

Các case sử dụng có thể khi một ứng dụng liên quan nhiều user liên lạc với nhau, từ server đến client chẳng như notification, chat. Chúng ta sẽ build một ứng dụng đơn giản nhận notification sử dụng STOMP messaging với Spring để tạo tương tác qua web khi có một notification được tạo sẽ push message cho các client đã subscribed.

Coding

Dependencies

Trước tiên cần thêm các dependency vào build.gradle.kts như sau

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {id("org.springframework.boot") version "2.5.1"id("io.spring.dependency-management") version "1.0.11.RELEASE"kotlin("jvm") version "1.5.10"kotlin("plugin.spring") version "1.5.10"}

group ="dev.hlk"
version ="0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {mavenCentral()}

dependencies {implementation("org.springframework.boot:spring-boot-starter-web")implementation("com.fasterxml.jackson.module:jackson-module-kotlin")implementation("org.jetbrains.kotlin:kotlin-reflect")implementation("org.jetbrains.kotlin:kotlin-stdlib")// Swagger OpenAPIimplementation("org.springdoc:springdoc-openapi-ui:1.5.9")implementation("org.springdoc:springdoc-openapi-kotlin:1.5.9")// Websocketimplementation("org.springframework:spring-websocket:5.3.8")implementation("org.springframework:spring-messaging:5.3.8")}

tasks.withType<KotlinCompile>{
    kotlinOptions {
        freeCompilerArgs =listOf("-Xjsr305=strict")
        jvmTarget ="11"}}

Sau khi thêm cần phải build lại bằng command ./gradlew build hoặc qua nút

reload của IDEA Intellij

WebSocket Configuration

Tạo một file config cho Websocket:

// config/WebSocketConfig.kt@Configuration@EnableWebSocketMessageBrokerclass WebSocketConfig : WebSocketMessageBrokerConfigurer {overridefunconfigureMessageBroker(registry: MessageBrokerRegistry){
        registry.apply{enableSimpleBroker("/topic")}}overridefunregisterStompEndpoints(registry: StompEndpointRegistry){
        registry.apply{addEndpoint("/notification").withSockJS()}}}

WebSocketConfig được thêm annotation @Configuration để chỉ rằng nó là một configuration class, @EnableWebSocketMessageBroker bật tính năng xử lý Websocket message bởi một message broker.

Method configureMessageBroker() thực thi method mặc định trong WebSocketMessageBrokerConfigurer để config message broker. Nó bắt đầu gọi enableSimpleBroker() để bật một message broker đơn giản trong bộ nhớ chứa tin nhắn cho client với đích có prefix /topic.

Method registerStompEndpoints() đăng ký Websocket endpoint mở cho SockJS client kết nối qua /notification.

Web Controller

Tạo controller NotificationWebController trong /controller/web/NotificationWebController.kt dành cho view.

@Controllerclass NotificationWebController {@GetMapping("/notifications")funindex()=ModelAndView("notifications/index")}

API Controller

Tạo controller NotificationController trong /controller/api/NotificationController.kt dành cho việc push notification.

@RestControllerclassNotificationController(privateval notificationService: NotificationService
){@PostMapping("/pushNotification")funpushNotification(@RequestBody payload: NotificationDao): Boolean {return notificationService.send(payload)}}

Client

Với server-side đã tạo thì bên client cần có phần nhận notification từ server.

Để có thể dùng views cần phải config thêm trong application.yml

spring:mvc:view:prefix: /views/
     suffix: .html

Tạo một index.html trong resources/static/views/notifications/index.html.

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1, shrink-to-fit=no"><title>Websocket</title><!-- CSS --><linkrel="stylesheet"href="../../css/app.css"><linkrel="stylesheet"href="../../css/bootstrap/bootstrap.min.css"><linkrel="stylesheet"href="../../css/bootstrap/bootstrap-grid.min.css"><linkrel="stylesheet"href="../../css/bootstrap/bootstrap-reboot.min.css"><!-- JS --><scriptsrc="../../js/jquery.min.js"></script><scriptsrc="../../js/bootstrap/popper.min.js"></script><scriptsrc="../../js/bootstrap/bootstrap.bundle.min.js"></script><scriptsrc="../../js/bootstrap/bootstrap.min.js"></script><scriptsrc="../../js/sockjs.js"></script><scriptsrc="../../js/stomp.js"></script><scriptsrc="../../js/timeago.full.min.js"></script><scriptsrc="../../js/app.js"></script></head><body><h2>Notifications</h2><ulid="notifications"></ul><divclass="toast"id="js-toast"role="alert"aria-live="assertive"aria-atomic="true"><divclass="toast-header"><svgxmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"version="1.1"id="Layer_1"x="0px"y="0px"viewBox="0 0 512 512"style="enable-background:new 0 0 512 512;"xml:space="preserve"><pathstyle="fill:#FF8A1E;"d="M174.74,430.93c0,44.878,36.382,81.07,81.26,81.07l22.261-103.331L174.74,430.93z"/><pathstyle="fill:#FF562B;"d="M256,512c44.878,0,81.26-36.192,81.26-81.07L256,408.669V512L256,512z"/><polygonstyle="fill:#FFA418;"points="34.846,364.142 34.846,430.93 256,430.93 278.261,341.881 "/><pathstyle="fill:#FFBE11;"d="M256,0C176.159,0,111.837,59.674,99.965,136.479c-5.683,36.763-25.84,146.535-25.84,146.535  l204.137,22.261L256,0z"/><g><pathstyle="fill:#FF8A1E;"d="M437.877,283.016c0,0-20.158-109.774-25.842-146.537C400.162,59.674,335.841,0,256,0v305.277   L437.877,283.016z"/><polygonstyle="fill:#FF8A1E;"points="256,341.881 256,430.93 477.154,430.93 477.154,364.142  "/></g><pathstyle="fill:#FFD460;"d="M256,283.016H74.123c-23.905,18.925-39.277,48.417-39.277,81.126H256l22.261-40.563L256,283.016z"/><pathstyle="fill:#FFA418;"d="M437.877,283.016H256v81.126h221.154C477.154,331.433,461.781,301.941,437.877,283.016z"/><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg><strongclass="js-toast-title mr-auto"></strong><smallclass="js-toast-timestamp"></small><buttontype="button"class="ml-2 mb-1 close"data-dismiss="toast"aria-label="Close"><spanaria-hidden="true">×</span></button></div><divclass="toast-body js-toast-content"></div></div></body></html>

Tạo app.js trong resources/static/js/app.js

let stompClient =null;functionconnect(){let socket =newSockJS("/notification");
    stompClient = Stomp.over(socket);
    stompClient.connect({},function(frame){
        console.log('Connected: '+ frame);
        stompClient.subscribe("/topic/notifications",function(notification){showNotification(JSON.parse(notification.body));});});// Reconnect socket
    socket.onclose=function(e){
        console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);setTimeout(function(){connect();},1000);}}functiondisconnect(){if(stompClient !=null){
        stompClient.disconnect();}
    console.log("Disconnected");}functionshowNotification(notification){let elem = document.createElement("li");let data ="Title: "+ notification.title
        +" - Timestamp: "+ notification.timestamp
        +" - Content: "+ notification.content;
    elem.appendChild(document.createTextNode(data));$("#notifications").append(elem);$('#js-toast').on('show.bs.toast',function(){let that =$(this);let header = that.children(".toast-header");
        header.children(".js-toast-timestamp").text(timeago.format(Date.parse(notification.timestamp)))
        header.children(".js-toast-title").text(notification.title);
        that.children(".js-toast-content").text(notification.content);})$(".toast").toast("show");}$(function(){disconnect();connect();$(".toast").toast({
        delay:2000// ms});});// Disconnect on close tab
window.onbeforeunload=function(){disconnect();};
  • Phần chính trong đoạn JS là connect()showNotification()
  • connect() function sử dụng SockJSstomp.js mở kết nối đến /notifications, là endpoint SockJS server chờ các kết nối. Khi kết nối thành công, client sẽ subscribe đến đích /topic/notifications phần mà server sẽ publish. Khi nhận notification DOM sẽ chền vào list và có sử dụng Toasts của bootstrap để làm popup.
  • showNotification() function nhận nội dung message để hiển thị.
  • socket.onclose(function(){}) phụ vụ cho việc kết nối lại với server khi server fail/restart.

Demo

Trong demo này cho thấy các trình duyệt nhận notification sau khi thao tác qua swagger gọi 1 API endpoint /notifications và nội dụng gồm title, content, timestamp.

Cảm ơn các bạn đã đọc bài viết. 😉

References

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ