개인적으로 토이 프로젝트를 하다가 서버에서 실시간으로 클라이언트의 화면을 변경해야하는 기능이 필요해서 웹소켓 통신을 구현하게 되었다.
처음에는 직접 웹소켓 통신을 구현하고 싶었는데 고려할 것이 너무 많았다... 메시지 전송 방식이나 heartbeat, 재연결 등등...
조금 더 찾아보니 STOMP(Simple Text Oriented Messaging Protocol)라는 프로토콜과 이를 사용하기 위한 라이브러리들이 존재하고 있었다.
그래서 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'
1) WebSocketMessageBrokerConfigurer을 상속 받은 Configuration 클래스 생성
2) 클래스에 @EnableWebSocketMessageBroker 어노테이션 추가: Spring WebSocket 메시징 시스템에서 브로커를 활성화
3) registerStompEndpoints override:
4) configureMessageBroker override:
@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();
}
}
위에서 언급한 것처럼 웹소켓 메시지 브로커 기능을 이용하여 웹소켓으로 메시지를 받고, 그 메시지를 클라이언트에 전달하기 위해서는 컨트롤러에 @MessageMapping, @SendTo 어노테이션을 붙여서 기능을 구현하면 된다.
내 경우에는 특정 API가 호출되면 로직을 처리한 후 대시보드에 내용을 실시간으로 업데이트하기 위해서, 즉 메시지를 전달하는 기능만 필요해서 SimpMessagingTemplate를 사용하였다. SimpMessagingTemplate의 convertAndSend 메서드에 메시지를 전달할 엔드포인트와 payload를 적어주면 된다.
@Service
@RequiredArgsConstructor
public class DashBoardService {
private final SimpMessagingTemplate simpMessagingTemplate;
public void processOrder( ) {
simpMessagingTemplate.convertAndSend("/dash-board", "TEST 메시지");
}
}
먼저 리액트 STOMP 관련 라이브러리를 찾아봤을 때, 크게 2가지 라이브러리를 사용하는 것 같았다.
stompjs와 @stomp/stompjs가 그것인데, stompjs가 좀 더 설정이 간단하지만 고급 설정에 제한이 있다는 단점이 있었고 @stomp/stompjs는 설정은 조금 더 복잡하지만 Heartbeat, 자동 재연결 같은 고급 기능을 제공한다는 장점이 있었다.
나는 일단 두 가지 다 구현해 보았다.
1) 라이브러리 설치(sockjs도 사용하려고 sockjs-client도 함께 설치)
npm install stompjs sockjs-client
2) 웹소켓 연결 코드
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();
};
}, []);
1) 라이브러리 설치
npm install @stomp/stompjs
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();
}
};
}, []);