Post

stomp

stomp 개념 정리

stomp

stomp에 대하여

STOMP는 ruby, python 같은 스크립트 기반 언어에서 메세지 브로커에 연결하기 위해 개발되었습니다.
이 프로토콜은 일반적으로 사용되는 메세징 패턴의 일부를 다루도록 설계되었습니다. STOMP는 TCP, WebSocket과 같은 양방향 스트리밍 네트워크 프로토콜을 통해 사용할 수 있습니다. STOMP는 텍스트 지향 프로토콜이지만, 메세지 페이로드는 바이너리 형식일 수도 있습니다.

STOMP는 HTTP에 기반한 frame-based protocol 입니다. 다음은 STOMP 프레임의 예시입니다.

1
2
3
4
5
COMMAND
header1:value1
header2:value2

Body^@

클라이언트는 SEND 혹은 SUBSCRIBE 커맨드와 destination 헤더를 사용해서 어디에 데이터를 보낼지를 명시할 수 있습니다. 이를 통해 브로커를 통해 다른 연결된 클라이언트에게 메세지를 전송하거나 서버에 작업 수행을 요청하는 메세지를 보낼 수 있는 pub/sub 메커니즘이 가능합니다.

Spring의 STOMP support를 사용할 때, Spring Websocket 애플리케이션은 클라이언트에 대한 STOMP 브로커 역할을 합니다. 메세지는 @Controller 메세지 처리 메서드로 라우팅되거나 구독을 추적하고 구독된 사용자에게 메세지를 브로드캐스트하는 간단한 인메모리 브로커로 전달됩니다. 또한 RabbitMQ, ActiveMQ와 같은 STOMP 브로커와 함께 메세지 브로드캐스팅을 수행할 수 있습니다. 이 경우 Spring은 브로커와의 TCP 연결을 유지하고, 메세지를 브로커에 중계하며 브로커로부터 연결된 WebSocket 클라이언트로 메세지를 전달합니다.

다음 예시는 구독 메세지의 예시입니다. 서버는 SimpMessagingTemplate을 통해 브로커에 메세지를 전송하는 스케줄링된 작업을 등록할 수 있습니다.

1
2
3
4
5
SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@

다음 예시는 클라이언트가 거래 요청을 전송하는 예시이며 서버는 이를 @MessageMapping 어노테이션을 통해 처리할 수 있습니다.

1
2
3
4
5
6
SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@

위 메세지를 처리한 후 서버는 거래 확인 메세지와 세부 정보를 클라이언트에게 브로드캐스할 수 있습니다.

STOMP 사양에서 목적지의 의미는 의도적으로 모호하게 남겨져있습니다. 목적지는 어떤 문자열이든 될 수 있으며, 지원하는 목적지의 의미와 구문을 정의하는 것은 전적으로 STOMP 서버에 달려 있습니다. 그러나 일반적으로 목적지는 경로와 같은 문자열로 /topic/..은 pub,sub을 의미하고,/queue/..는 일대일 메세지 교환을 의미하는 경우가 많습니다.

STOMP 서버는 MESSAGE 명령을 사용하여 모든 구독자에게 메시지를 브로드캐스트할 수 있습니다. 다음 예시는 서버가 구독한 클라이언트에게 주식 시세를 전송하는 모습을 보여줍니다.

1
2
3
4
5
6
MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@

서버는 요청하지 않은 메시지를 전송할 수 없습니다. 서버에서 보내는 모든 메시지는 특정 클라이언트의 구독에 대한 응답이어야 하며, 서버 메시지의 구독 헤더는 클라이언트 구독의 id 헤더와 일치해야 합니다.

stomp의 이점

