이번엔 저수준 API에 이어서 고수준 STOMP API에 대해서 공부해보려고 한다. 대부분의 경우 순수 websocket만 이용하지 않고 stomp와 함께 사용하니까 stomp에서 제공하는게 뭐가 있는지 알아보면 좋을 것 같다.
STOMP API는 WebSocket 위에서 동작하는 텍스트 기반의 메시징 프로토콜로 클라이언트와 서버 간의 메시지 교환을 위한 규격을 정의한다. STOMP를 사용하면 많은 부분을 프레임워크에서 처리해주기 때문에 순수 websocket을 사용해서 개발자가 구현해줘야 하는 부분을 상당히 줄일 수 있다.
이런 메시지 명령어들이 STOMP API를 통해서 자동으로 처리되기 때문에 개발자는 더 높은 수준의 추상화를 통해서 메시징 로직에 집중할 수 있는 것이다.
WebSocketMessageBrokerConfigurer
인터페이스public interface WebSocketMessageBrokerConfigurer {
default void configureMessageBroker(MessageBrokerRegistry registry) {}
default void registerStompEndpoints(StompEndpointRegistry registry) {}
default void configureClientInboundChannel(ChannelRegistration registration) {}
default void configureClientOutboundChannel(ChannelRegistration registration) {}
default void configureWebSocketTransport(WebSocketTransportRegistration registry) {}
default boolean configureMessageConverters(List<MessageConverter> messageConverters) { return true; }
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {}
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {}
}
이 인터페이스는 STOMP의 설정 파일을 작성하기 위해 사용되는 인터페이스이다.
SimpMessageSendingOperations
/SimpMessagingTemplate
인터페이스public interface SimpMessageSendingOperations {
void send(String destination, Object payload);
void convertAndSend(String destination, Object payload);
void convertAndSendToUser(String user, String destination, Object payload);
// 기타 메서드...
}
위의 인터페이스를 통해서 메시지를 전달할 수 있다. 구현체는 SimpMessagingTemplate
이 클래스를 사용하면 된다.
MessageHandler
인터페이스public interface MessageHandler {
void handleMessage(Message<?> message) throws MessagingException;
}
이 헨들러는 메시지를 처리할 때 사용하는 헨들러다 다른 메시지 통합처리를 할 경우 사용되는데 여러 MessageHandler
인스턴스를 연결해서 순차적으로 처리하는 복합 헨들러 MessageHandlerChain
을 구현할 수 있다.
ChannelInterceptor
인터페이스public interface ChannelInterceptor {
Message<?> preSend(Message<?> message, MessageChannel channel);
void postSend(Message<?> message, MessageChannel channel, boolean sent);
void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex);
boolean preReceive(MessageChannel channel);
Message<?> postReceive(Message<?> message, MessageChannel channel);
void afterReceiveCompletion(Message<?> message, MessageChannel channel, Exception ex);
}
이 인터페이스는 메시지 채널의 전송 과정을 가로채는 인터셉터 인터페이스이다. 이 인터페이스를 사용해서 JWT 토큰 검증을 구현하면 된다.
아키텍처에서 인터셉터가 가로채는 순간은 위와 같다. 3가지 구간에서 인터셉터를 가로챌 수 있고 각 구간에 등록하기 위해서는
요런 설정에 인터셉터를 등록해주면 된다.
메시지를 전달하는 방식은 2가지가 있다. controller에서 어노테이션을 사용하는 방식, SimpMessagingTemplate
를 사용해서 직접 전달하는 방식 이렇게 존재하고 필요에 맞게 사용하면 된다.
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
SimpMessagingTemplate
public void notifyUsers(String message) {
messagingTemplate.convertAndSend("/topic/notifications", message);
}
메시지를 전달할 때 넘길 수 있는 매개변수는 다양한데 인증된 사용자, 경로, 메시지 페이로드등 제공하는 것들을 활용해서 넘겨주면 된다.
@MessageMapping("/advanced")
public void handleAdvanced(
@Payload CustomMessage message, // 메시지 페이로드
@Header("customHeader") String header, // 특정 헤더 값
@Headers Map<String, Object> headers, // 모든 헤더
MessageHeaders messageHeaders, // 메시지 헤더 객체
Principal principal, // 인증된 사용자
@DestinationVariable String id // 경로 변수
) {
// 처리 로직
}
대규모의 경우 내부 메시지 브로커를 사용한다면 메시지 데이터가 일관성이 생기지 않을 것이다. 그래서 외부에서 공통으로 관리하는 외부 메시지 브로커인 RabbitMQ나 ActiveMQ와 같은 메시지 브로커 서버를 추가로 사용해야 될 것이다. 그럼 연결은 어떻게 하지?
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 외부 메시지 브로커 연동
registry.enableStompBrokerRelay("/topic", "/queue")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest")
.setSystemLogin("guest")
.setSystemPasscode("guest")
.setSystemHeartbeatSendInterval(5000)
.setSystemHeartbeatReceiveInterval(5000);
registry.setApplicationDestinationPrefixes("/app");
}
본인의 설정에 맞게 작성하면 된다.
STOMP에서 제공해주는 API들을 확인해 봤는데 stomp를 사용하면 기존에 websocket을 사용해서 구현하는 부분을 상당히 줄여주는 걸 알게 됬다. 기본적인 구현을 stomp 프레임워크에서 처리하기 때문에 우리는 그 동작 원리만 알면 더욱 간편하게 사용할 수 있다.