기본적인 WebSocket은 클라이언트와 서버 간의 양방향 통신을 가능하게 하지만, 메시지를 어떻게 구성하고 처리할지에 대한 표준이 없습니다.

이를 해결하기 위해 WebSocket 위에서 동작하는 STOMP을 사용하면, 일정한 규격을 갖춘 메시지를 주고받을 수 있으며, 메시지 브로커를 활용한 Pub/Sub 구조를 쉽게 구현할 수 있습니다. (Pub/Sub 구조MQTT에서도 사용하고 있으며, 해당 글에 관련 내용을 자세히 정리해놨습니다)

필요 라이브러리

// build.gradle
...
dependencies {  
    ...
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
}
...

구현

1. WebSocketMessageBrokerConfigurer 구현

WebSocketMessageBrokerConfigurer 페이지 참고

2. Controller 구현

@MessageMapping과 @SendTo를 사용하여 클라이언트가 보낸 메시지를 특정 경로에서 처리하고, 다른 클라이언트들에게 전달할 수 있도록 브로커로 전송할 수 있습니다.

2-1. @SendTo를 사용하여 전달

@Controller  
@RequiredArgsConstructor
public class ChatController {  
	
    private final MessageService messageService;  
	
    // /app/chat.sendMessage 로 들어오는 메시지를 처리  
    @MessageMapping("/chat.sendMessage")
    // /topic/public을 구독하는 클라이언트에게 전달
    @SendTo("/topic/public")  
    public ChatMessage sendMessage(ChatMessage chatMessage) {  
        return chatMessage;  
    }
}

2-2. SimpMessagingTemplate를 사용하여 전달

@Controller  
@RequiredArgsConstructor
public class ChatController {  
	
    private final MessageService messageService; 
    // 직접 브로커에 메시지를 전달할 수 있는 기능을 갖고 있음
    private final SimpMessagingTemplate messagingTemplate; 
	
    // /app/chat.sendMessage 로 들어오는 메시지를 처리  
    @MessageMapping("/chat.sendMessage")
    public void sendMessage(ChatMessage chatMessage) {  
	    // /topic/public을 구독하는 클라이언트에게 전달
        messagingTemplate.convertAndSend("/topic/public", chatMessage);  
    }
}

클라이언트에서 사용

1. WebSocket 연결

const socket = new SockJS('http://localhost:8083/ws');
const stompClient = Stomp.over(socket);

2. 연결 및 구독

/topic/public을 구독하며, 해당 브로커에 메시지가 온다면 처리할 로직을 구현할 수 있습니다.

stompClient.connect({}, function(frame) {
	
	const subscription = stompClient.subscribe('/topic/public', function(message) {
		
		const data = JSON.parse(message.body);
		displayMessage(data);
	});
});

3. WebSocket에 데이터 보내기

document.getElementById('chat-form').addEventListener('submit', (e) => {
 
	e.preventDefault();
	
	const input = document.getElementById('chat-input');
	const message = input.value;
	const chatMessage = {
		type: 'CHAT',
		roomId: roomId,
		sender: username,
		content: message
	};
	// 클라이언트에서 사용하는 경로에 send()를 통해 메시지를 전달할 수 있다
	stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
});