Spring, Spring Security와 함께 STOMP를web socket의 sub protocol로 사용하는것은 더 풍부한 프로그래밍 모델의 사용을 가능하게 합니다.

  • 메세지 형식과 프로토콜을 별도로 개발하지 않아도됩니다.
  • STOMP Client를 사용 가능합니다.
  • RabbitMQ, ActiveMQ 같은 메세지 브로커를 사용 가능합니다.
  • 애플리케이션 로직은 여러 개의 @Controller 인스턴스로 구성할 수 있으며, 메시지는 STOMP 목적지 헤더에 따라 이들로 라우팅될 수 있습니다. 이는 특정 연결에 대해 단일 WebSocketHandler로 원시 WebSocket 메시지를 처리하는 것과 대조됩니다.
  • Spring Security를 사용하여 STOMP 목적지와 메시지 유형에 따라 메시지를 보호할 수 있습니다.

spring stomp flow of messages

STOMP 엔드포인트를 다음과 같이 선언할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {

	override fun registerStompEndpoints(registry: StompEndpointRegistry) {
		// /portfolio is the HTTP URL for the endpoint to which a WebSocket (or SockJS)
		// client needs to connect for the WebSocket handshake
		registry.addEndpoint("/portfolio")
	}

	override fun configureMessageBroker(config: MessageBrokerRegistry) {
		// STOMP messages whose destination header begins with /app are routed to
		// @MessageMapping methods in @Controller classes
		config.setApplicationDestinationPrefixes("/app")
		// Use the built-in message broker for subscriptions and broadcasting and
		// route messages whose destination header begins with /topic or /queue to the broker
		config.enableSimpleBroker("/topic", "/queue")
	}
}

stompjs 같은 클라이언트 라이브러리로 다음과 같이 연결할 수 있습니다.

1
2
3
4
5
6
const stompClient = new StompJs.Client({
       brokerURL: 'ws://domain.com/portfolio',
       onConnect: () => {
           // ...
       }
   });

간단한 빌트인 메세지 브로커를 구성할 컴포넌트 다이어그램은 다음과 같습니다. https://docs.spring.io/spring-framework/reference/_images/message-flow-simple-broker.png

위 다이어그램은 3가지 메세지 채널을 보여줍니다.

  • clientInboundChannel : 웹 소켓 클라이언트로부터 받은 메세지를 전달하는 채널
  • clientOutboundChannel : 웹 소켓 클라이언트로 메세지를 전달하는 채널
  • brokerChannel : 애플리케이션 코드에서 메세지 브로커로 메세지를 전달하는 채널

외부 브로커를 사용하는 경우 컴포넌트 다이어그램은 다음과 같습니다. https://docs.spring.io/spring-framework/reference/_images/message-flow-broker-relay.png

앞서 언급한 두 개의 다이어그램 간의 주요 차이점은 TCP를 통해 외부 STOMP 브로커로 메시지를 전달하고, 브로커에서 구독된 클라이언트로 메시지를 전달하기 위한 “브로커 릴레이”의 사용입니다.

웹 소켓 커넥션을 통해 메세지를 전달 받으면, 메세지는 STOMP 프레임으로 디코딩되고, 프레임은 Spring Message로 이후 처리를 위해 clientInBoundChannel로 전송됩니다. 예를들어, destination header가 /app로 시작하는 STOMP 메세지는 @MessageMapping 어노테이션이 포함된 컨트롤러 메소드로 라우팅되고, /topic, /queue로 시작하는 메세지들은 곧 바로 브로커로 라우팅됩니다.

클라이언트로부터의 STOMP 메시지를 처리하는 주석이 달린 @Controller는 brokerChannel을 통해 메시지 브로커에 메시지를 전송할 수 있으며, 브로커는 해당 메시지를 clientOutboundChannel을 통해 일치하는 구독자에게 브로드캐스트합니다. 동일한 컨트롤러는 HTTP 요청에 대한 응답으로도 같은 작업을 수행할 수 있으므로, 클라이언트가 HTTP POST를 수행하면 @PostMapping 메서드가 메시지 브로커에 메시지를 전송하여 구독된 클라이언트에게 브로드캐스트할 수 있습니다.

This post is licensed under CC BY 4.0 by the author.