[Spring boot + React] STOMP로 실시간 채팅 구현하기 (1) - Spring boot 서버 구현하기

찐찐·2022년 9월 6일
2

STOMP

  • Simple Text Orientated Messaging Protocol의 약자이며, 중개 서버를 통해서 클라이언트간에 비동기적으로 메시지를 전송하기 위한 프로토콜이다. 클라이언트와 서버 간의 전달되는 텍스트 기반의 메시지 형식을 정의한다.

    • 단순성상호운용성(하나의 시스템이 여러 기종에서 제약 없이 호환) 두 가지를 메인 이념으로 삼고 디자인됐다.
  • subscribe/publish 기반으로 동작한다.

    • subscribe: 하나의 채널(타깃)을 클라이언트가 구독한다.
    • publish: 하나의 채널(타깃)을 구독 중인 모든 클라이언트에게 메시지를 발행한다.
  • STOMP는 HTTP를 모델로한 frame 기반의 프로토콜이다.
    - frame은 command, optional header, optional body로 구성된다.

    COMMAND
    header1:value1
    header2:value2
    
    Body^@
  • 텍스트 기반의 메시지 프로토콜이지만 binary 메시지의 전송도 허용한다.

  • STOMP 서버는 메시지가 전송될 수 있는 여러 destination으로 구성된다.

    • STOMP 프로토콜은 각 destination을 opaque string(형식이 정해져있지 않는 문자열)로 다루고, syntax는 각 서버가 구현하기에 따라 달라진다.
    • STOMP는 destination의 delivery semantics(메시지 전송 전략)를 정의하지 않고, 각 delivery semantics는 서버 별로, 혹은 destinations 별로 달라질 수 있다.
  • STOMP 클라이언트는 두 가지 모드의 동작을 할 수 있는 user-agent이다.

    • 생산자로서, SEND frame을 사용해 서버에게 메시지를 보낼 수 있다.
    • 소비자로서, SUBSCRIBE frame을 사용해 특정 destination에 메시지를 보낼 수 있고 서버로부터 MESSAGE frame을 받을 수 있다.

Spring boot에서 STOMP 사용하기

STOMP 이점

  1. @MessageMapping을 사용해서 handler를 직접 구현하지 않고 controller를 따로 분리해서 관리할 수 있다.
  2. 메시지에 header를 줄 수 있어 header 기반의 사용자 인증 구현이 가능하다.

실시간 채팅 구현

  • 빌드 도구: gradle
  • spring boot version: 2.3.8

0. 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-websocket'

1. WebScoketConfig 작성

웹소켓 설정을 위해 먼저 config를 작성한다.

  1. @EnableWebSocketMessageBroker annotation을 사용해서 STOMP를 사용할 수 있게 설정한다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { }
  1. 엔드포인트를 등록하기 위해 registerStompEndpoints method를 overide 한다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOrigins("*");
    }
}
  • 앞으로 웹소켓 서버의 엔드포인트는 /ws이다.
  • 클라이언트는 다른 origin이므로 cors 오류를 방지하기 위해 setAllowedOrigins를 미리 사용해서 허용할 origin을 등록해둔다.
    • * 는 모든 origin 허용이다. (보안을 위해선 특정 origin만 등록하자...)
  1. Message broker를 설정하기 위해 configureMessageBroker method를 overide 한다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOrigins("*");
    }
    
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub");
        registry.setApplicationDestinationPrefixes("/pub");
    }
}
  • enableSimpleBroker()를 사용해서 /sub가 prefix로 붙은 destination의 클라이언트에게 메시지를 보낼 수 있도록 Simple Broker를 등록한다.
  • setApplicationestinationPrefiexs()를 사용해서 /pub가 prefix로 붙은 메시지들은 @MessageMapping이 붙은 method로 바운드된다.

2. 메시지 DTO 작성

통신시에 주고 받을 메시지 형식을 작성한다.

@Data
public class ChatDto {
    private Integer channelId;
    private Integer writerId;
    private String chat;
}
  • 그냥 사용하고 싶은 형식으로 만들면 된다. 대신 각 구독 채널을 구분할 수 있는 식별자가 하나 있어야한다.

3. 메시지 핸들링 controller 작성

@RestController
@RequiredArgsConstructor
public class WebSocketController {
    private final SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/chat")
    public void sendMessage(ChatDto chatDto, SimpMessageHeaderAccessor accessor) {
        simpMessagingTemplate.convertAndSend("/sub/chat/" + chatDto.getChannelId(), chatDto);
    }
}
  • @MessageMapping annotation은 메시지의 destination이 /hello였다면 해당 sendMessage() method가 불리도록 해준다.
    • sendMessage()에서는 simpMessagingTemplate.convertAndSend를 통해 /sub/chat/{channelId} 채널을 구독 중인 클라이언트에게 메시지를 전송한다.
    • SimpMessagingTemplate 는 특정 브로커로 메시지를 전달한다.
    • 현재는 외부 브로커를 붙이지 않았으므로 인메모리에 정보를 저장한다.
  • 메시지의 payload는 인자(chatDto)로 들어온다.

참고

profile
백엔드 개발자 지망생

0개의 댓글