스프링은 SockJS를 제공한다.
build.gradle 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-websocket'
TextWebSocketHandler를 상속받은 WebSocketHandler 추가
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketChatHandler extends TextWebSocketHandler {
...
WebSocketConfigurer 인터페이스를 구현한 WebSocketConfig 추가
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketChatHandler webSocketChatHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry
.addHandler(webSocketChatHandler, "/ws/chat")
.setAllowedOrigins("*")
.withSockJS();
}
}
@EnableWebSocket
은 WebSocket 서버를 사용하도록 정의하는 데 사용한다./ws/chat
으로 설정한다.setAllowedArigins("*")
를 통해 클라이언트에서 WebSocket 서버에 요청을 보낸다면, 모든 요청을 수용한다. (CORS)same-origin
만 허용하는 것이 기본 정책이다.WebSocketChatHandler
클래스를 WebSocket 핸들러로 지정한다.withSockJS()
로 SockJS 라이브러리를 사용하도록 설정할 수 있다.서버-클라이언트 소켓 통신에서 사용하는 메시지 스펙 MessageDto 정의
@Getter
@Setter
@NoArgsConstructor(access = PROTECTED)
public class MessageDto {
private MessageType type; // 메시지 타입
private String sender; // 보내는 사람
private String receiver; // 받는 사람
private String message; // 메시지
private LocalDateTime time; // 채팅 발송 시간
@Builder
public MessageDto(
MessageType type, String sender, String receiver, String message, LocalDateTime time) {
this.type = type;
this.sender = sender;
this.receiver = receiver;
this.message = message;
this.time = time;
}
}
메시지 전달 JSON 형식
{
"type": "TALK",
"receiver": [UUID],
"sender": [UUID],
"message": "hello"
}
WebSocket 핸들러 클래스 WebSocketChatHandler
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketChatHandler extends TextWebSocketHandler {
// 양방향 데이터 통신
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { ... }
// 웹 소켓 연결
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception { ... }
// 소켓 연결 종료
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { ... }
// 소켓 통신 에러
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { ... }
}
WebSocketHandler에 세션 정보를 담을 sessions 추가
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
WebSocketHandler의 afterConnectionEstablished() 메서드 구현
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 세션에 사용자 저장
String sessionId = session.getId();
sessions.put(sessionId, session);
log.info("[afterConnectionEstablished] ID={} 접속", sessionId);
// 입장 메시지 구성
MessageDto chatMessage = MessageDto.builder()
.type(ENTER)
.message(sessionId + "님이 입장했습니다")
.sender(sessionId)
.time(LocalDateTime.now())
.build();
// 본인을 제외한 나머지 세션에 입장 이벤트 전송
sessions.values().forEach(s -> {
if (!s.getId().equals(sessionId)) {
try {
s.sendMessage(new TextMessage(objectMapper.writeValueAsString(chatMessage)));
} catch (IOException e) {
log.info("[afterConnectionEstablished] errors={}", e.getMessage());
}
}
});
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 웹 소켓 클라이언트로부터 채팅 메시지(JSON)를 전달받음
String payload = message.getPayload();
log.info("[handleTextMessage] payload={}", payload);
// 전달받은 채팅 메시지를 채팅 메시지 객체로 변환 (JSON -> ChatDto)
MessageDto chatMessage = objectMapper.readValue(payload, MessageDto.class);
chatMessage.setType(TALK);
chatMessage.setTime(LocalDateTime.now());
// 메시지를 받는 대상
WebSocketSession receiver = sessions.get(chatMessage.getReceiver());
// 메시지를 받아야 하는 타겟 상대방 조회 후 메시지 전송
if (receiver != null && receiver.isOpen()) {
receiver.sendMessage(new TextMessage(objectMapper.writeValueAsString(chatMessage)));
}
}
handleTextMessage()
는 첫 번째 사용자가 두 번째 사용자에게 메시지를 전송할 때 거치게 되는 메서드이다.@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 세션 저장소에서 연결이 끊긴 사용자 삭제
String sessionId = session.getId();
sessions.remove(sessionId);
log.info("[afterConnectionClosed] ID={} 접속 해제", sessionId);
// 퇴장 메시지 구성
MessageDto chatMessage = MessageDto.builder()
.type(LEAVE)
.message(sessionId + "님이 나갔습니다")
.sender(sessionId)
.time(LocalDateTime.now())
.build();
// 본인을 제외한 나머지 세션에 퇴장 이벤트 전송
sessions.values().forEach(s -> {
try {
s.sendMessage(new TextMessage(objectMapper.writeValueAsString(chatMessage)));
} catch (IOException e) {
log.info("[afterConnectionEstablished] errors={}", e.getMessage());
}
});
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.info("[handleTransportError] errors={}", exception.getMessage());
}
WebSocketHandler
는 WebSocket이 1대인 경우에만 동작한다.