들어가며
웹 애플리케이션에서 실시간 양방향 통신이 필요한 경우가 많습니다. 채팅, 알림, 실시간 데이터 업데이트 등이 대표적인 예입니다. WebSocket이 이런 기능의 표준이지만, 모든 브라우저나 네트워크 환경에서 지원되는 것은 아닙니다. 이때 SockJS가 해결책이 됩니다.
SockJS가 뭔가요?
SockJS는 WebSocket과 유사한 API를 제공하는 JavaScript 라이브러리입니다. WebSocket을 우선적으로 사용하지만, WebSocket을 지원하지 않는 환경에서는 자동으로 다른 전송 방식(폴링, 스트리밍 등)으로 폴백(fallback)합니다.
주요 특징
- ✅ 브라우저 호환성: 오래된 브라우저도 지원
- ✅ 프록시/방화벽 통과: WebSocket이 막혀있어도 작동
- ✅ 투명한 폴백: 개발자가 신경 쓸 필요 없이 자동 처리
- ✅ Spring과의 완벽한 통합: Spring Framework에서 공식 지원
WebSocket vs SockJS
구분WebSocketSockJS
| 프로토콜 | ws:// / wss:// | http:// / https:// |
| 브라우저 지원 | 최신 브라우저만 | 거의 모든 브라우저 |
| 방화벽/프록시 | 막힐 수 있음 | 우회 가능 |
| 성능 | 최고 | 폴백 시 약간 느림 |
SockJS의 작동 방식
SockJS는 다음 순서로 전송 방식을 시도합니다:
- WebSocket - 가능하면 우선 사용
- HTTP Streaming - 서버가 계속 연결을 유지하며 데이터 전송
- HTTP Long Polling - 클라이언트가 요청을 보내고 서버가 응답을 지연
- XHR Polling - 주기적으로 요청을 보내 데이터 확인
Spring Boot에서 SockJS 설정하기
1. 의존성 추가
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. WebSocket 설정 클래스
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 클라이언트가 구독할 prefix
config.enableSimpleBroker("/topic", "/queue");
// 클라이언트가 메시지 보낼 때 prefix
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS(); // SockJS 활성화!
}
}
3. 컨트롤러 작성
@Controller
public class ChatController {
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(@Payload ChatMessage chatMessage) {
chatMessage.setType("JOIN");
return chatMessage;
}
}
클라이언트(JavaScript) 구현
1. 라이브러리 추가
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
2. 연결 코드
let stompClient = null;
function connect() {
// SockJS 객체 생성
const socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
// 메시지 구독
stompClient.subscribe('/topic/public', function(message) {
showMessage(JSON.parse(message.body));
});
});
}
function sendMessage() {
const messageContent = document.getElementById('message').value;
if(messageContent && stompClient) {
const chatMessage = {
sender: username,
content: messageContent,
type: 'CHAT'
};
// 메시지 전송
stompClient.send("/app/chat.sendMessage",
{},
JSON.stringify(chatMessage)
);
}
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
실전 예제: 간단한 채팅 애플리케이션
HTML
<!DOCTYPE html>
<html>
<head>
<title>SockJS 채팅</title>
</head>
<body>
<div id="chat-page">
<div id="messageArea"></div>
<div>
<input type="text" id="message" placeholder="메시지를 입력하세요">
<button onclick="sendMessage()">전송</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<script src="/js/chat.js"></script>
</body>
</html>
JavaScript (chat.js)
const username = prompt("이름을 입력하세요:");
let stompClient = null;
// 페이지 로드 시 연결
window.onload = function() {
connect();
};
function connect() {
const socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, onConnected, onError);
}
function onConnected() {
// 채팅방 구독
stompClient.subscribe('/topic/public', onMessageReceived);
// 입장 메시지 전송
stompClient.send("/app/chat.addUser",
{},
JSON.stringify({sender: username, type: 'JOIN'})
);
}
function onError(error) {
console.log('WebSocket 연결 실패: ' + error);
}
function sendMessage() {
const messageInput = document.getElementById('message');
const messageContent = messageInput.value.trim();
if(messageContent && stompClient) {
const chatMessage = {
sender: username,
content: messageContent,
type: 'CHAT'
};
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
messageInput.value = '';
}
}
function onMessageReceived(payload) {
const message = JSON.parse(payload.body);
const messageArea = document.getElementById('messageArea');
const messageElement = document.createElement('div');
messageElement.classList.add('chat-message');
if(message.type === 'JOIN') {
messageElement.textContent = message.sender + '님이 입장했습니다.';
} else if(message.type === 'LEAVE') {
messageElement.textContent = message.sender + '님이 퇴장했습니다.';
} else {
messageElement.textContent = message.sender + ': ' + message.content;
}
messageArea.appendChild(messageElement);
messageArea.scrollTop = messageArea.scrollHeight;
}
SockJS 엔드포인트 확인 방법
SockJS가 제대로 동작하는지 확인하려면 브라우저에서 다음 URL에 접속해보세요:
http://localhost:8080/ws/info
정상 작동 시 다음과 같은 JSON 응답이 나옵니다:
{
"websocket": true,
"origins": ["*:*"],
"cookie_needed": false,
"entropy": 123456789
}
주의사항 및 팁
1. CORS 설정
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*") // 개발 환경
// .setAllowedOrigins("https://yourdomain.com") // 운영 환경
.withSockJS();
}
2. 세션 타임아웃 설정
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(128 * 1024)
.setSendTimeLimit(20 * 1000)
.setSendBufferSizeLimit(512 * 1024);
}
3. 연결 끊김 처리
stompClient.reconnect_delay = 5000; // 5초 후 재연결
socket.onclose = function() {
console.log('연결이 끊어졌습니다. 재연결 시도 중...');
setTimeout(connect, 5000);
};
디버깅 팁
서버 로그 활성화
# application.yml
logging:
level:
org.springframework.web.socket: DEBUG
org.springframework.messaging: DEBUG
클라이언트 디버깅
// STOMP 디버그 모드 활성화
stompClient.debug = function(str) {
console.log('STOMP: ' + str);
};
실무에서 자주 사용하는 패턴
1. 특정 사용자에게만 메시지 전송
@MessageMapping("/chat.private")
public void sendPrivateMessage(@Payload ChatMessage message) {
messagingTemplate.convertAndSendToUser(
message.getReceiver(),
"/queue/private",
message
);
}
2. 인증된 사용자만 접근
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpDestMatchers("/app/**").authenticated()
.simpSubscribeDestMatchers("/topic/**", "/queue/**").authenticated()
.anyMessage().denyAll();
}
}
마치며
SockJS는 WebSocket의 편리함을 유지하면서도 다양한 환경에서 안정적으로 작동하는 훌륭한 라이브러리입니다. Spring Boot와의 통합도 매우 쉬워서 실시간 기능이 필요한 프로젝트에 바로 적용할 수 있습니다.
실시간 채팅, 알림, 대시보드 등을 구현할 때 SockJS를 활용해보세요! 🚀
'Spring Boot' 카테고리의 다른 글
| Spring Boot 3.x + Security 6.x JWT 인증 구현 가이드 (0) | 2025.12.23 |
|---|---|
| Spring RestTemplate vs WebClient 가이드 (0) | 2025.12.08 |
| Spring에서 Caffeine 캐시 완벽 가이드 (0) | 2025.12.05 |
| Spring Tag 라이브러리로 JSP include시 id 겹침 방지 (0) | 2025.01.03 |
| 개발 환경에서 서버 2개이상 돌릴 때 세션 유지 방법 (0) | 2023.12.19 |
댓글