중고 경매 사이트에 필요한 채팅기능을 구현하기 앞어서 먼저 웹소켓을 공부하기 위해 구현해 보았다.
이전에 간단한 Websocket을 이용하여 구현하였는데 Stomp를 적용시켜서 채팅 기능을 고도화해보자.
Stomp는 메시징 전송을 효율적으로 하기 위해 나온 프로토콜로 pub/sub구조로 되어있어서 메시지를 발송하면 구독(sub)하고 있는 세션에게 발행(pub)하는 방식이다. 또한 Stomp를 이용하면 통신 메시지의 헤더에 값을 세팅할 수 있어서 헤더 값을 기반으로 통신 처리도 가능한 것이 장점이다.
이를 채팅방에 대입해서 생각해보면
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.1.2'
implementation 'org.webjars:stomp-websocket:2.3.3-1'
implementation 'org.webjars.bower:axios:0.17.1'
}
thymeleaf를 이용하여서 헤더를 import했는데 이 의존성은 지우고 기본 html로 적용 가능하다.
webjars는 웹 화면 구성을 위한 라이브러리이다.
v1에 사용하였던 메시지를 세션에게 직접 보내는 기능을 하는 WebSocketChatHandler는 Stomp를 사용하여 sub/pub으로 동작하기 때문에 없어도 된다. 이외에도 테스트를 Simple WebSocket Client를 사용하여 테스트 하였기 때문에 필요없었던 ChatRoomController를 추가하였다.
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/sub");
registry.setApplicationDestinationPrefixes("/pub");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp")
.withSockJS();
}
}
@EnableWebSocketMessageBroker
는 웹 소켓 서버를 사용하겠다는 설정이다. 또한 WebSocketMessageBrokerConfigurer
를 통해 웹 소켓에 관련된 몇 가지 기능을 구현할 수 있다.
registerStompEndPoints()
를 이용하여 클라이언트에서 웹소켓에 접속하는 endpoint를 등록한다.
javascript코드를 살짝 확인해보면 아래와 같이 /ws-stomp
라는 경로로 소켓에 접속한다.
다음으로 configureMessageBroker()
는 클라이언트에서 다른 클라이언트로 메시지를 라우팅할 때 /sub
이라는 경로로 보낸다. /pub
으로 시작되는 주제를 가진 메시지 핸들러로 라우팅하여 해당 주제를 구독하고 있는 모든 클라이언트에게 메시지를 발송한다.
마찬가지로 javascript 코드를 확인하면 ws.send(/pub/chat/message)
라는 경로로 메시지를 보낸다. roomId기준으로 /sub/chat/room/{roomId}
를 구독하고 있는 사용자에게 메시지를 전송하는 것이다.
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
@Slf4j
@Getter
@AllArgsConstructor
@Builder
public class ChatRoom {
private String roomId;
private String name;
public static ChatRoom of(String name) {
return ChatRoom.builder()
.name(name)
.roomId(UUID.randomUUID().toString())
.build();
}
}
Stomp를 사용하여 pub/sub 방식으로 구현되기 때문에 기존에 직접 메시지를 보내던 기능을 없앨 수 있다.
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;
import site.leui.chat_example.chat.dto.ChatMessage;
@RequiredArgsConstructor
@Controller
public class ChatController {
private final SimpMessageSendingOperations messagingTemplate;
@MessageMapping("/chat/message")
public void sendMessage(ChatMessage message) {
if (isJoin(message))
message.setMessage(message.getSender() + "님이 입장하였습니다");
messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
}
private boolean isJoin(ChatMessage messageType) {
return messageType.getMessageType().equals(ChatMessage.MessageType.JOIN);
}
}
@MessageMapping
을 통해 웹 소켓으로 들어오는 메시지 발행을 처리한다.
위에서 설정하였던 prefix인 /pub
+ /chat/message
로 발행 요청을 하면 Controller가 해당 메시지를 받아서 처리하게 된다. messagingTemplate.convertAndSend()
함수를 통해 /sub/chat/room/
+ roomId
를 구독하고 있는 유저에게 메시지를 발행한다.(보낸다)
방을 다시 들어갈 때마다 socket 객체를 만들어 주고 구독을 해야한다.
위 ws.connect()
를 확인해보면 먼저 roomId를 기준으로 구독을 한다. 그 후 ~~님이 입장하셨습니다 메시지를 모든 구독자에게 보낸다. 만약 연결이 끊기거나 에러가 발생하면 최대 6번 연결을 한다.
대화 내용 전송 함수와 메시지를 받았을 때 처리하는 코드이다.
너무 좋은 글이네요. 공유해주셔서 감사합니다.