
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 로 오기 직전에 낚아채서 핸들러에서 검증 작업
}
}
WebSocketMessageBrokerConfigurer를 구현한 StompWebsocketConfig 클래스 생성
WebSocketMessageBrokerConfigurer 는 STOMPMessageBrokerRegistry를 통해 메시지를 발행, 구독하기 위한 url 패턴(prefix)를 정의. 이 클래스는 simplebroker라고 불리고, 메시지를 특정 topic에 연결해주는 브로커 역할 수행. - 외부 브로커를 연결 가능ChannelInterceptor를 구현한 StompHandler 클래스 생성
ChannelInterceptor는 클라이언트가 서버로 전송하는 STOMP 메시지(CONNECT, SUBSCRIBE, SEND, DISCONNECT)를 가로채는 인터셉터StompController클래스 생성
MessageMapping,SendTo어노테이션 활용한 메서드내에서 간단한 메시지 라우팅MessageMapping과 SimpMessageSendingOperations클래스의 convertAndSend 기능을 활용하여 송신 메서드, 수신 메서드 분리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());
}
}
EvenListener클래스 생성
EvenListener 생성채팅 코드 테스트

위 그림의 메시지 교환 절차
/(app)publish/{roomId} 경로로 메시지를 발행하면 broker에 의해서 /(topic)subscribe/{roomId} 이라는 경로의 채널에 메시지가 전달된다./topic/{roomId}를 구독하고 있는 클라이언트에게 실시간으로 메시지가 전달SimpAnnotationMethodMessageHandler
StompController에서 @MessageMapping과 @SendTo와 같은 어노테이션이 선언된 메서드를 처리하는 핸들러다.SimpleBroker(브로커)
SimpleBroker (내장 메시지 브로커 -enableSimpleBroker), StompBrokerRelay (외부 메시지 브로커 -enableStompBrokerRelay) 사용한다. StompWebSocketConfig에서 cofigureMessageBroker를 통해 설정