본문 바로가기
Java/Spring

Spring) Web-Socket - Stomp (전체/개별 공지사항 전송 구현)

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

 

웹 소켓 이용하여 공지 전송하기

 

header.jsp

<!-- 로그인 했을 때만 웹 소켓 연결! -->
<sec:authorize access="isAuthenticated()">
	<script>
	const memberId = "<sec:authentication property='principal.username'/>";
	</script>
	<script src="${pageContext.request.contextPath}/resources/js/ws.js"></script>
</sec:authorize>
</head>

 

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);
		})
	*/
	
	// 전체 공지
	stompClient.subscribe("/app/notice", (message) => {
		console.log("/app/notice : ", message);
	});
	
	// 개별 공지
	stompClient.subscribe(`/app/notice/${memberId}`, (message) => {
		console.log(`/app/notice/${memberId} : `, message);
	})
});

전체공지는 "/app/notice", 개별공지는 "/app/notice/(memberId)" 구독처리를 하였습니다.

memberId는 ws.js 선언 전에 변수화하였기 때문에 사용할 수 있겠죠.

 

※ 만일 subscribe를 connect 이후에 호출한다면 작동되지 않음!

stomp 또한 비동기처리이기 때문에 call stack에서 바로바로 처리 되지 않고 백그라운드에 위임시켜버리기 때문에 connect 호출 후 연결이 완료되기 전에 subscribe가 처리 되므로 오류 발생!!!

따라서 subscribe는 connect의 콜백함수 안에 위치해야 함

https://chanychu.tistory.com/281

 

Javascript) 동기식 / 비동기식 - Timer API, DOM 관련 이벤트 처리, DOM 연속

안녕하세요, 코린이의 코딩 학습기 채니 입니다. 개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다. 동기 (synchronous) / 비동기 (asynchronous) 비동기 - Javascript는 싱글 쓰레드로 진

chanychu.tistory.com

 

로그인 시 위와 같이 "/app/notice"와 "/app/notice/(memberId)" 구독처리하는 것을 확인할 수 있습니다.


memberList.jsp

<jsp:include page="/WEB-INF/views/common/header.jsp">
	<jsp:param name="title" value="관리자 회원 관리" />
</jsp:include>

<div class="text-center">관리자 회원관리 페이지입니다. 이 페이지를 볼 수 있는 당신은 관리자!!</div>
    <div class="w-75 mx-auto">
        <button 
            type="button" 
            class="btn btn-block btn-outline-primary"
            data-toggle="modal" data-target="#adminNoticeModal">공지</button>
    </div>
    
	<!-- 관리자용 공지 modal -->
    <div class="modal fade" id="adminNoticeModal" tabindex="-1" role="dialog" aria-labelledby="#adminNoticeModalLabel" aria-hidden="true">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="adminNoticeModalLabel">Notice</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">&times;</span>
            </button>
          </div>
          <div class="modal-body">
            <form name="adminNoticeFrm">
              <div class="form-group">
                <label for="send-to-name" class="col-form-label">받는이 :</label>
                <input type="text" class="form-control" id="send-to-name" placeholder="공란인 경우, 전체공지로 전송됩니다.">
              </div>
              <div class="form-group">
                <label for="message-text" class="col-form-label">메세지 :</label>
                <textarea class="form-control" id="message-text"></textarea>
              </div>
            </form>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-primary" id="adminNoticeSendBtn">전송</button>
            <button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
          </div>
        </div>
      </div>
    </div>
<script>
document.querySelector("#adminNoticeSendBtn").addEventListener('click', (e) => {
	const to = document.querySelector("#send-to-name").value;
	const msg = document.querySelector("#message-text").value;
	if(!msg) return;
	
	const payload = {
		from : '<sec:authentication property="principal.username"/>',
		to,
		msg,
		time : Date.now(),
		type : 'NOTICE'
	};
	
	const url = to ? `/app/admin/notice/\${to}` : '/app/admin/notice';
	stompClient.send(url, null, JSON.stringify(payload)); // 텍스트 기반으로 송부!(객체론 안됨)
	document.adminNoticeFrm.reset();
	
	// 모달 숨기기
	$("#adminNoticeModal").modal('hide');	
});
</script>

받는이를 설정하였기 때문에 '/app/admin/notice/(memberId)'로 잘 전송 된 것을 확인할 수 있습니다.

하지만 messageMapping을 안 했기 때문에 구독자들에게 해당 메세지가 전달 되진 않았습니다.

mapping처리를 하기 위해 Controller로 가보겠습니다.


전체공지

 

Controller

StompController

/**
 * 전체공지
 * @RequestBody -> json을 자바 객체로 변환!
 */
@MessageMapping("/admin/notice")
@SendTo("/app/notice")
public Payload notice(@RequestBody Payload payload) {
    log.debug("payload = {}", payload);

    return payload;
}

@콘솔출력값
DEBUG: com.ce.spring2.ws.controller.StompController - payload = Payload(type=NOTICE, to=, from=admin, msg=전체공지!!, time=1663222906963)

 

'/app/notice' 구독자들에게 메세지가 잘 전송 된 것을 확인할 수 있습니다.

관리자 또한 '/app/notice'의 구독자이므로 메세지를 보내고, 그대로 돌려받았습니다.

 

받은 메세지 시각화

ws.js

	// 전체 공지
	stompClient.subscribe("/app/notice", (message) => {
		console.log("/app/notice : ", message);
		const {body} = message;
		const {msg, time} = JSON.parse(body);
		alert(`전체공지
=========================
${msg}
=========================
${new Date(time)}`);
	});


개별공지

 

Controller

StompController

@MessageMapping("/admin/notice/{memberId}")
@SendTo("/app/notice/{memberId}")
public Payload notice(@RequestBody Payload payload, @DestinationVariable String memberId) {
    log.debug("payload = {}", payload);
    log.debug("memberId = {}", memberId);

    return payload;
}

@콘솔출력값
DEBUG: com.ce.spring2.ws.controller.StompController - payload = Payload(type=NOTICE, to=honggd, from=admin, msg=홍지디야 속닥속닥, time=1663223916158)
DEBUG: com.ce.spring2.ws.controller.StompController - memberId = honggd

 

ws.js

	// 개별 공지
	stompClient.subscribe(`/app/notice/${memberId}`, (message) => {
		console.log(`/app/notice/${memberId} : `, message);
		const {body} = message;
		const {msg, time} = JSON.parse(body);
		alert(`개인공지
=========================
${msg}
=========================
${new Date(time)}`);
	});

'/app/notice/honggd'를 구독하고 있는 honggd에게만 메세지가 도착한 것을 알 수 있습니다.