AutoSchedule 프로젝트는 실시간 협업 기능이 핵심이다.
캘린더 수정이나 작업 생성, 충돌 알림 등 모든 상호작용이 다른 사용자 화면에 즉시 반영되어야 한다.
그런데 기존에는 WebSocket이 실제로 잘 작동하는지 확인할 방법이 없다는 문제가 있었다.
- JWT 인증 실패
- Rate Limit 초과
- Origin 검증 실패
- 토큰 만료
- 네트워크 끊김
- 서버 재시작 중 연결 해제
이런 문제들이 발생해도 브라우저 콘솔에 로그만 찍힐 뿐,
실제로 클라이언트가 정상적으로 구독·수신을 하고 있는지 눈으로 확인하기 어려운 구조였다.
코드를 보면 실제로 아래와 같은 위험 요소가 있었다 :
- 구독 실패 시 조용히 실패
- 메시지가 전송/수신됐는지 확인 불가
- 여러 토픽 구독 시 중복 메시지 위험
- 예외 상황에 대한 대응 부족
이런 문제들을 해결하기 위해, 프론트엔드에서 바로 WebSocket 상태를 확인할 수 있는 클라이언트 구독 테스트 도구를 만들었다.
또한 서버에서는 테스트 메시지를 발행할 수 있는 테스트용 API를 추가해 실시간 기능 전체를 검증할 수 있는 환경을 구성하였다.
1. HTML 기반 WebSocket 테스트 페이지
프론트엔드에 정적 리소스 폴더에 ws-test.html 파일을 추가했다.
이 페이지에서 WebSocket/STOMP의 주요 기능을 수동으로 직접 확인할 수 있도록 구성했다.
// STOMP 클라이언트 생성 및 연결
const client = new StompJs.Client({
webSocketFactory: () => new SockJS('http://localhost:8080/ws'),
reconnectDelay: 5000,
connectHeaders: {
Authorization: `Bearer ${localStorage.getItem('accessToken')}`
},
onConnect: (frame) => {
updateStatus('connected', '연결됨');
addLog(`✅ 연결 성공!`, 'success');
},
onStompError: (frame) => {
addLog(`❌ STOMP 에러: ${frame.headers?.message}`, 'error');
}
});
client.activate();
// 토픽 구독
const subscription = client.subscribe('/topic/tasks/1',
(message) => {
const payload = JSON.parse(message.body);
addLog(`📨 [/topic/tasks/1]\n${JSON.stringify(payload, null, 2)}`, 'success');
},
(error) => {
addLog(`❌ 구독 실패: ${error.headers?.message}`, 'error');
}
);
구현한 기능
- STOMP 연결 / 해제
- JWT 자동 인증 (localStorage에서 읽어서 Authorization 헤더로 전달)
- 토픽 입력 후 구독 / 구독 해제
- 구독된 토픽 목록 표시
- 수신 메시지 실시간 로그 출력
- 메시지 타입별 색상 구분(INFO / SUCCESS / ERROR)
- 테스트 메시지 발행 폼과 연동
개발 중 WebSocket 문제를 디버깅할 때,
프론트 코드를 계속 수정하지 않고도 바로 WebSocket 상태를 확인할 수 있다는 점이 가장 큰 장점이다.
2. 테스트용 메시지 발행 API
서버에서도 테스트를 도와주기 위해 별도의 테스트 컨트롤러를 만들었다.
POST /api/test/ws/publish
@RestController
@RequestMapping("/api/test/ws")
public class WebSocketTestController {
private final CollaborationEventPublisher eventPublisher;
@PostMapping("/publish")
public ResponseEntity<String> publishTestMessage(@RequestBody PublishRequest request) {
Long teamId = request.teamId() != null ? request.teamId() : 1L;
switch (request.type().toLowerCase()) {
case "task" -> publishTaskEvent(teamId, request.payload());
case "calendar" -> publishCalendarEvent(teamId, request.payload());
case "notification" -> publishNotification(teamId, request.payload());
default -> {
return ResponseEntity.badRequest()
.body("지원하지 않는 메시지 타입: " + request.type());
}
}
return ResponseEntity.ok("메시지가 성공적으로 발행되었습니다.");
}
private void publishTaskEvent(Long teamId, Map<String, Object> payload) {
TaskEventMessage message = TaskEventMessage.created(
createDefaultTask(System.currentTimeMillis(), teamId)
);
eventPublisher.publishTaskEvent(message);
}
public record PublishRequest(
String type,
Long teamId,
Map<String, Object> payload
) {}
}
지원 기능
- Task / Calendar / Notification 등 이벤트 타입별 메시지 발행
- JSON 본문 자유 입력
- teamId 기반으로 /topic/tasks/{teamId} 같은 토픽에 메시지 전송
- 테스트 페이지에서 구독한 토픽에 바로 메시지 수신되는지 확인 가능
이 API를 통해 “구독이 정상적으로 되었는지”, “메시지가 프론트로 도착하는지”를 눈으로 볼 수 있다.
3. 테스트 시나리오 화면
테스트 도구를 만든 뒤, 다음 시나리오별로 화면을 캡처해 동작을 검증했다.
1) 페이지 첫 진입 화면

2) 연결 제어
- 로그인하지 않은 상태에서 연결 시도 → 인증 실패 로그 확인
- 로그인 후 다시 연결 시도 → STOMP CONNECTED 프레임 수신



3) 토픽 구독
- 예: /topic/tasks/1, /topic/calendar/1 등 실제에서 사용하는 토픽 구독
- 구독 성공 시, 구독 목록에 토픽이 추가되고, INFO 로그로도 기록

4) 메시지 발행
- 테스트 페이지 하단의 폼에서 JSON 메시지를 작성 후 발행
- 서버에서 해당 토픽으로 메시지 브로드캐스트
- 상단 메시지 로그에 토픽 이름 + JSON 내용이 초록색(SUCCESS) 박스로 출력

5. 없는 채널 구독
- 존재하지 않는 토픽(예: /topic/does/not/exist)을 구독 시도
- STOMP ERROR 프레임 및 에러 로그를 빨간색 배경으로 출력
- “어떤 토픽에서 어떤 이유로 실패했는지”를 한 번에 확인 가능

'Project > AutoSchedule' 카테고리의 다른 글
| 13일차 이후 ... - 그리디 배치(1차 MVP) (0) | 2025.11.23 |
|---|---|
| (12일차)13일차 - 일정 드래그/수정 반영 (0) | 2025.11.14 |
| 10일차 - SlotLock 구현 (0) | 2025.11.12 |
| 9일차 - 실시간 스케줄 브로드캐스트 구현 (1) | 2025.11.11 |
| 8.5일차 - Spring WebSocket 보안 3단계 추가 (0) | 2025.11.11 |