Send message from server to client use Websocket. Java

237 Views Asked by At

I hope for your help, I have already looked through everything but have not found an answer. The task is this: I need to send information from my Java application to the client (frontend). The essence of the information is that the user has a password expiration date in the system and I need to send a notification to the user that his password is about to expire. Of course, to do this, I need to make a request to the database for a specific user, calculate how many days he still has left before the password expires. I want to use Websocket for this. I made a config class for training videos and other servers and controllers, but I still didn’t understand how my application should send information to the client. Help! I already thought of using the @Sheduled annotation, but a method marked with this annotation cannot accept method arguments, then I will not be able to execute a timer request for any user. Here is my code, please tell me!

WebSocketConfig class

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(final MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/ws");
    }

    @Override
    public void registerStompEndpoints(final StompEndpointRegistry registry) {
        registry.addEndpoint("/our-websocket")
                .setHandshakeHandler(new UserHandshakeHandler())
                .withSockJS();
    }
}

UserHandshakeHandler class

public class UserHandshakeHandler extends DefaultHandshakeHandler {
    private final Logger LOG = LoggerFactory.getLogger(UserHandshakeHandler.class);

    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {

        final String randomId = UUID.randomUUID().toString();
        LOG.info("User with ID '{}' opened the page", randomId);

        return new UserPrincipal(randomId);
    }
}

WSService class

@Service
public class WSService {

    private final SimpMessagingTemplate messagingTemplate;
    private final NotificationService notificationService;

    @Autowired
    public WSService(SimpMessagingTemplate messagingTemplate, NotificationService notificationService) {
        this.messagingTemplate = messagingTemplate;
        this.notificationService = notificationService;
    }

    public void notifyFrontend(final String message) {
        ResponseMessage response = new ResponseMessage(message);
        notificationService.sendGlobalNotification();

        messagingTemplate.convertAndSend("/topic/messages", response);
    }

    public void notifyUser(final String id, final String message) {
        ResponseMessage response = new ResponseMessage(message);

        notificationService.sendPrivateNotification(id);
        messagingTemplate.convertAndSendToUser(id, "/topic/private-messages", response);
    }
}

WSController class

@RestController
public class WSController {

    @Autowired
    private WSService service;

    @Autowired
    private UsersRepository usersRepository;

    @PostMapping("/send-message")
    public void sendMessage(@RequestBody final Message message) {
        service.notifyFrontend(message.getMessageContent());
    }

    @PostMapping("/send-private-message/{id}")
    public void sendPrivateMessage(@PathVariable final String id,
                                   @RequestBody final Message message) {
            service.notifyUser(id, message.getMessageContent());
    }

    @Scheduled(fixedDelay = 10000)
    public void sendPrivateMessage() {
        List<Users> list = this.usersRepository.findAll();
        for (Users users: list){
            service.notifyUser(users.getId().toString(), "testString");
        }
    }
}

index.html code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello WS</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/scripts.js"></script>
</head>
<body>
    <div class="container" style="margin-top: 50px">
        <div class="row">
            <div class="col-md-12">
                <form class="form-inline">
                    <div class="form-group">
                        <label for="message">Message</label>
                        <input type="text" id="message" class="form-control" placeholder="Enter your message here...">
                    </div>
                    <button id="send" class="btn btn-default" type="button">Send</button>
                </form>
            </div>
        </div>
        <div class="row" style="margin-top: 10px">
            <div class="col-md-12">
                <form class="form-inline">
                    <div class="form-group">
                        <label for="private-message">Private Message</label>
                        <input type="text" id="private-message" class="form-control" placeholder="Enter your message here...">
                    </div>
                    <button id="send-private" class="btn btn-default" type="button">Send Private Message</button>
                </form>
            </div>
        </div>
        <div class="row">
            <div class="col-md-12">
                <table id="message-history" class="table table-striped">
                    <thead>
                    <tr>
                        <th>Messages
                            <span
                                    id="notifications"
                                    style="
                                    color: white;
                                    background-color: red;
                                    padding-left: 15px;
                                    padding-right: 15px;">
                            </span>
                        </th>
                    </tr>
                    </thead>
                    <tbody id="messages">
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

