팀 프로젝트에서 JAVA의 STOMP를 통해 실시간 채팅 기능을 구현하였고
이 과정에서 사용한 STOMP의 개념과 활용에 대해 Spring 문서를 참고하여 정리해 보았다.
STOMP는 TCP 및 WebSocket과 같은 안정적인 양방향 스트리밍 네트워크 프로토콜을 통해 사용할 수 있다.
즉 WebSocket 위에서 동작하는 메시징 프로토콜로 추가적인 메시징 기능과 편리함을 제공한다.
다음은 STOMP 프레임의 구조이며 명령(command), 헤더(headers), 본문(body)으로 이루어져 있다.
STOMP의 Pub/Sub 구조와 메시지 브로커
STOMP는 메시지 브로커를 통해 pub/sub 구조로 메시지를 전송하고 수신하는 프로토콜이다.
이 구조는 분산 시스템에서 효율적이고 확장 가능한 메시징을 지원한다.
메시지 브로커는 메시지 발행자(publisher)와 메시지 구독자(subscriber) 사이에서 통신을 중재하는 역할을 한다.
- Publisher: 메시지를 생성하고 특정 주제(topic) 또는 큐(queue)에 메시지 발행
- Subscriber: 특정 주제 또는 큐를 구독(subscribe)하고, 해당 주제나 큐에 발행된 메시지 수신
- Broker: pub/sub 구조에서 중앙 허브 역할
Broker를 통해 Publisher가 발행한 메시지를 구독자에게 전달할 수 있다. 이로 인해 Publisher와 Subscriber는
서로 직접적으로 연관되지 않고 서로 영향을 주지 않기 때문에 Publisher와 Subscriber를 각각 쉽게 추가하고 제거할 수 있다.
STOMP Config 설정
STOMP 관련 설정을 위해 WebSocketMessageBrokerConfigurer을 구현한 Config Class를 생성한다.
WebSocketMessageBrokerConfigurer에는 WebSocket 클라이언트에서STOMP을 사용하여 메시지 처리를 구성하기
위한 메서드가 정의되어 있다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/chat")
.setAllowedOriginPatterns("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}
- @EnableWebSocketMessageBroker: STOMP 사용 활성화
- registerStompEndpoints(StompEndpointRegistry registry) : STOMP EndPoint 설정 및 SockJS 옵션 활성화
- addEndPoint(): WebSocket 핸드셰이크를 위해 WebSocket 클라이언트가 연결해야 하는 URL 엔드포인트
- withSockJS(): WebSocket을 지원하지 않는 브라우저에 대한 설정
- configureMessageBroker(MessageBrokerRegistry registry): Message Broker 옵션 구성
- setApplicationDestinationPrefixes(): 클라이언트가 스프링 애플리케이션으로 메시지를 보낼 때 사용하는 주소의 Prefix
- enableSimpleBroker(): 인메모리(in-memori) 메시지 브로커 활성화
스프링 서버가 클라이언트로 메시지를 송신할 때 사용하는 주소
해당 Prefix를 가진 주소를 구독(subscribe)하면 서버는 해당 주소로 메시지를 전송
보통 /topic 은 Pub/Sub 관계의 일대다를, /queue 는 일대일 메시지 교환을 위해 일반적으로 사용
코드 구현 예제
위에서 설정한 Config를 바탕으로 간단한 예시 구현 코드를 작성했다.
Dto
@Getter
public class StompDto {
private Long roomId;
private String message;
private String type;
}
Controller
@RestController
@RequiredArgsConstructor
public class StompController {
private final SimpMessageSendingOperations simpMessageSendingOperations;
@MessageMapping("/chat/message")
public void enter(StompDto stompDto) {
StompResDto stompRes = StompResDto.builder()
.message(stompDto.getMessage())
.sendTime(LocalDateTime.now())
.build();
simpMessageSendingOperations.convertAndSend("/queue/chat/room/" + stompDto.getRoomId(), stompRes);
}
}
@MessageMapping에 정의한 URI를 통해 클라이언트에서 서버로 요청이 온다. 이때 Config setApplicationDestinationPrefixes()
를 통해 설정한 "/app"이 prefix가 되고 서버는 "/app/chat/message"로 메시지 요청을 받게 된다.
SimpMessageSendingOperations 인터페이스에 정의되어 있는 convertAndSend 메서드를 통해 지정된 사용자에게 메시지를 반환
할 수 있다. 파라미터에는 enableSimpleBroker에서 설정한 Prefix를 가진URI에 클라이언트에서 받은 RoomId를 통해 사용자가
구독하는 주소를 지정해 주었다. 따라서 해당 주소를 구독하고 있는 사용자들에게 메시지가 반환된다.
위의 예시는 아주 간단하게 작성되었으며 실제 프로젝트에서는 메시지가 실제 채널에 전송되기 전이나 후에 대해 처리하기 위해
ChannelInterceptor 인터페이스를 구현하였고 STOMP 연결 혹은 메시지를 보낼 때마다 유효한 사용자인지 검증하기 위해
Spring Security와 JWT를 접목시켰다.
Reference
https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html
https://tecoble.techcourse.co.kr/post/2021-09-05-web-socket-practice/
'study > java' 카테고리의 다른 글
JAVA 21 Virtual Threads (0) | 2024.06.08 |
---|---|
자바 실행 과정(JVM의 개념 및 동작 과정) (0) | 2023.02.04 |
java 객체 배열이란? (객체 배열의 정의와 코드 구현 예제) (0) | 2023.01.09 |
java 접근 제어자 private, getter, setter.. (0) | 2022.10.13 |
[java/Eclipse] "Editor does not contain a main type" 에러 해결 (0) | 2022.08.03 |
댓글