💻 버전 관리
Spring Boot : 3.2.1
JDK : 17
Build : Maven
채팅 기능을 구현하면서 공부한 내용을 정리한 내용입니다.
채팅 기능을 구현하려면 데이터가 실시간으로 통신이 가능해야 한다.
물론 client 에서 서버에 API 호출을 여러번 하면 되겠지만, 서버의 부하가 커질 것 이다. WebSocket
프로토콜을 써야 하는 이유에 대해선 예전에 정리 한 적이 있으니 생략 하겠다.
사실 WebSocket
만으로도 메세징 방식만 잘 정의한다면 채팅기능을 구현할 수 있다.
하지만 왜 WebSocket
의 상위 호환인 STOMP
를 사용할까?
이유는 여러가지가 있겠지만, 둘 다 사용해본 학부생 🥔가 생각하기엔 메세지 유형을 정의 할 수 있다 없다의 차이가 큰 것 같다.
이를 이해하기 위해선 먼저 STOMP 통신 구조를 알아야 한다.
STOMP
는 하위 수준 WebSocket
위에서 작동하는 서브 프로토콜이며,
메세지 전송을 효율적을 하기 위해 탄생한 프로토콜이다.
기본적으로 WebSocket
과 동일하게, 서버와 클라이언트 연결은 HTTP
로 연결한다. 서버-클라이언트 연결 후 데이터를 전송하는 엔드포인트
를 지정 하기 위해 해야 하는 과정이 2가지가 있다.
이 2가지 과정 중 서버는 중간에 있고 메세지 전송으로써 브로커 역할을 하게 된다.
다음 그림을 보면 좀 더 이해가 쉬울 것 같다.
구조 자체는 간단하다. 이 구조를 이제 코드로써 구현해보자.
Spring STOMP 공식문서
참고한 공식문서이다.
Websocket
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
먼저 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();
}
}
@Configuration
- Spring 구성 클래스임을 나타내기 위한 어노테이션@EnableWebSocketMessageBroker
- 이름에서 직관적으로 알 수 있듯이 메시지 브로커가 지원하는 WebSocket 메시지 처리를 활성화하는 어노테이션이다.configureMessageBroker()
- WebSocketMessageBrokerConfigurer
인터페이스를 구현하는 걸 확인 할 수 있는데, configureMessageBroker
메서드를 기본으로 사용한다./sub
접두사, 전송 시에는 /pub
접두사를 꼭 붙어야 한다는 뜻이다.registerStompEndpoints
- Socket 연결 시의 엔드포인트다.setAllowedOriginPatterns()
- CORS 설정, 테스트 중이라 모두 허용하였다.withSockJS()
- 브라우저 별로 WebSocket을 지원하지 않을 경우가 있다. 이때, SockJS를 사용하여 대체 옵션을 지원한다.💡 로컬에서는 ws://localhost:8080/ws-stomp 로 연결 가능하다.
배포 시에는 ssl 적용 시 ws->wss로 변경된다.
public enum MessageType {
SEND,
EXIT
}
메세지 타입을 enum 형식으로 상수 집합을 지정해두었다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class RequestChatContentsDto {
private MessageType type;
private String contents;
}
STOMP 통신 시에 메세지는 dto 에 담아서 통신이 가능하다. 통신 시에 메세지 타입과 내용을 담아서 서버에 전송하게 되면 메세지 타입에 맞는 액션을 취하게 끔 로직을 짰다.
@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); // 구독한 채팅방으로 메세지 전송
}
}
SimpMessagingTemplate
- SpringBoot 에서 제공하는 객체이다. 사용자에게 메시지를 보내는 방법을 제공한다. 공식문서처럼 @SendTo()를 사용해도 된다.WebSocketConfig
설정한 대로 구독은 /sub 로 해야 하고, 전송은 /pub/test 경로로 해야 한다.테스트를 해봐야 하는데.. 일단 Postman 에선 STOMP 가 아직 지원이 안된다고 한다....🥲
그래서 찾았던 Apic!
몇달 전까지만 해도 사이드 프로젝트를 진행하면서 잘됐다..! 근데 서버가 갑자기 죽어버렸다..(2024.09월 기준)
그래서 그냥 간단한 바닐라 코드를 짜기로 했다.
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>
정상적으로 잘 작동한다.
직접 써보니 활용도가 굉장히 높은 통신이라고 생각된다.
실제 프로젝트에 적용하려면 생각해야 하는 부분이 많은 것 같다.(채팅 내역 저장, 조회...) 빨리 만들어 보고 싶다.