STOMP

박태정·2025년 3월 27일

웹소켓

목록 보기
8/8

STOMP 기반 통신 구조

package site.gatein.backend.chat.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker // STOMP는 EnableWebSocketMessageBroker 를 사용
@RequiredArgsConstructor
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final StompHandler stompHandler;

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/connect")
            .setAllowedOrigins("http://localhost:3000")
            .withSockJS(); // sockJS 라이브러리: ws:// 가 아닌 http:// 엔드포인트를 사용할 수 있게 해줌
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {

        // /publish로 시작하는 url에 메세지가 발행이 되면 @Controller객체의 @MessageMapping메서드로 메세지가 전달(라우팅)

        registry.setApplicationDestinationPrefixes("/publish"); //  /publish/{몇번방} 형태로 메세지가 발행해야함
        registry.enableSimpleBroker("/topic"); //  /topic/{몇번방} 형태로 메세지를 수신해야함
    }

    @Override // 웹소켓 요청(connect, subscribe, diconnect)등의 요청시에는 http 헤더 http메세지를 넣어올 수 있고 이를 interceptors하여 토큰을 검증
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(stompHandler); // 여기는 핸들러가 들어간다. 즉, 필터를 지나서 configs 로 오기 직전에 낚아채서 핸들러에서 검증 작업
    }
}
  1. WebSocketMessageBrokerConfigurer를 구현한 StompWebsocketConfig 클래스 생성

    • WebSocketMessageBrokerConfigurerSTOMP
      프로토콜
      을 기반으로 WebSocket 메시지 브로커를 설정하고 구성할 때 사용
    • 주요 기능
      • 최초 연결을 맺기 위한 endpoint를 정의한다.
      • MessageBrokerRegistry를 통해 메시지를 발행, 구독하기 위한 url 패턴(prefix)를 정의. 이 클래스는 simplebroker라고 불리고, 메시지를 특정 topic에 연결해주는 브로커 역할 수행. - 외부 브로커를 연결 가능
      • interceptor가 필요하다면 interceptor 클래스를 추가
  2. ChannelInterceptor를 구현한 StompHandler 클래스 생성

    • ChannelInterceptor는 클라이언트가 서버로 전송하는 STOMP 메시지(CONNECT, SUBSCRIBE, SEND, DISCONNECT)를 가로채는 인터셉터
  3. StompController클래스 생성

    • 실습 1
      • MessageMapping,SendTo어노테이션 활용한 메서드내에서 간단한 메시지 라우팅
    • 실습 2
      • MessageMappingSimpMessageSendingOperations클래스의 convertAndSend 기능을 활용하여 송신 메서드, 수신 메서드 분리
      • 이 실습 2 는 실습 1의 한계를 해결한 메서드였다. SendTo 어노테이션을 특정 클래스를 활용해서 송신 기능을 구현. 어노테이션의 자유롭지 못한 것을 해소
package site.gatein.backend.chat.config;


import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/*
* - 실시간 서비스의 가장 큰 문제점이 너무많은 연결 객체 때문에 서버에 과부화가 오는것이다.
* 그래서 가장 중요한 점은 연결 객체가 적절하게 잘 제거되는가? 를 잘확인해야한다.
*
* - Spring 과 STOMP 는 기본적으로 세션관리를 자동(내부적)으로 처리
* - 연결 객체를 새롭게 만드는 것은 백엔드에서는 어떻게 할 수 가 없다.
* 프론트엔드에서 적절하게 커넥션을 끊어 주지 않아 가지고 발생. 그래서 그런 것들을 디버깅 할 목적으로 여기에 코드를 짜는 것이다.
* - 연결/해제 이벤트를 기록, 연결된  세션수를 실시간으로 확인할 목적으로 이벤트 리스너를 생성 -> 로그, 디버깅 목적
* */
@Slf4j
@Component
public class StompEventListener {

    private final Set<String> sessions = ConcurrentHashMap.newKeySet();

    // 세션이 커넥트가 발생하면 아래 이벤트 리스너가 동작하게 된다.
    @EventListener
    public void connectHandle(SessionConnectEvent event){
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
        sessions.add(accessor.getSessionId());
        log.info("connect session ID {}", accessor.getSessionId());
        log.info("total session : {}", sessions.size());
    }

    // 세션이 디커넥트가 발생하면 아래 이벤트 리스너가 동작하게 된다.
    @EventListener
    public void disconnectHandle(SessionDisconnectEvent event){
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
        sessions.remove(accessor.getSessionId());
        log.info("disconnect session ID {}", accessor.getSessionId());
        log.info("total session : {}", sessions.size());
    }
}
  1. EvenListener클래스 생성

    • 스프링과 STOMP는 기본적으로 세션 관리를 자동으로 처리
      • 클라이언트가 /connect 엔드포인트를 통해 연결될 때 세션이 생성
      • 클라이언트가 연결을 종료하거나 타임아웃이 발생하면 세션이 삭제
    • 세션관련하여 추가적인 작업이 필요한 경우 EvenListener 생성
      • 연결/해제 이벤트를 기록
      • 연결된 세션 수를 실시간으로 조회
  2. 채팅 코드 테스트

    • Websocket 같은 경우는 postman에서 지원하지만 stomp기반의 통신은 현재 postman에서는 지원하지 않고, 이를 지원하는 외부 서비스가 마땅치 않으므로, 간단한 프론트엔드 구현과 함께 테스트를 하는 것을 권장

Spring에서의 STOMP 주요 요소

  • 위 그림의 메시지 교환 절차

    • 클라이언트에서 지정된 /(app)publish/{roomId} 경로로 메시지를 발행하면 broker에 의해서 /(topic)subscribe/{roomId} 이라는 경로의 채널에 메시지가 전달된다.
    • 동시에 /topic/{roomId}를 구독하고 있는 클라이언트에게 실시간으로 메시지가 전달
  • SimpAnnotationMethodMessageHandler

    • 우리 코드상의 StompController에서 @MessageMapping@SendTo와 같은 어노테이션이 선언된 메서드를 처리하는 핸들러다.
    • 어노테이션을 통해 지정된 메서드로 메시지를 라우팅하는 역할
  • SimpleBroker(브로커)

    • Spring 에서 제공하는 메모리 기반 브로커로서, 경로에 따라 메시지를 분배하고 클라이언트에게 메시지를 전달한다.
    • SimpleBroker (내장 메시지 브로커 -enableSimpleBroker), StompBrokerRelay (외부 메시지 브로커 -enableStompBrokerRelay) 사용한다.
    • 우리 코드 상에 StompWebSocketConfig에서 cofigureMessageBroker를 통해 설정

0개의 댓글