이제 Spring으로 간단하게 Websocket을 구현해보자
앞서 설명이 길었던게 무색하리만큼, 실제 기본적인 코드 작성은 간단하다
WebSocket 요청을 처리할 핸들러와 해당 핸들러를 등록하기 위한 설정파일, 2가지이다.
- WebSocket 설정 파일
- WebSocket 핸들러
하나하나 알아보도록 하자
스프링이 지원하는 웹소켓 의존성를 추가해준다
build.gradle 파일
implementation 'org.springframework.boot:spring-boot-starter-websocket'
우선 여기서는 기본 WebSocket 만을 사용할 것이므로, 설정을 위한 어노테이션인 @EnabledWebSocket을 붙여준다
그리고 WebSocketConfigurer를 구현하여 사용한다
이를 코드로 표현하면 다음과 같다
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSockConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/chat")
.setAllowedOrigins("*")
.withSockJS();
}
}
여기서 구현한 인터페이스는 간단하게 핸들러를 설정 및 등록하는 기능을 갖고있다
registerWebSocketHandlers
addHandler 메소드로 사용할 핸들러를 등록해주며,
이후 Origin, OriginPattern 등 메소드들로 핸들러들을 설정한다
(관련 메소드들은 WebSocketHandlerRegistration를 참고하자)
withSockJS()
SockJS를 사용하겠다는 의미이며, 이것을 붙여야 SockJS를 사용할 수 있다
해당 메소드를 붙이면 더이상 WebSocket이 아닌 SockJS 설정으로 바뀌며,
SockJS에 관련한 각종 설정을 할 수 있다
(관련 메소드들은 SockJsServiceRegistration를 참고하자)
websocket 요청을 어떻게 처리할지를 담당하는 부분이다
Spring에서 사용하는 핸들러에는 대표적으로 다음 3가지가 존재한다
- BinaryWebSocketHandler
- TextWebSocketHandler
- SockJsWebSocketHandler
매우 간단하니 빠르게 알아보자
TextWebSocketHandler
이름 그대로 단순한 텍스트 메시지만을 다루는 핸들러이다. 이외의 데이터는 불가하다
BinaryWebSocketHandler
이미지, 파일 등 바이너리 데이터를 다루는 핸들러이다. 마찬가지로 단순 텍스트는 불가하다
SockJsWebSocketHandler
SockJS를 사용하기 위한 핸들러이다
SockJsWebSocketHandler는 잘 모르겠어서 확인해보았는데,
기존에 사용하던 WebSocketHandler에 SockJS를 사용할 수 있도록 바꿔주는 역할은 하는 것 같다
자세히 보니 SockJsWebSocketHandler를 사용하려면
기존의 WebSocketHandler가 있어야 하고,
SockJS 설정을 위한 SockJsServiceConfig도 있어야 하며,
SockFrame에 맞는 WebSocketServerSockJsSession도 생성해야 했다
즉, 기존 핸들러(Text, Binary)에 SockJS 설정을 덧씌워 사용가능하게 한 것!
하지만 withSockJS()를 통해 체이닝 방식으로 충분히 구현 가능하기에, 굳이 사용하지 않아도 될 것 같다
여기서는 간단하게 텍스트 핸들러를 작성해보기로 한다
코드는 다음과 같다
package org.example.multichat;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketHandler extends TextWebSocketHandler {
private final List<WebSocketSession> webSocketSessionList = new ArrayList<>();
// 커넥션 맺은 후(세션 리스트에 추가)
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
webSocketSessionList.add(session);
String msg = session.getId() + " 님이 입장하셨습니다.";
for (WebSocketSession wsSession : webSocketSessionList) {
wsSession.sendMessage(new TextMessage(msg+ "\n"));
}
}
// 메시지 다루기
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
for (WebSocketSession wsSession : webSocketSessionList) {
wsSession.sendMessage(new TextMessage(session.getId() + "> " +message.getPayload() + "\n"));
}
}
// 에러 다루기
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
}
// 연결이 끊긴 후(채팅방을 나갔으므로, 세션 리스트에서 삭제)
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
for (WebSocketSession wsSession : webSocketSessionList) {
wsSession.sendMessage(new TextMessage(session.getId() + " 님이 퇴장하셨습니다.\n"));
}
webSocketSessionList.remove(session);
super.afterConnectionClosed(session, status);
}
}
webSocketSessionList는 멀티 채팅 구현을 위해 추가한 변수이며, 입장/퇴장 시 추가/제거된다
크게 연결 및 종료 관련 메소드(afterConnectionEstablished, afterConnectionClosed)와
텍스트 메시지 처리 메소드(handleTextMessage),
웹소켓 에러 처리(handleTransportError)로 나뉜다
웹소켓 연결된 세션(WebSocketSession)으로 메시지를 보내거나(snedMessage) 인증된 유저정보(getPrincipal)를 가져올 수 있다
이전 편에서는 웹소켓은 메시지(프레임들)를 주고받는다 했는데, Spring은 이를 WebSocketMessage<T> 타입으로 구현했다
웹소켓 데이터 타입과 동일하게 텍스트/바이너리/핑으로 구성되어 있으며, 적절한 것을 사용하면 된다
이로써 간단하게 Spring으로 WebSocket + SockJS를 구현해보았는데, 아직 한단계가 더 남아있다
바로 STOMP 이라는 것인데, 다음 편에서 알아보도록 하자!