[SpringBoot] 채팅 기능에 Stomp 적용하기(+Veu) - 2

이의찬·2023년 7월 17일
1

Springboot

목록 보기
5/12

🐱Github
https://github.com/Eui9179/spring-boot-chat-example

중고 경매 사이트에 필요한 채팅기능을 구현하기 앞어서 먼저 웹소켓을 공부하기 위해 구현해 보았다.
이전에 간단한 Websocket을 이용하여 구현하였는데 Stomp를 적용시켜서 채팅 기능을 고도화해보자.

이전 게시물
WebSocket을 이용하여 채팅 기능 구현하기 - 1

Stomp

Stomp는 메시징 전송을 효율적으로 하기 위해 나온 프로토콜로 pub/sub구조로 되어있어서 메시지를 발송하면 구독(sub)하고 있는 세션에게 발행(pub)하는 방식이다. 또한 Stomp를 이용하면 통신 메시지의 헤더에 값을 세팅할 수 있어서 헤더 값을 기반으로 통신 처리도 가능한 것이 장점이다.

이를 채팅방에 대입해서 생각해보면

  1. 채팅방을 생성하고
  2. 채팅방에 참여한 유저들은 채팅방을 구독한다.
  3. 채팅방에서 메시지를 주고(pub) 받는다(sub).

원리

build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	implementation 'org.webjars:sockjs-client:1.1.2'
	implementation 'org.webjars:stomp-websocket:2.3.3-1'
	implementation 'org.webjars.bower:axios:0.17.1'
}

thymeleaf를 이용하여서 헤더를 import했는데 이 의존성은 지우고 기본 html로 적용 가능하다.

webjars는 웹 화면 구성을 위한 라이브러리이다.

폴더 구조

v1에 사용하였던 메시지를 세션에게 직접 보내는 기능을 하는 WebSocketChatHandler는 Stomp를 사용하여 sub/pub으로 동작하기 때문에 없어도 된다. 이외에도 테스트를 Simple WebSocket Client를 사용하여 테스트 하였기 때문에 필요없었던 ChatRoomController를 추가하였다.

WebSocketConfig 파일 수정

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub");
        registry.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp")
                .withSockJS();
    }
}

@EnableWebSocketMessageBroker는 웹 소켓 서버를 사용하겠다는 설정이다. 또한 WebSocketMessageBrokerConfigurer를 통해 웹 소켓에 관련된 몇 가지 기능을 구현할 수 있다.

registerStompEndPoints()를 이용하여 클라이언트에서 웹소켓에 접속하는 endpoint를 등록한다.

javascript코드를 살짝 확인해보면 아래와 같이 /ws-stomp라는 경로로 소켓에 접속한다.

다음으로 configureMessageBroker()는 클라이언트에서 다른 클라이언트로 메시지를 라우팅할 때 /sub이라는 경로로 보낸다. /pub으로 시작되는 주제를 가진 메시지 핸들러로 라우팅하여 해당 주제를 구독하고 있는 모든 클라이언트에게 메시지를 발송한다.

마찬가지로 javascript 코드를 확인하면 ws.send(/pub/chat/message)라는 경로로 메시지를 보낸다. roomId기준으로 /sub/chat/room/{roomId}를 구독하고 있는 사용자에게 메시지를 전송하는 것이다.

ChatRoom DTO 수정

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.UUID;

@Slf4j
@Getter
@AllArgsConstructor
@Builder
public class ChatRoom {
    private String roomId;
    private String name;

    public static ChatRoom of(String name) {
        return ChatRoom.builder()
                .name(name)
                .roomId(UUID.randomUUID().toString())
                .build();
    }
}

Stomp를 사용하여 pub/sub 방식으로 구현되기 때문에 기존에 직접 메시지를 보내던 기능을 없앨 수 있다.

ChatController 수정

import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;
import site.leui.chat_example.chat.dto.ChatMessage;

@RequiredArgsConstructor
@Controller
public class ChatController {

    private final SimpMessageSendingOperations messagingTemplate;

    @MessageMapping("/chat/message")
    public void sendMessage(ChatMessage message) {
        if (isJoin(message))
            message.setMessage(message.getSender() + "님이 입장하였습니다");

        messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
    }

    private boolean isJoin(ChatMessage messageType) {
        return messageType.getMessageType().equals(ChatMessage.MessageType.JOIN);
    }
}

@MessageMapping을 통해 웹 소켓으로 들어오는 메시지 발행을 처리한다.
위에서 설정하였던 prefix인 /pub + /chat/message로 발행 요청을 하면 Controller가 해당 메시지를 받아서 처리하게 된다. messagingTemplate.convertAndSend()함수를 통해 /sub/chat/room/ + roomId를 구독하고 있는 유저에게 메시지를 발행한다.(보낸다)

UI 및 소켓 연결

방을 다시 들어갈 때마다 socket 객체를 만들어 주고 구독을 해야한다.

ws.connect()를 확인해보면 먼저 roomId를 기준으로 구독을 한다. 그 후 ~~님이 입장하셨습니다 메시지를 모든 구독자에게 보낸다. 만약 연결이 끊기거나 에러가 발생하면 최대 6번 연결을 한다.

대화 내용 전송 함수와 메시지를 받았을 때 처리하는 코드이다.

실제 화면

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

너무 좋은 글이네요. 공유해주셔서 감사합니다.

답글 달기