본문 바로가기
Java/Spring

Spring) Spring-WebSocket - Stomp, Stomp 환경설정 및 흐름파악

by 박채니 2022. 9. 14.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.

 

Stomp

https://docs.spring.io/spring-framework/docs/4.3.x/spring-framework-reference/html/websocket.html#websocket-stomp-message-flow

 

26. WebSocket Support

This part of the reference documentation covers Spring Framework’s support for WebSocket-style messaging in web applications including use of STOMP as an application level WebSocket sub-protocol. Section 26.1, “Introduction” establishes a frame of m

docs.spring.io

 

SimpleBroker발행된 목적지를 보고 구독자들에게 전달해주는 역할! (topic으로 시작하는 주소)

SimpAnnotationMethod부가적인 업무를 수행 후 SimpleBroker에게 전달! (app으로 시작하는 주소)

 

stomp cnd

<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js" integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

https://cdnjs.com/libraries/stomp.js

 

stomp.js - Libraries - cdnjs - The #1 free and open source CDN built to make life easier for developers

STOMP for JavaScript apps (Web browser & node.js) - Simple. Fast. Reliable. Content delivery at its finest. cdnjs is a free and open-source CDN service trusted by over 12.5% of all websites, serving over 200 billion requests each month, powered by Cloudfla

cdnjs.com


pom.xml

stomp 관련 의존 추가

<!-- #14 websocket 관련 의존 추가 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>${org.springframework-version}</version>
</dependency>	 

<!-- stomp관련 -->	    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

 

security-context.xml

<intercept-url pattern="/stomp/**" access="permitAll"/>

 

servlet-context.xml

<websocket:message-broker application-destination-prefix="/app">
    <websocket:stomp-endpoint path="/stomp">
        <websocket:sockjs />
    </websocket:stomp-endpoint>
    <websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>

application-destincation-prefix를 먼저 검사 후 해당된다면 (/app이라면), messageMapping을 타게 해주고,

해당하지 않는다면 simpleBroker가 처리해줍니다.

 

header.jsp

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js" integrity="sha512-1QvjE7BtotQjkq8PxLeF6P46gEpBRXuskzIVgjFpekzFVF4yjRgrQvTG1MTOJ3yQgvTteKAcO7DSZI92+u/yZw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js" integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- 로그인 했을 때만 웹 소켓 연결! -->
<sec:authorize access="isAuthenticated()">
	<script src="${pageContext.request.contextPath}/resources/js/ws.js"></script>
</sec:authorize>
</head>

로그인 했을 때만 웹 소켓 연결을 하기 위해 isAuthenticated()일 때만 ws.js 스크립트를 추가해주었습니다.

 

ws.js

// ws.js
const ws = new SockJS(`http://${location.host}/spring2/stomp`);

// stomp 객체 생성 -> 웹소켓 객체를 직접 제어하지 않고 stomp를 통해서 제어
const stompClient = Stomp.over(ws);

// 연결된 이후 호출해주는 핸들러. {} -> 옵션
stompClient.connect({}, (frame) => {
	console.log("connect : ", frame);
	
	// 연결 이후 구독 신청
	stompClient.subscribe("/topic/a", (message) => {
		console.log("/topic/a : ", message);
	});
	
	stompClient.subscribe("/app/a", (message) => {
		console.log("/app/a : ", message);
	})
});

SockJS를 이용하여 웹 소켓 객체를 생성(/stomp)하고, 직접 제어하지 않고 stomp를 통해 제어하기 위하여 Stomp.over()를 이용해 Stomp 객체를 생성해주었습니다.

사용자가 로그인을 하면 ws.js 스크립트가 실행될 것이고,  객체를 생성함에 따라 웹 소켓에 연결 될 것입니다.

connect()를 이용해 연결된 이후 "/topic/a", "/app/a"에 대해 구독 신청을 하게 됩니다. (subscribe())

 

로그인 후 콘솔창

따라서 로그인 후 콘솔창을 확인해보면, web socket open 후 connect 핸들러가 실행되어 "/topic/a"와 "/app/a"에 대해 구독 신청이 된 것을 확인할 수 있습니다.

 

"/topic/a" 구독자들에게 메세지 전송

'/topic/a'를 구독한 사람들에게 '안녕' 메세지를 보내보니, /topic/a를 구독한 현재의 로그인 사용자(honggd, qwerty)에게 메세지가 전달된 것을 확인할 수 있습니다.


SimpAnnotationMethod를 거치는 app


Controller

StompController

 

@MessageMapping - url은 prefix 제외하고 작성 (ex. /app/a → /a)

@SendTo - prefix부터 모두 작성, simpleBroker에게 전달

@Controller
@Slf4j
public class StompController {
	
	/**
	 * @MessageMapping
	 * - url은 prefix를 제외하고 작성
	 * - /app/a -> /a
	 * 
	 * @SendTo
	 * - prefix부터 모두 작성
	 * - simpleBroker에게 전달
	 */
	@MessageMapping("/a")
	@SendTo("/topic/a")
	public String simpleMessage(String message) {
		log.debug("message = {}", message);
		return message;
	}
}

"/app/a"로 받은 메세지를 @SendTo, "/topic/a"를 구독한 구독자들에게 전달하기 위해 simpleBroker에게 전달하여 simpleBroker가 구독자들에게 메세지를 전달합니다.

 

로그인 후 /app/a 구독자에게 메세지 송부

@콘솔출력값
DEBUG: com.ce.spring2.ws.controller.StompController - message = ㅋㅋ

마찬가지로 "/app/a"의 구독자 (현재 로그인한 사용자 - honggd, qwerty)에게 메세지를 송부한 것을 확인할 수 있습니다.

하지만 @SendTo로 simpleBroker에게는 "/topic/a"을 구독한 사용자에게 메세지를 전달하라고 하였기 때문에 destincation은 "/topic/a"인 것을 확인할 수 있습니다.

 

여기서 만일! "/app/a"로 보내면 "/topic/a"가 아닌 그대로 "/app/a" 구독자들에게 메세지를 송부하고 싶어서 @SendTo("/app/a")를 하면 simpleBroker는 "/app/a"를 몰라 처리 못해줍니다.

그 이유는 servlet-context.xml에 있습니다.

<websocket:simple-broker prefix="/topic"/>

simple-broker는 "/topic" prefix만 처리해주는 걸로 설정 되어있기 때문입니다.

 

<websocket:simple-broker prefix="/topic, /app"/>

이처럼 수정해주어 /app 구독자들에게도 메세지를 전달해줄 수 있게 해줍니다.

 

이번엔 @SendTo를 "/app/a"로 지정해주었습니다.

@MessageMapping("/a")
@SendTo("/app/a")
public String simpleMessage(String message) {
    log.debug("message = {}", message);
    return message;
}

destincation이 "/app/a"인 것을 확인할 수 있습니다.