새발블로그
[Spring] WebSocket + STOMP 본문
1. WebSocket이란?
기존 HTTP 통신의 한계를 보완하기 위한 양방향 통신 프로토콜
| 항목 | 설명 |
| 비연결성(Connectionless) | 요청/응답 후 연결 종료 |
| 무상태성(Stateless) | 서버는 클라이언트 상태를 기억하지 않음 |
| 단방향 통신 | 클라이언트가 요청해야 응답 가능 → 실시간성 부족 |
-> 이런 단점을 보완하는 게 바로 WebSocket
2. WebSocket 특징
| 특징 | 설명 |
| 양방향 통신 | 서버 ↔ 클라이언트 실시간 메시지 교환 |
| 소켓 유지 | 한번 연결되면 TCP 소켓이 계속 유지됨 |
| 포트 공유 | HTTP와 동일한 80/443 포트 사용 (ws://, wss://) |
| 업그레이드 방식 | 최초 연결은 HTTP → Upgrade: websocket 헤더로 전환 |
3. STOMP 통신 구조 (Pub/Sub 모델)
- STOMP: WebSocket 위에서 동작하는 텍스트 기반 메시징 프로토콜
- Broker: 메시지를 중개해서 구독자에게 전달
- Spring: 내장 SimpleBroker 지원, 확장 시 RabbitMQ/Kafka 등 사용 가능
개념 정리
- Publisher: 메시지를 보내는 주체 (예: 채팅 입력한 사용자)
- Subscriber: 특정 토픽을 구독하고 메시지를 수신하는 클라이언트
- Broker: 메시지를 받아 구독자들에게 전달
- Topic: 채널 이름, /topic/… 형태
4. Spring 구성 흐름
WebSocket 설정
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic"); // 구독 prefix
config.setApplicationDestinationPrefixes("/app"); // 발행 prefix
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat-app").setAllowedOrigins("*");
// ws://localhost:8080/chat-app 접속
}
}
Controller
@Controller
public class ChatController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public GreetingMessage greeting(GreetingMessage message) {
return message;
}
@MessageMapping("/chat")
@SendTo("/topic/chat")
public ChatMessage chat(ChatMessage message) {
return message;
}
}
DTO
@Data @NoArgsConstructor @AllArgsConstructor
public class GreetingMessage {
private String name;
}
@Data @NoArgsConstructor @AllArgsConstructor
public class ChatMessage {
private String name;
private String content;
}
5. 클라이언트 (JS)
stomp.js 연결
<script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"></script>
연결 코드
const stompClient = new StompJs.Client({
brokerURL: 'ws://localhost:8080/chat-app'
});
stompClient.onConnect = (frame) => {
stompClient.subscribe('/topic/greetings', (greeting) => {
const msg = JSON.parse(greeting.body);
showMessage(`${msg.name}님이 입장했습니다.`);
});
stompClient.subscribe('/topic/chat', (chat) => {
const msg = JSON.parse(chat.body);
showMessage(`${msg.name}: ${msg.content}`);
});
stompClient.publish({
destination: '/app/hello',
body: JSON.stringify({name: document.getElementById('name').value})
});
};
메시지 전송
function sendMessage() {
const name = document.getElementById('name').value;
const content = document.getElementById('content').value;
stompClient.publish({
destination: '/app/chat',
body: JSON.stringify({ name, content })
});
}
6. 전체 통신 흐름 요약
- 클라이언트가 /chat-app으로 WebSocket 연결
- 구독자 → /topic/... 구독
- 발행자 → /app/... 경로로 메시지 전송
- 서버 Controller에서 처리 후 @SendTo 경로로 브로드캐스트
- 구독 중인 클라이언트들이 메시지 수신
7. 장점
- 채팅, 알림, 주식 시세 등 실시간 기능 쉽게 구현
- HTTP Polling 대비 효율적
- Spring에서 설정이 단순 (@EnableWebSocketMessageBroker 한 줄)
- STOMP 덕분에 Pub/Sub 모델 명확
8. STOMP Trouble Shooting
1. CORS 문제
- 증상: JS WebSocket 연결 시 403 Forbidden
- 원인: setAllowedOrigins("*") 누락
- 해결:
registry.addEndpoint("/chat-app").setAllowedOrigins("*");
2. 연결은 되는데 메시지가 안 옴
- 원인 1: @MessageMapping 경로와 JS destination 불일치
- 원인 2: /app prefix(발행), /topic prefix(구독) 혼동
- 체크:
- 발행용: /app/...
- 구독용: /topic/...
3. HTTPS 환경에서만 연결 실패
- 원인: ws://는 HTTPS 환경에서 차단
- 해결: wss://(보안 WebSocket) 사용
4. 메시지 유실/끊김
- 원인: 내장 SimpleBroker는 대규모 트래픽에 취약
- 해결: RabbitMQ, Kafka 같은 외부 Broker 사용
5. 클라이언트 오류 감지 안 됨
- 원인: onStompError 콜백 미등록
- 해결:
stompClient.onStompError = (frame) => {
console.error("Broker error: " + frame.headers['message']);
};
'Server > Spring' 카테고리의 다른 글
| [Spring] MyBatis에서 Enum 타입 안전하게 매핑하기 (0) | 2025.10.07 |
|---|---|
| [Spring] Spring Batch + Scheduler (0) | 2025.10.07 |
| [Spring] AOP (Aspect Oriented Programming) (0) | 2025.09.22 |
| [Spring] 직렬화와 역직렬화 (0) | 2025.09.22 |
| [Spring] 파일 업로드 & 다운로드 (0) | 2025.09.22 |