[SpringBoot / React] STOMP로 웹소켓 통신하기

leeng·2025년 1월 14일
0

개인적으로 토이 프로젝트를 하다가 서버에서 실시간으로 클라이언트의 화면을 변경해야하는 기능이 필요해서 웹소켓 통신을 구현하게 되었다.
처음에는 직접 웹소켓 통신을 구현하고 싶었는데 고려할 것이 너무 많았다... 메시지 전송 방식이나 heartbeat, 재연결 등등...
조금 더 찾아보니 STOMP(Simple Text Oriented Messaging Protocol)라는 프로토콜과 이를 사용하기 위한 라이브러리들이 존재하고 있었다.
그래서 STOMP를 이용해 웹소켓 통신을 구현하기로 했고, 역시나 처음에 조금 헤맸었기에 일단 내가 구현한/이해한 내용을 정리해보려고 한다. 😁



SpringBoot 설정

1. build.gradle에 웹소켓과 관련 STOMP 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.5.1'
implementation 'org.webjars:stomp-websocket:2.3.4'

2. WebSocketConfig 추가

1) WebSocketMessageBrokerConfigurer을 상속 받은 Configuration 클래스 생성

2) 클래스에 @EnableWebSocketMessageBroker 어노테이션 추가: Spring WebSocket 메시징 시스템에서 브로커를 활성화

3) registerStompEndpoints override:

  • registry.addEndpoint()에 웹소켓 연결을 실행할 수 있는 엔드포인트 지정
  • setAllowedOrigins()으로 CORS 에러가 발생하지 않도록 origin 허용
  • withSockJS()로 SockJS 사용 여부 설정(사용하지 않을 거라면 withSockJS()를 생략하면 된다.)

4) configureMessageBroker override:

  • setApplicationDestinationPrefixes: 클라이언트로부터 메시지를 받을 엔드포인트의 접두사 설정
  • enableSimpleBroker: 메시지를 클라이언트가 구독한 경로로 전달하기 위한 메시지 브로커 설정
    (참고로 내 경우에는 서버에서 특정 로직 수행 후 클라이언트에 메시지를 전송하는 기능만 구현했기 때문에 configureMessageBroker는 실제 필요하지 않았다. 다만 참고를 위해 적어두었다. MessageBroker 기능을 사용하기 위해서는 @MessageMapping @SendTo 어노테이션을 이용해 메시지를 수신하고 전달하게 된다.)
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry
                // 클라이언트가 서버로 메시지를 보낼 때 엔드포인트의 접두사
                .setApplicationDestinationPrefixes("/pub")
                // 클라이언트로 구독한 경로로 메시지를 전달하기 위해 사용하는 내장 메시지 브로커 활성화
                .enableSimpleBroker("/sub");
    }
    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOrigins("http://localhost:3000")
                .withSockJS();
    }
}

3. WebSocket 메시지 전송

위에서 언급한 것처럼 웹소켓 메시지 브로커 기능을 이용하여 웹소켓으로 메시지를 받고, 그 메시지를 클라이언트에 전달하기 위해서는 컨트롤러에 @MessageMapping, @SendTo 어노테이션을 붙여서 기능을 구현하면 된다.

내 경우에는 특정 API가 호출되면 로직을 처리한 후 대시보드에 내용을 실시간으로 업데이트하기 위해서, 즉 메시지를 전달하는 기능만 필요해서 SimpMessagingTemplate를 사용하였다. SimpMessagingTemplate의 convertAndSend 메서드에 메시지를 전달할 엔드포인트와 payload를 적어주면 된다.

@Service
@RequiredArgsConstructor
public class DashBoardService {

    private final SimpMessagingTemplate simpMessagingTemplate;

    public void processOrder( ) {
        simpMessagingTemplate.convertAndSend("/dash-board", "TEST 메시지");
    }
}


React 설정

먼저 리액트 STOMP 관련 라이브러리를 찾아봤을 때, 크게 2가지 라이브러리를 사용하는 것 같았다.
stompjs와 @stomp/stompjs가 그것인데, stompjs가 좀 더 설정이 간단하지만 고급 설정에 제한이 있다는 단점이 있었고 @stomp/stompjs는 설정은 조금 더 복잡하지만 Heartbeat, 자동 재연결 같은 고급 기능을 제공한다는 장점이 있었다.
나는 일단 두 가지 다 구현해 보았다.

1. stompjs 사용

1) 라이브러리 설치(sockjs도 사용하려고 sockjs-client도 함께 설치)

npm install stompjs sockjs-client

2) 웹소켓 연결 코드

  • 해당 페이지 렌더링 시 웹소켓을 연결하기 위해 useEffect 안에 웹소켓 연결 로직 작성
    useEffect(() => {
      const socket = new SockJS('http://localhost:9090/ws'); // 서버 url + 서버에서 설정한 웹소켓 연결 엔드포인트
      const stompClient = Stomp.over(socket);

      stompClient.connect({}, () => {
          stompClient.subscribe('/dash-board', (message) => {
              console.log(message.body); 
          });
      });

      return () => {
          stompClient.disconnect(); 
      };
  }, []); 

2. @stomp/stompjs 사용

1) 라이브러리 설치

npm install @stomp/stompjs

2) 웹소켓 연결 코드

SockJS를 사용하는 경우에는 webSocketFactory를 설정해주고, 사용하지 않는 경우에는 brokerURL만 적어주면 된다.
참고로 둘 다 적어준 경우에는 webSocketFactory가 우선 적용(?)되는 것 같았다. (= 서버에 withSockJS() 설정이 있어야 웹소켓이 연결됨)

  const [client, setClient] = useState(null);
  
  useEffect(() => {
    const connect = () => {
      const clientdata = new Client({
        // webSocketFactory: () => new SockJS("http://localhost:9090/ws"), // 서버에 withSockJS() 설정이 있는 경우
        brokerURL: "ws://localhost:9090/ws", // 서버에 withSockJS() 설정이 없는 경우
        debug: function (str) {
          console.log(str);
        },
        reconnectDelay: 9000, // 재연결 시간 
        heartbeatIncoming: 4000,
        heartbeatOutgoing: 4000,
      });

      clientdata.onConnect = () => {
        clientdata.subscribe("/dash-board", (message) => {
          console.log("Received message:", message.body);
        });
      };

      clientdata.onStompError = (frame) => {
        console.error("STOMP error:", frame.headers["message"]);
      };

      clientdata.activate();
      setClient(clientdata);
    };

    connect();

    return () => {
      if (client) {
        client.deactivate();
      }
    };
  }, []);



이상 내가 기억하려고 적어본 웹소켓 서버/클라이언트 설정 방법이다. 나중에 기회가 되면 채팅 어플리케이션도 만들어 보고 싶다! 😊
profile
기술블로그보다는 기록블로그

0개의 댓글

관련 채용 정보