사이드 프로젝트로 채팅 서버를 만들어보고 있다.
해당 서버는 STOMP 프로토콜을 사용해서 구현하고자 했는데, 이전에 쓰던 웹 앱이 안들어가지면서 STOMP 프로토콜을 테스트하기가 어려워서 직접 만들고자 하였다.
1. apic
이전 게시글을 보면 apic 라는 웹 사이트를 사용했었다. 하지만 현재(2024-10-27)는 접속이 안되어서 사용할 수가 없었다.
2. POSTMAN
POSTMAN 에도 웹소켓 프로토콜을 테스트해볼 수 있다. STOMP 역시 웹소켓을 기반으로 하기 때문에 양식만 맞춰 작성한다면 테스트할 수 있다.
문제는 STOMP 프로토콜의 마지막에는 null octet 이 들어가야한다.
하지만 아스키코드로 입력해보고, `^@` 를 사용해보고, 실제 `\0` 을 출력하고 그걸 복사해서 집어넣어도 null 값을 인식하지 못했다.
null 값으로 인해 테스트를 못하는 시간이 길어지자 그냥 만들기로 결정하였다.
실제 한 블로그에서도 중국에 있는 블로그를 통해서 테스트해볼 수 있었다는데 지금은 게시글이 내려간 상태였다.
3. 구현
이걸 위해 서버를 따로 올리기엔 부담스러우니 이전에 운영하던 Github Page 를 그대로 사용해보고자 했다.
이렇게 할 수 있는 이유는 해당 기능이 js 로 굉장히 간단하게 구현되기 때문이다.
해당 페이지에서 제공하는 기능은 다음과 같다.
- CONNECT
- SUBSCRIBE
- UNSUBSCRIBE
- SEND
- MESSAGE
기본적인 연결, 구독 기능이 가능하며 메세지를 보내고 받을 수 있다.
사이트 주소는 아래 있으니 실제 사용해볼 수 있다.
https://min9805.github.io/stomp/
HTML 로 이루어져있기 때문에 이걸 그대로 복사해서 로컬에서 사용해도 된다.
STOMP 테스트하면서 나처럼 쓸데없는 시간 소비하지 않기를 바라며 공유드린다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>STOMP</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
<script type="text/javascript">
let stompClient; // stompClient 변수를 전역으로 설정
let subscriptions = {}; // 구독 정보를 저장할 객체
function connectWebSocket() {
const url = $('#websocketUrl').val(); // 웹소켓 URL 가져오기
// websocket URL을 사용하여 stompClient 초기화
stompClient = Stomp.client(url);
stompClient.connect({}, stompConnectHandler, stompErrorHandler);
}
function stompConnectHandler() {
console.log('connected!');
}
function stompErrorHandler(e) {
console.error('stomp connect error - ', e);
}
function subscribeToPath(path) {
const subscription = stompClient.subscribe(path, (data) => {
displayMessage(data.body); // 메시지 수신 시 displayMessage 호출
});
// 구독 정보를 저장
subscriptions[path] = subscription;
}
function unsubscribeFromPath(path) {
if (subscriptions[path]) {
subscriptions[path].unsubscribe(); // 구독 해제
delete subscriptions[path]; // 구독 정보 삭제
console.log(`Unsubscribed from ${path}`);
}
}
function displayMessage(messageData) {
const messageBox = $('#messageBox');
// 수신된 메시지에서 message만 추출하여 추가
messageBox.append(`<div class="alert alert-info">${messageData}</div>`);
messageBox.scrollTop(messageBox[0].scrollHeight);
}
$(function () {
// 연결 버튼 클릭 시 웹소켓 연결
$('#connectBtn').click(connectWebSocket);
// 구독 추가 버튼 클릭 시
$('#addSubscriptionBtn').click(function () {
const subscriptionCount = $('#subscriptionList .subscription-form').length; // 현재 구독 수
const subscriptionForm = `
<div class="mb-3 input-group subscription-form" id="subscription-${subscriptionCount}" style="width: 500px;">
<input type="text" class="form-control" placeholder="SUB PATH" id="path-${subscriptionCount}" />
<button class="btn btn-primary subscribeBtn">SUB</button>
<button class="btn btn-danger unsubscribeBtn" style="display: none;">UNSUB</button>
</div>`;
$('#subscriptionList').append(subscriptionForm);
});
// 구독 버튼 클릭 시
$(document).on('click', '.subscribeBtn', function () {
const inputField = $(this).siblings('input');
const path = inputField.val();
subscribeToPath(path);
inputField.prop('disabled', true); // 입력 필드 비활성화
$(this).prop('disabled', true).hide(); // 구독 버튼 비활성화 및 숨김
$(this).siblings('.unsubscribeBtn').show(); // 구독 해제 버튼 표시
});
// 구독 해제 버튼 클릭 시
$(document).on('click', '.unsubscribeBtn', function () {
const inputField = $(this).siblings('input');
const path = inputField.val();
unsubscribeFromPath(path); // 구독 해제 함수 호출
inputField.prop('disabled', false); // 입력 필드 재활성화
$(this).siblings('.subscribeBtn').prop('disabled', false).show(); // 구독 버튼 재활성화
$(this).hide(); // 구독 해제 버튼 숨김
});
// 메시지 전송 버튼 클릭 시
$('#sendBtn').click(function () {
const destinationPath = $('#destinationPath').val(); // 대상 경로 가져오기
const messageJson = $('#message').val(); // JSON 형태의 메시지 가져오기
try {
const message = JSON.parse(messageJson); // JSON으로 변환
stompClient.send(destinationPath, {}, JSON.stringify(message)); // 메시지 발행
} catch (error) {
alert('유효한 JSON을 입력하세요!'); // JSON 오류 처리
}
});
});
</script>
</head>
<body>
<div class="container">
<h1>WebSocket CONNECT</h1>
<div class="mb-3 input-group" style="width: 500px;">
<input type="text" id="websocketUrl" class="form-control" placeholder="ws://localhost:8080/ws-stomp"/>
<button id="connectBtn" class="btn btn-primary">CONN</button>
</div>
<h2>SUBSCRIBE</h2>
<div id="subscriptionList"></div>
<div class="input-group mb-3">
<button id="addSubscriptionBtn" class="btn btn-secondary">ADD</button>
</div>
<h2>SEND MESSAGE</h2>
<div class="mb-3">
<label for="destinationPath" class="form-label">DESTINATION PATH:</label>
<input type="text" id="destinationPath" class="form-control" placeholder="/pub/send/message"/>
</div>
<div class="mb-3">
<label for="message" class="form-label">MESSAGE(JSON):</label>
<textarea id="message" class="form-control" placeholder='{"targetUsername": "유저명", "message": "전송할 메시지", "sender": "발신자명"}'></textarea>
</div>
<button id="sendBtn" class="btn btn-success">SEND</button>
<h2 class="mt-4">MESSAGES</h2>
<div id="messageBox" class="border p-3" style="height: 200px; overflow-y: auto;"></div>
</div>
</body>
</html>
'Programming > Coding' 카테고리의 다른 글
Clustered Index 를 활용한 Full Table Scan 없는 N 명 추첨 알고리즘 (3) | 2024.11.10 |
---|---|
Docker 를 통한 일관된 개발 환경 배포 방법 (5) | 2024.10.31 |
우아콘 2022 발표 'API Gateway Pattern 에는 API Gateway 가 없다' (4) | 2024.09.08 |
[CI/CD] Github Actions 에서 AWS Credential 을 AccessKey 로?? (0) | 2024.08.23 |
[CI/CD] Docker, Github Action, AWS 를 통한 자동 배포! (4) | 2024.07.03 |