WebSocket & Stomp - pub/sub 채팅 기능

박영준·2023년 8월 21일
0

Spring

목록 보기
53/58

websocket 만 사용할 경우, websocket session 에 직접 메시지를 전달해줘야만 했다.

1. Stomp 정의

  • "Simple Text Oriented Messaging Protocol"

  • 메시지 전송을 효율적으로 하기 위해 나온 프로토콜

    • websocket 위에서 동작
    • 클라이언트와 서버가 전송할 메세지의 유형, 형식, 내용들을 정의

2. 작동 원리

1) pub/sub 구조

이를 통해, 메세지를 공급하는 주체와 소비하는 주체를 분리해 제공할 수 있다.

예시를 들어보자.
우체통(Topic, 채팅방)이 있다고 가정할 때,
집배원(Publisher, 메세지 송신자)은 신문을 우체통(Topic, 채팅방)에 배달할 수 있다.
구독자(Subscriber, 메세지 수신자)는 우체통(Topic, 채팅방)에 신문이 배달되는 것을 기다렸다가 빼서 볼 수 있다.(단, 구독자는 다수가 될 수 있다)

이를 채팅방의 경우로 바꿔보자.
채팅방 생성 : pub/sub 구현을 위한 Topic 이 생성됨
채팅방 입장 : Topic 을 구독
채팅방에서 메세지를 송수신 : 해당 Topic 으로 메세지를 송신(pub), 수신(sub)

2) Broker

중간 다리 역할을 한다.
(유튜브의 구독을 생각하면 편하다)

  1. Subscriber 가 채팅창 채널을 구독하겠다고 Broker 에게 알린다

  2. 해당 채팅창 채널을 구독하고 있는 Sender 가 메시지를 보낸다

  3. Broker 는 해당 채팅창 채널을 구독하는 모든 클라이언트들에게 메시지를 전달한다

Broker 는 외부 Message Broker(RabbitMQ, ActiveMQ)를 사용해도 된다.

3. 특징

1) pub/sub 구조

  • 메세지를 전송하고 메세지를 받아 처리하는 부분이 확실히 정해져 있기 때문에, 개발자 입장에서 명확하게 인지하고 개발할 수 있는 이점

2) @MessageMapping

  • websocket 에서는 각 connection 별로 따로따로 handler 를 구현해야만 했으나,
    stomp 는 @Controller 가 적용된 객체를 이용해 조직적으로 관리가 가능

    • Controller 객체의 @MessageMapping 을 통해, 메시지를 라우팅시킬 수 있다.
      • 메세지는 stomp 의 "destination" 헤더를 기반으로 @Controller 객체의 @MethodMapping 메서드로 라우팅 된다.

    라우팅?

    • 패킷에 포함된 주소 등의 상세 정보를 이용하여 목적지까지 데이터 또는 메세지를 체계적으로 다른 네트워크에 전달하는 경로 선택, 스위칭하는 과정.
    • 데이터가 전달되는 과정에서 여러 네트워크들을 통과해야하는 경우가 생길 수 있는데, 여러 네트워크들의 연결을 담당하고 있는 라우터 장비가 데이터의 목적지가 어디인지 확인하여 빠르고 정확한 길을 찾아 전달해주는 것

3) Spring Security

  • Spring Security 를 통해, 메시지를 보호할 수 있다.

  • 메세지의 헤더에 값을 줄 수 있기 때문에, 헤더 값을 기반으로 통신 시 인증 처리를 구현할 수 있다.

4. 구현

1) 의존성 추가

dependencies {
	// stomp
    implementation 'org.webjars:stomp-websocket:2.3.4'
}
  • sockjs 를 추가해서, websocket 을 지원하지 않는 낮은 버전의 브라우저에서도 websocket 을 사용할 수 있도록 해준다.
    • 그러나, WebSocketConfig 클래스에 해당 설정을 추가했을 경우 프론트 쪽과 연결이 되지 않는 문제가 있었기에 해당 설정을 하지 않았다.

2) WebSocketConfig

@RequiredArgsConstructor
@Configuration
@EnableWebSocket        			// webSocket 활성화
@EnableWebSocketMessageBroker       // stomp 활성화
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {		// WebSocketMessageBrokerConfigurer 인터페이스를 구현

    // 1. Stomp websocket 연결
    @Override
    public void  registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-stomp")
                .setAllowedOriginPatterns("*");
    }

    // Stomp 사용을 위한 Message Broker 설정
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub");				// 2.
        registry.setApplicationDestinationPrefixes("pub");	// 3.
    }
}

1.

  • websocket 의 연결 주소는 엔드포인트에 설정한 대로, ws://localhost8080/ws-stomp 이 된다.

  • Postman 에서 테스트 할 경우, .setAllowedOriginPatterns("*") 설정이 있으면 websocket 연결이 불가능했다.

    • 단, Postman 에서는 Stomp 테스트 기능을 제공하지 않는다. 그래서 프론트 쪽과의 연결 이후 테스트를 진행했다.
  • 1) 에서와 같은 이유로 .withSockJS(); 설정은 해주지 않았다.

2.

  • 메시지 구독 요청
  • 메시지를 수신
  • Broker 가 해당 경로를 가로챈다

3.

  • 메시지 발행 요청
  • 메시지를 전송
  • Broker 에게로 전달된다
  • 메세지는 "destination" 헤더인 "/pub" 를 기반으로 @Controller 객체의 @MethodMapping 메서드로 라우팅된다.

3) Controller

(1) MessageController

@Controller
@RequiredArgsConstructor
public class MessageController {
    private final SimpMessageSendingOperations messagingTemplate;

    @MessageMapping("/chat/message")        // 1. 
    public void message(MessageDto message) {
        messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);     // 2.
    }
}
  • MessageController 는 websocket 에서의 WebSocketHandler 를 대체해준다.
    • 따라서, WebSocketHandler 는 불필요해진다.

1.

  • "/pub/chat/message" 로 들어오는 메시지를 처리

2.

  • "/sub/chat/room/{roomId}" 로 메시지 전송
  • 채팅룸을 구분하는 값
  • pub/sub 에서 Topic 역할

(2) MessageRoomController

@RestController
@RequestMapping("/message")
@RequiredArgsConstructor
public class MessageRoomController {
    private final MessageRoomService messageRoomService;

    // 쪽지방 생성
    @PostMapping("/room")
    public MessageRoomDto createRoom(@AuthenticationPrincipal UserDetailsImpl userDetails) {
        return messageRoomService.createRoom(userDetails.getUser());
    }

    // 쪽지방 전체 조회
    @GetMapping("/rooms")
    public List<MessageRoomDto> findAllRoom() {
        return messageRoomService.findAllRoom();
    }

    // 쪽지방 삭제
    @DeleteMapping("room/{id}")
    public MsgResponseDto deleteRoom(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return messageRoomService.deleteRoom(id, userDetails.getUser());
    }
}

참고: [Spring Boot] WebSocket과 채팅 (3) - STOMP
참고: [spring 멀티 채팅] websocket,sockjs,STOMP 이용한 채팅 기능-STOMP채팅구현(2)
참고: [Server] Websocket과 Stomp
참고: Stomp 이란?
참고: Spring websocket chatting server(2) - Stomp로 채팅서버 고도화하기

profile
개발자로 거듭나기!

0개의 댓글