[SpringBoot] STOMP chatting 서버 구축

HandMK·2024년 9월 13일
0

SpringBoot

목록 보기
6/6
post-thumbnail
post-custom-banner

💻 버전 관리
Spring Boot : 3.2.1
JDK : 17
Build : Maven

📌 요약

채팅 기능을 구현하면서 공부한 내용을 정리한 내용입니다.

🤔 본론

채팅 기능을 구현하려면 데이터가 실시간으로 통신이 가능해야 한다.
물론 client 에서 서버에 API 호출을 여러번 하면 되겠지만, 서버의 부하가 커질 것 이다. WebSocket 프로토콜을 써야 하는 이유에 대해선 예전에 정리 한 적이 있으니 생략 하겠다.

⛓️‍💥 WebSocket 프로토콜을 써야 하는 이유

사실 WebSocket 만으로도 메세징 방식만 잘 정의한다면 채팅기능을 구현할 수 있다.
하지만 왜 WebSocket 의 상위 호환인 STOMP 를 사용할까?

이유는 여러가지가 있겠지만, 둘 다 사용해본 학부생 🥔가 생각하기엔 메세지 유형을 정의 할 수 있다 없다의 차이가 큰 것 같다.

이를 이해하기 위해선 먼저 STOMP 통신 구조를 알아야 한다.
STOMP는 하위 수준 WebSocket 위에서 작동하는 서브 프로토콜이며,
메세지 전송을 효율적을 하기 위해 탄생한 프로토콜이다.

기본적으로 WebSocket 과 동일하게, 서버와 클라이언트 연결은 HTTP 로 연결한다. 서버-클라이언트 연결 후 데이터를 전송하는 엔드포인트를 지정 하기 위해 해야 하는 과정이 2가지가 있다.

  1. 내가 보내고자 하는 채팅방(데이터를 보내고 싶은 경로) 구독(sub)
  2. 메세지 유형 선택 후 전송(pub)

이 2가지 과정 중 서버는 중간에 있고 메세지 전송으로써 브로커 역할을 하게 된다.

다음 그림을 보면 좀 더 이해가 쉬울 것 같다.

  1. Socket 연결 후 연결하고자 하는 채팅방 번호(또는 이름) 구독
  2. 메세지 유형 선택(ex. SEND, BROD...) 후 메세지 전송
  3. 서버에서 해당 채팅방으로 엔드포인트 지정하여 메세지 전송
    a. 여기서 B(채팅 대상자) 는 채팅방(1번방) 을 구독하고 있을 때 실시간 수신이 가능하다.
  4. Socket 연결 끊기

구조 자체는 간단하다. 이 구조를 이제 코드로써 구현해보자.

💻 코드 구현

Spring STOMP 공식문서
참고한 공식문서이다.

dependency 추가

Websocket

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocketConfig.java

먼저 WebSocket 및 STOMP 메시징을 활성화 해야한다.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/sub"); // 메세지 받을 때
        config.setApplicationDestinationPrefixes("/pub"); // 메세지 보낼때
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }
}
  1. @Configuration - Spring 구성 클래스임을 나타내기 위한 어노테이션
  2. @EnableWebSocketMessageBroker - 이름에서 직관적으로 알 수 있듯이 메시지 브로커가 지원하는 WebSocket 메시지 처리를 활성화하는 어노테이션이다.
  3. configureMessageBroker() - WebSocketMessageBrokerConfigurer 인터페이스를 구현하는 걸 확인 할 수 있는데, configureMessageBroker 메서드를 기본으로 사용한다.
    구독 시에는 /sub 접두사, 전송 시에는 /pub 접두사를 꼭 붙어야 한다는 뜻이다.
  4. registerStompEndpoints - Socket 연결 시의 엔드포인트다.
  5. setAllowedOriginPatterns() - CORS 설정, 테스트 중이라 모두 허용하였다.
  6. withSockJS() - 브라우저 별로 WebSocket을 지원하지 않을 경우가 있다. 이때, SockJS를 사용하여 대체 옵션을 지원한다.

    💡 로컬에서는 ws://localhost:8080/ws-stomp 로 연결 가능하다.
    배포 시에는 ssl 적용 시 ws->wss로 변경된다.

