Skip to content

[Feat] RAGOps Dashboard WebSocket 실시간 push #137

Description

@kangcheolung

📌 Description

embedding_jobs 등 상태가 변경되는 시점에 대시보드를 보고 있는 모든 관리자 브라우저에 즉시 갱신된 지표를 전송합니다.

  • WebSocket 연결: /ws (SockJS)
  • 구독 채널: /topic/dashboard
  • Polling·고정 주기 스케줄러 방식은 채택하지 않음 — 상태 변경 이벤트 발생 시점에만 push
  • 이슈 1의 집계 로직을 그대로 재사용해 push payload를 만듦

✅ To-do

  • spring-boot-starter-websocket 의존성 추가
  • WebSocketConfig (STOMP endpoint /ws, /topic prefix) 구현
  • STOMP endpoint에 SockJS origin 허용 설정 추가 (CorsConfig와 별개로 관리 — Spring MVC CORS와 STOMP endpoint origin 설정은 서로 다른 설정 지점)
  • DashboardWebSocketController.sendDashboardUpdate() 구현 (이슈 1의 DashboardQueryService 재사용)
  • SecurityConfig에서 /ws/**를 permitAll()로 설정 (HTTP 레벨 인증 제거, STOMP 레이어로 이전 — 네이티브 websocket transport는 Upgrade 요청에 커스텀 헤더를 못 붙이므로 HTTP 레벨에서 hasRole("ADMIN")으로 막으면 STOMP CONNECT까지 도달하지 못함)
  • clientInboundChannelChannelInterceptor 추가 — STOMP CONNECT 프레임의 Authorization 헤더로 JWT 검증 후 Principal을 세션에 부착 (실패 시 연결 거부)
  • @EnableWebSocketSecurity로 SUBSCRIBE 커맨드에 /topic/dashboardROLE_ADMIN 명시적 권한 체크 추가 (CONNECT 검증 단독 의존 방지, 이중 방어)
  • 수동 트리거 기반 push 동작 검증 (테스트)

✅ 완료 기준

  • /ws 연결 및 /topic/dashboard 구독이 정상 동작한다
  • sendDashboardUpdate() 호출 시 구독 중인 클라이언트에게 최신 지표가 1초 이내 전달된다
  • ADMIN이 아닌 사용자는 STOMP CONNECT 또는 SUBSCRIBE 시점에 거부된다 (HTTP 핸드셰이크 자체는 permitAll이므로 여기서 막히지 않는다)
  • JWT 없이 또는 만료된 토큰으로 CONNECT 시도 시 연결이 거부된다

📒 기타

  • 선행 이슈: #이슈1 (대시보드 집계 API)
  • 후속 이슈(재처리 트리거, Worker 이벤트 훅)가 이 push 함수를 그대로 사용함
  • 인증을 HTTP 핸드셰이크가 아닌 STOMP CONNECT 프레임으로 이전한 이유: SockJS가 websocket transport로 직접 연결하면 브라우저 네이티브 WebSocket()이 커스텀 헤더를 지원하지 않아, xhr-streaming/xhr-polling 폴백에서만 되던 헤더 기반 인증이 transport에 따라 되다 안 되다 하는 문제가 있었음

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions