본문 바로가기
공부

STOMP 프로토콜

by son_i 2024. 1. 2.
728x90

STOMP란 ?

Simple Text Oriented Messaging Protocol 의 약자로 메세징 전송을 효율적으로 하기 위해 탄생한 프로토콜이다.

기본적으로 pub/sub 구조로 되어있어 메세지를 전송하고 메세지를 받아 처리하는 부분이 확실히 정해져 있어 개발자가 명확하게 인지하고 개발할 수 있음.

 

STOMP 프로토콜은 WebSocket위에서 동작하는 프로토콜로, 클라이언트와 서버가 전송할 메세지의 유형, 형식, 내용들을 정의하는 매커니즘.

 

STOMP는 TCP 또는 WebSocket 같은 양방향 네트워크 프로토콜 기반으로 동작.

이름에서 알 수 있듯이 Text 지향 프로토콜이나, Message Payload에는 Text or Binary 데이터를 포함할 수 있다.

 

pub/sub는 메세지를 공급하는 주체, 소비하는 주체를 분리해 제공하는 메세징 방법.

 

채팅방 생성 : pub/sub 구현을 위한 Topic이 생성
채팅방 입장 : Topic 구독
채팅방에서 메세지 송/수신 : 해당 Topic으로 메세지를 송신(pub), 수신(sub)

 

클라이언트는 메세지를 전송하기 위해 SEND, SUBSCRIBE COMMAND를 사용할 수 있다.

 

COMMAND 요청 frame에는 메세지가 무엇이고, 누가 받아서 처리할지에 대한 Header 정보가 포함되어 있다.

이런 명령어들은 "destination" 헤더를 요구하는데 이것이 어디에 전송할지, 어디에서 메세지를 구독할 것인지를 나타낸다.

 

위와같은 과정으로 STOMP는 publish-subscribe 매커니즘을 제공.

 

-> 즉, Broker를 통해 타 사용자들에 메세지를 보내거나 서버가 특정 작업을 수행하도록 메세지를 보낼 수 있게 됨.

 

Spring에서 지원하는 STOMP

- Spring WebSocket 어플리케이션은 STOMP Broker로 동작.

 

- Simple In-Memory Broker를 이용해 SUBSCRIBE 중인 다른 클라이언트 들에게 메세지를 보내준다.

   Simple In Memory Broker는 클라이언트의 SUBSCRIBE 정보를 자체적으로 메모리에 유지한다.

 

- RabbitMQ, ActiveMQ 같은 외부 메세징 시스템을 STOMP Broker로 사용할 수 있도록 지원한다.

   구조적인 면으로, 스프링은 메세지를 외부 Broker에게 전달하고,

   Broker는 WebSocket으로 연결된 클라이언트에게 메세지를 전달하는 구조가 된다. 

 

-> 이와 같은 구조 덕분에 HTTP 기반의 보안 설정 공통된 검증 등을 적용할 수 있게 된다.

 

 

STOMP는 HTTP에서 모델링되는 Frame 기반 프로토콜

Frame은 몇 개의 Text Line으로 지정된 구조로

  첫 번째 라인은 Text, 이후 Key:Value 형태로 Header의 정보를 포함한다.

  다음 빈라인을 추가하고 payload가 존재한다.

COMMAND
header1:value
header2:value

Body^@

HTTP 요청과 구조가 유사

 

- COMMAND : SEND, SUBSCRIBE를 지시할 수 있다.

- header : 기존의 WebSocket으로는 표현이 불가능한 header를 작성할 수 있다.

    · destination : 이 헤더로 메세지를 보내거나(SEND), 구독(SUBSCRIBE)할 수 있다.

 

destination은 의도적으로 정보를 불분명하게 정의함. 이는 STOMP 구현체에서 문자열 구문에 따라 직접 의미를 부여하도록 하기 위함이다.

 

따라서 destination 정보는 STOMP 서버 구현체마다 달라질 수 있기 때문에 각 구현체의 스펙을 살펴봐야 함.

 

일반적으로는 아래와 같은 형식을 따름

"topic/.." ==> publish-subscibe(1:N)
"queue/" ==> point-to-point(1:1)

 

 

ex) clientA가 5번 채팅방에구독하는예시

SUBSCRIBE
destination: /topic/chat/room/5
id: sub-1

^@

 

ex) clientB에서 채팅 메세지를 보내는 예시

SEND
destination: pub/chat
content-type: application/json

{"chatRoomId":5, "type":"MESSAGE","writer":":clientB"}^@

 

 

STOMP 서버는 모든 구독자에게 메세지를 BroadCasting하기 위해 MESSAGE COMMAND를 사용할 수 있다.

서버는 내용을 기반(chatRoomId)으로 메세지를 전송할 broker에게 전달한다. (topic을 sub로 봐도 될 것 같음)

MESSAGE
destination: topic/chat/room/5
message-id: d4c0d7f6-1
subscription: sub-1

 

 

서버는 불분명한 메세지를 보낼 수 없다.

그러므로 서버의 모든 메세지는 특정 클라이언트 구독에 응답해야하고,

서버 메세지의 "subscription-id" 헤더클라이언트 구독의 "id"헤더와 일치해야 함.

 

Client Frames

SEND

SEND frame은 destionation의 메세징 시스템으로 메세지를 보낸다.

header : 필수인 destination(어디로 보낼지)

body : 보내고자 하는 메세지

SEND
destination: /queue/a
content-type: text/plain

hello queue a
^@

- SEND frame은 body가 있는 경우 "content-length"와 "content-type" 헤더를 반드시 가져야 한다.

 

SUBSCRIBE

SUBSCRIBE frame은 주어진 destination에 등록(구독)하기 위해 사용된다.

SEND frame과 마찬가지로 client가 구독을 원하는 목적지를 가리키는 destination 헤더를 필요로 한다.

가입된 대상에서 수신된 모든 메세지는 이후 MESSAGE frame으로 서버에서 클라이언트에게 전달된다.

 

header : destination(필수)

SUBSCRIBE
id : 0
destination: /queue/foo
ack: client

^@

- 단일 연결은 여러 개의 구독을 할 수 있으므로 구독 ID를 고유하게 식별하기 위해 "id"헤더가 프레임에 포함되어야 한다.

 

이외의 기능

- UNSUBSCRIBE

- BEGIN

- COMMIT

- ABORT

- ACK

- NACK

- DISCONNECT

https://stomp.github.io/stomp-specification-1.2.html#Frames_and_Headers

 

https://stomp.github.io/stomp-specification-1.2.html#Frames_and_Headers

STOMP Protocol Specification, Version 1.2 Abstract STOMP is a simple interoperable protocol designed for asynchronous message passing between clients via mediating servers. It defines a text based wire-format for messages passed between these clients and s

stomp.github.io

 


STOMP의 이점

Spring framework,Spring Security는 STOMP를 사용하여 Websocket만 사용할 때보다 더 다채로운 모델링을 가능하게 함.

 - Messaging Protocol을 만들고 메세지 형식을 커스터마이징 할 필요가 없다.

 

 - RabbitMQ, ActiveMQ같은 Message Broker를 이용해 Subscription(구독)을 관리하고 메세지를 브로드캐스팅 할 수 있다.

 

 - WebSocket 기반으로 각 Connection(연결)마다 WebSocketHandler를 구현하는 것보다 @Controller된 객체를 이용해 조직적으로 관리할 수 있다.

  · 메세지는 STOMP의 "destination" 헤더를 기반으로 @Controller 객체의 @MethodMapping 메서드로 라우팅 된다 !

 

- STOMP의 "destination" 및 Message Type를 기반으로 메세지를 보호하기 위해 Spring Security를 사용할 수 있다.

 

 


STOMP 사용

Spring은 WebSocket / SocketJS를 기반으로 STOMP를 위해 spring-messaging과 spring-websocket 모듈을 제공.

기본적으로 커넥션을 위한 STOMP Endpoint를 설정해야 함.

 

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
	// sockJS Fallback을 이용해 노출할 endpoint 설정
	@Override
    public vboid registerStompEndpoints(StompEndpointRegistry registry) {
    	//웹 소켓이 handshake를 하기 위해 연결하는 endpoint
    	registry.addEndpoint("/example").withSockJs();
    }
    
    //메세지 브로커에 관한 설정
    @Override
    public void configuremessageBroker(MessageBrokerRegistry config) {
    	//클라이언트 -> 서버로 발행하는 메세지에 대한 endpoint 설정
          : 구독에 대한 메세지
    	config.setApplicationDestinationPrefixes("/test");
        
        // 서버 -> 클라이언트로 발행하는 메세지에 대한 endpoint 설정
            : 구독
        config.enableSimpleBroker("/topic", "/queue");
    }
}

- /example은 websocket 또는 sockJS client가 웹 소켓 핸드쉐이크 커넥션을 생성할 경로.

 

-/test 경로로 시작하는 STOMP 메세지의 destination 헤더는 @Controller 객체의 @MessageMapping 메서드로 라우팅 됨.

 