MessageType.java

public enum MessageType {
    SEND,
    EXIT
}

메세지 타입을 enum 형식으로 상수 집합을 지정해두었다.

RequestChatContentsDto.java

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class RequestChatContentsDto {
    private MessageType type;
    private String contents;
}

STOMP 통신 시에 메세지는 dto 에 담아서 통신이 가능하다. 통신 시에 메세지 타입과 내용을 담아서 서버에 전송하게 되면 메세지 타입에 맞는 액션을 취하게 끔 로직을 짰다.

ChatController.java

@Controller
public class ChatController {
    private final SimpMessagingTemplate template;
    @Autowired
    public ChatController(SimpMessagingTemplate template) {
        this.template = template;
    }

    @MessageMapping("/test") // 메세지 전송
    public void sendMessage(RequestChatContentsDto message) throws Exception {
        Thread.sleep(1000); // simulated delay
        template.convertAndSend("/sub", message); // 구독한 채팅방으로 메세지 전송
    }
}
  1. SimpMessagingTemplate - SpringBoot 에서 제공하는 객체이다. 사용자에게 메시지를 보내는 방법을 제공한다. 공식문서처럼 @SendTo()를 사용해도 된다.
  2. 이때 WebSocketConfig 설정한 대로 구독은 /sub 로 해야 하고, 전송은 /pub/test 경로로 해야 한다.

테스트

테스트를 해봐야 하는데.. 일단 Postman 에선 STOMP 가 아직 지원이 안된다고 한다....🥲
그래서 찾았던 Apic!
몇달 전까지만 해도 사이드 프로젝트를 진행하면서 잘됐다..! 근데 서버가 갑자기 죽어버렸다..(2024.09월 기준)

Apic 웹뷰

그래서 그냥 간단한 바닐라 코드를 짜기로 했다.
chat.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/stompjs/lib/stomp.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client/dist/sockjs.min.js"></script>
    <style>
        #messages {
            border: 1px solid #ccc;
            padding: 10px;
            margin-top: 10px;
            height: 200px;
            overflow-y: scroll;
        }
    </style>
</head>
<body>
<input type="text" id="messageInput" placeholder="메시지를 입력하세요" />
<button id="sendButton">전송</button>

<div id="messages"></div>

<script>
    const socket = new SockJS('http://localhost:8080/ws-stomp'); // socket 연결 경로
    const stompClient = Stomp.over(socket);

    stompClient.connect({}, function (frame) {
        console.log('Connected: ' + frame);


        stompClient.subscribe('/sub', function (message) { // 구독 경로
            const receivedMessage = JSON.parse(message.body); // 메시지 파싱
            displayMessage(receivedMessage.contents); // 화면에 메시지 표시
        });

        // Send message on button click
        document.getElementById('sendButton').addEventListener('click', function () {
            const contents = document.getElementById('messageInput').value;
            const message = {
                type: "SEND", // 고정된 type
                contents: contents // 입력된 내용
            };
            stompClient.send("/pub/test", {}, JSON.stringify(message)); // 메세지 전송 경로
            document.getElementById('messageInput').value = ''; // 입력 필드 초기화
        });
    });

    // 화면에 메시지를 추가하는 함수
    function displayMessage(message) {
        const messagesDiv = document.getElementById('messages');
        const messageElement = document.createElement('div');
        messageElement.textContent = message; // 메시지 내용 설정
        messagesDiv.appendChild(messageElement); // 메시지 추가
        messagesDiv.scrollTop = messagesDiv.scrollHeight; // 스크롤을 맨 아래로
    }
</script>
</body>
</html>

정상적으로 잘 작동한다.

📌 마무리

직접 써보니 활용도가 굉장히 높은 통신이라고 생각된다.
실제 프로젝트에 적용하려면 생각해야 하는 부분이 많은 것 같다.(채팅 내역 저장, 조회...) 빨리 만들어 보고 싶다.

소스코드

profile
몫을 다하는 개발자
post-custom-banner

0개의 댓글