script.js code

var stompClient = null;
var notificationCount = 0;

$(document).ready(function() {
    console.log("Index page is ready");
    connect();

    $("#send").click(function() {
        sendMessage();
    });

    $("#send-private").click(function() {
        sendPrivateMessage();
    });

    $("#notifications").click(function() {
        resetNotificationCount();
    });
});

function connect() {
    var socket = new SockJS('/our-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        console.log('Connected: ' + frame);
        updateNotificationDisplay();
        stompClient.subscribe('/topic/messages', function (message) {
            showMessage(JSON.parse(message.body).content);
        });

        stompClient.subscribe('/user/topic/private-messages', function (message) {
            showMessage(JSON.parse(message.body).content);
        });

        stompClient.subscribe('/topic/global-notifications', function (message) {
            notificationCount = notificationCount + 1;
            updateNotificationDisplay();
        });

        stompClient.subscribe('/user/topic/private-notifications', function (message) {
            notificationCount = notificationCount + 1;
            updateNotificationDisplay();
        });
    });
}

function showMessage(message) {
    $("#messages").append("<tr><td>" + message + "</td></tr>");
}

function sendMessage() {
    console.log("sending message");
    stompClient.send("/ws/message", {}, JSON.stringify({'messageContent': $("#message").val()}));
}

function sendPrivateMessage() {
    console.log("sending private message");
    stompClient.send("/ws/private-message", {}, JSON.stringify({'messageContent': $("#private-message").val()}));
}

function updateNotificationDisplay() {
    if (notificationCount == 0) {
        $('#notifications').hide();
    } else {
        $('#notifications').show();
        $('#notifications').text(notificationCount);
    }
}

function resetNotificationCount() {
    notificationCount = 0;
    updateNotificationDisplay();
}
1

There are 1 best solutions below

2
On

If you want to use websocket, the users you send notifications to must be using the application at that moment. That is, if the user to whom you send a notification is not logged in to the system, they cannot receive the message. Therefore, it would be more logical to use a mail server instead of websocket.

If you still want to use websocket, please share the client(frontend) code as well. Before you can send information to the client, the client must connect to websocket. After the connection is successfully established, you must subscribe to the channel.

First of all, the WebSocketConfig class needs to be edited as follows.

    @Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
    registry.addEndpoint("/our-websocket")
            .setAllowedOrigins("*") // or your domain.com
            .setHandshakeHandler(new UserHandshakeHandler())
            .withSockJS();
}

Then, you should correct the UseHandshakeHandler class. You must access and use the information of the requesting user with the following method.

    @Override
protected Principal determineUser(
        ServerHttpRequest request,
        WebSocketHandler wsHandler,
        Map<String, Object> attributes
) {

    // The user's information can be accessed with the token in the request object.
    /*
    String token = request.getHeader("Authorization");
    UserDetailsImpl user = Jwts.parserBuilder()......
    String id = user.getId;
    return new UserPrincipal(id);
    */
    
    // for testing
    String id = UUID.randomUUID().toString(); //You should use this id when sending private messages to the user.
    System.out.println(id);
    return new UserPrincipal(id);
}

I added an example of html and js code below. you can use this for testing.

src/main/resources/static/app.js:

var stompClient = null;

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    var socket = new SockJS('http://localhost:8080/our-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/user/topic/private-messages', function (greeting) {
        });
    });

    socket.addEventListener("message", (event) => {
        showGreeting(event.data);
    });

}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}



$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
});

src/main/resources/static/index.html:

    <!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/4.5.0/cerulean/bootstrap.min.css" rel="stylesheet">

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">

    <br>
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

Now, run the application and open the index.html file. Press the connect button and send a request to the user-name value shown on the screen from postman. You will see that the message reaches the user.

Test pictures.

connect to websocket

send message to user by id

show message