- 내장된 메세지 브로커를 사용해 Client에게 Subscriptions, Broadcasting 기능을 제공.

 또한 /topic, /queue로 시작하는 "destination" 헤더를 가진 메세지를 브로커로 라우팅한다.

 

* 내장된 Simple Message Broker는 /topic, /queue prefix에 대해 특별한 의미를 부여하지 않는다.

* SockJS를 사용할 경우, 클라이언트에서 WebSocket 요청을 보낼 때 설정한 엔드포인트 뒤에 /webSocket를 추가해줘야 정상 작동된다.

 

SockJS로 브라우저에 연결하기 위해 sockjs-client를 이용할 수 있다.

STOMP에 있어 많은 어플리케이션들이 jmesnil/stomp-websocket(stomp.js로 알려진) 라이브러리를 사용했지만, 더이상 유지되지 않음.

 

최근에는 JSteunou/webstomp-client를 많이 사용한다.

var sock = new SockJS("/ws/chat");
var stomp = webstomp.over(sock);

stomp.connect({}, function(frame) {
}

/* WebSocket만 이용할 경우 */

var websocket = new WebSocket("/ws/chat");
var stomp = webstomp.over(websocket);

stomp.connect({}, function(frame) {
}

 

 


Message의 흐름

STOMP Endpoint가 노출되고 나면, Spring 어플리케이션은 연결돼있는 client 들에 대해 STOMP 브로커가 된다.

(/app == /pub, /topic == /sub) 아래 그림은 내장 메세지 브로커를 사용한 경우의 컴포넌트 구성

 

 

spring-message 모듈은 Spring framework의 통합된 Messaging 어플리케이션을 위한 지원을 함.

 - Message : headers와 payload를 포함하는 메세지의 표현

 - MessageHandler : Message 처리에 대한 계약

 - SimpleAnnotationMethod : @MessageMapping등 client의 SEND를 받아서 처리.

 - SimpleBroker : Client의 정보를 메모리 상에 들고 있으며, Client로 메세지를 보낸다.

 - channel 

    · clientInboundChannel : WebSocket client로부터 들어오는 요청을 전달하며, WebSocketMessageBrokerConfigurer를 통해 intercept, taskExecutor를 설정할 수 있다.

       -> 클라이언트로 받은 메세지를 전달

 

    · clientOutboundChannel : WebSocket Client로 Server의 메세지를 내보내며, WebSocketMessageBrokerConfigurer를 통해 intercept, tackExecutor를 설정할 수 있다.

       -> 클라이언트에게 메세지를 전달

 

    · brokerChannel : Server 내부에서 사용하는 채널이며, 이를 통해 SimpleAnnotationmethod는 SimpleBroker의 존재를 직접 알지 못 해도 메세지를 전달할 수 있다.

       -> 서버의 어플리케이션 코드 내에서 브로커에게 메세지를 전달


STOMP 적용

1. 의존성 주입

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

 

2. WebSocketConfig -> StompWebSocketConfig

WebSocketConfigurer를 구현한 WebSocketConfig 설정파일을 WebSocketMessageBrokerConfigurer를 구현한 StompWebSocketConfig로 변경한다.

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@EnableWebSocketMessageBroker
@Configuration
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {
	@Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
    	registry.addEndpoint("/stomp/chat")
        	.setAllowedOrigins("http://localhost:8080")
            .withSockJS();
    }
    
    //어플리케이션 내부에서 사용할 path 지정
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    	registry.setApplicationDestinationPrefixes("/pub");
        registry.enableSimpleBroker("/sub");
    }
}

* 이 부분 구현시 setAllowedOrigins("*")로 변경해서 테스트

 

▶ @EnableWebSocketMessageBroker

 : Stomp를 사용하기 위해 선언하는 어노테이션

 

  setApplicationDestinationPrefixes : Client에서 SEND 요청을 처리

 - Spring docs에서는 /topic, /queue로 나오지만 /pub, /sub로 변경

 

  enableSimpleBroker

 : 해당 경로로 SimpleBroker를 등록/. SinmpleBroker는 해당하는 경로를 SUBSCRIBE하는 Client에게 메세지를 전달하는 간단한 작업 수행

 

  enableStompBrokerRelay

 : SimpleBroker의 기능과 외부 Message Broker(RabbitMQ, ActiveMQ 등)에 메세지를 전달하는 기능을 가짐.

'공부' 카테고리의 다른 글

CI / CD 기본 개념  (0) 2024.01.19
즉시로딩과 지연로딩  (0) 2024.01.04
Websocket 프로토콜  (1) 2024.01.01
data.sql 실행 안 될 때  (0) 2023.12.15
동시성(Concurrency) 이슈  (0) 2023.12.13