Chat w Spring Boot


Chat w Spring Boot z użyciem WebSocket

W tym wpisie zrealizujemy aplikację webową typu Chat z użyciem Spring Boota. Zanim jednak przejdziemy do realizacji zadania należy wyjaśnić kilka pojęć:

Socket to reprezentacja jednego z końców dwustronnej komunikacji pomiędzy programami pracującymi w sieci. Zwykle komunikacja za pomocą gniazd implementowana jest na bazie protokołu TCP lub protokołu UDP. Unikalne gniazdo oznacza się adresem IP i numerem portu.

Pakiet java.net udostępnia dwie klasy oparte o mechanizm socketów (Gniazd):

  • Socket – reprezentującą połączenie po stronie klienta,
  • ServerSocket – reprezentującą połączenie po stronie serwera.

Prosty przykład architektury Client-Server z użyciem gniazd:

Klasa Server:

public class Server {

    public static void main(String[] args) {

        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket(6666);
        }
        catch(IOException e){
            System.out.println("Could not listen on port: 6666");
            System.exit(-1);
        }

        Socket clientSocket = null;

        try{
            clientSocket = serverSocket.accept();
        }catch(IOException e){
            System.out.println("Accept failed: 6666");
            System.exit(-1);
        }

        PrintWriter out = null;
        try {
            out = new PrintWriter(clientSocket.getOutputStream(), true);

            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            out.println(in.readLine() + "[INFO]");

            out.close();
            in.close();
            clientSocket.close();
            serverSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Klasa Client:

public class Client {

    public static void main(String[] args) throws IOException {

        Socket echoSocket = null;
        PrintWriter out   = null;
        BufferedReader in = null;

        try {
            echoSocket = new Socket("localhost", 6666);
            out        = new PrintWriter(echoSocket.getOutputStream(), true);
            in         = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));
        } catch(UnknownHostException e){
            System.err.println("UnknownHostException");
            System.exit(1);
        } catch(IOException e){
            System.err.println("IOException");
            System.exit(1);
        }

        BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));

        System.out.println("Type a message: ");
        out.println(stdIn.readLine());
        System.out.println(in.readLine());

        out.close();
        in.close();
        stdIn.close();
        echoSocket.close();
    }
}

Wynik:

Type a message: 
test message
test message[INFO]

WebSocket jest protokołem opartym o TCP, zapewniającym dwukierunkową (ang. full-duplex) komunikację pomiędzy klientem a serwerem. Po zestawieniu połączenia, obie strony mogą wymieniać się danymi w dowolnym momencie, wysyłając pakiet danych.  Strona która chce nawiązać połączenie, wysyła do serwera żądanie (ang. handshake). Zapytanie to informuje serwer WWW, że aplikacja chce nawiązać połączenie, wykorzystując protokół WebSocket. Po więcej zapraszam na stronę https://sekurak.pl/bezpieczenstwo-protokolu-websocket-w-praktyce/. Bardzo dobrze opisuje to zdanie:

WebSocket is a protocol that allows the server to send things to the browser without the browser having to request it.

STOMP – prosty tekstowy protokół (z ang. Simple (or Streaming) Text Oriented Message Protocol który wykorzystuje WebSocket .

W projekcie wykorzystamy dwie kluczowe zależności:

Wsparcie dla protokołu WebSocket:

spring-boot-starter-websocket

Wsparcie dla protokołu STOMP:

spring-boot-starter-integration

Jaka jest zależność między protokołem WebSocket a protokołem STOMP:

“you can make websocket connection without STOMP as the use of a subprotocol is not mandatory, you can deal with the raw websocket. When using a raw websocket, the message sent lacks of information to make Spring route it to a specific message handler method (we don’t have any messaging protocol), so instead of annotating your controller, you’ll have to implement a WebSocketHandler.”

Co oznacza, że nie trzeba używać protokołu STOMP do komunikacji z użyciem protokołu WebSocket. Jednak wtedy jesteśmy zobowiązani do implementacji interfejsu WebSocketHandler https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/WebSocketHandler.html

Do dzieła! Klasa modelu Message z użyciem biblioteki Lombok:

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Message {
    private String user;
    private String message;
}

Plik pom.xml – niezbędne zależności:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-integration</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-websocket</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.webjars</groupId>
		<artifactId>bootstrap</artifactId>
		<version>3.3.6</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.integration</groupId>
		<artifactId>spring-integration-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.10</version>
		<scope>provided</scope>
	</dependency>
</dependencies>

Klasa konfiguracyjna – WebSocketConfig :

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
 
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat");
    }
 
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }
}

Klasa kontrolera – ChatMessageController:

@Controller
public class ChatMessageController {
 
    @MessageMapping("/chat")
    @SendTo("/topic/messages")
    public Message getMessage(Message message) {
        return message;
    }
}

W folderze ./static zamieszczamy plik biblioteki stomp.js:

https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js

oraz plik Chat.js:

var client = null;

function showMessage(value, user) {

    var today    = new Date();
    var date     = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
    var time     = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
    var dateTime = date+' '+time;

    var newResponse = document.createElement('p');
    newResponse.appendChild(document.createTextNode("[" + dateTime + "] "));
    newResponse.appendChild(document.createTextNode(user));
    newResponse.appendChild(document.createTextNode(" : "));
    newResponse.appendChild(document.createTextNode(value));
    var respone = document.getElementById('reponse');
    respone.appendChild(newResponse);
}

function connect() {
    client = Stomp.client('ws://localhost:8080/chat');
    client.connect({}, function (frame) {
        client.subscribe("/topic/messages", function(message){
            showMessage(JSON.parse(message.body).message, JSON.parse(message.body).user)
        });
    })
}

function sendMessage() {
    var messageToSend = document.getElementById('messageToSend').value;
    var user          = document.getElementById('user').value;
    client.send("/app/chat", {}, JSON.stringify({'message': messageToSend, 'user': user}));
}

Jako, że korzystamy z systemu szablonów thymeleaf to w katalogu ./templates zamieszczamy plik szablonu index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JavaLeader.pl | development chat application</title>
   <link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
</head>
    <body onload="connect()">
        <div class="container">
            <div class="hero-unit">
                <div class="row-fluid">
                    <div class="offset4 span4">
                        <legend>JavaLeader.pl | development chat application</legend>
                            <div class="control-group">
                                <div class="controls">
                                    <label>User:</label>
                                    <input class="form-control" id="user" type="text"/><br>
                                </div>
                            </div>
                            <div class="control-group">
                                <label>You message:</label>
                                <input class="form-control" id="messageToSend" type="text"/><br>
                            </div>
                            <div class="control-group">
                                <div class="controls">
                                    <button class="btn btn-primary" onclick="sendMessage()">Send</button>
                                </div>
                            </div>
                    </div>
                </div>
            </div>
            <br>
            <div id="reponse"></div>
        </div>
        <script src="Chat.js"></script>
        <script src="Stomp.js"></script>
    </body>
</html>

Po uruchomieniu aplikacji wynik jest następujący:

http://localhost:8080/

Zdjęcie z logo wpisu pobrano ze strony: https://www.shutterstock.com/search/conversation+listener?section=1&image_type=vector&search_source=base_related_searchesroyalty-free images.

Kod źródłowy do wglądu na GitHub!

Jeśli chcesz uzyskać dostęp do GitHuba na 30 dni i pobrać kod źródłowy wyślij smsa o treśći DOSTEP.EDUSESSION na numer 7943. Tyle wiedzy a koszt to tylko 9 PLN (11.07 PLN z VAT).





Leave a comment

Your email address will not be published.


*