HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🤩
개발
/
Spring
Spring
/
STOMP

STOMP

Configure Spring for STMOP messaging

package com.lguplus.chumi.common.config import com.lguplus.chumi.common.message.websocket.StompEventHandler import org.springframework.context.annotation.Configuration import org.springframework.messaging.simp.config.ChannelRegistration import org.springframework.messaging.simp.config.MessageBrokerRegistry import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker import org.springframework.web.socket.config.annotation.StompEndpointRegistry import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration @Configuration @EnableWebSocketMessageBroker class StompWebSocketConfig( private val corsProperties: CorsProperties, private val stompEventHandler: StompEventHandler, ) : WebSocketMessageBrokerConfigurer { override fun configureMessageBroker(registry: MessageBrokerRegistry) { registry .setPreservePublishOrder(true) .setApplicationDestinationPrefixes("/app") // pub .enableSimpleBroker("/topic") // sub .setTaskScheduler(taskScheduler()) .setHeartbeatValue(longArrayOf(50000, 50000)) // inbound, outbound (50s) } override fun registerStompEndpoints(registry: StompEndpointRegistry) { registry .addEndpoint("/ws") .setAllowedOrigins(*corsProperties.allowedOrigins.toTypedArray()) } override fun configureClientInboundChannel(registration: ChannelRegistration) { registration.interceptors(stompEventHandler) } // STOMP 에서 64KB 이상의 데이터 전송을 못하는 문제 해결 override fun configureWebSocketTransport(registration: WebSocketTransportRegistration) { registration .setMessageSizeLimit(160 * 64 * 1024) .setSendTimeLimit(100 * 10000) .setSendBufferSizeLimit(3 * 512 * 1024) } private fun taskScheduler(): ThreadPoolTaskScheduler { val taskScheduler = ThreadPoolTaskScheduler() taskScheduler.initialize() return taskScheduler } }
  • @EnableWebSocketMessageBroker : WebSocket 메시지 handling을 가능케 함, backed by message broker
  • configureMessageBroker() :
    • enableSimpleBroker() : 단순한 메모리 기반의 메시지 브로커를 작동시킴. destination 의 prefix가 /topic 인 경우에 대해 message를 다시 클라이언트로 돌려보냄
    • setApplicationDestinationPrefixes() : /app 으로 시작하는 메시지는 @MessageMapping 으로 bound 시킴
      • @Controller public class GreetingController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); } }
      • /app/hello 엔드포인트는  GreetingController.greeting() 메서드로 매핑되어 작동하게 됨
      • @SendTo : /topic/greetings 를 구독하고 있는 모든 구독자에게 response를 보내게 됨

STOMP Client와 Server 사이 Sample 코드

const stompClient = new StompJs.Client({ brokerURL: 'ws://localhost:8081/ws' }); stompClient.onConnect = (frame) => { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings/greetingId', (greeting) => { showGreeting(greeting.body); }); }; stompClient.onWebSocketError = (error) => { console.error('Error with websocket', error); }; stompClient.onStompError = (frame) => { console.error('Broker reported error: ' + frame.headers['message']); console.error('Additional details: ' + frame.body); }; function setConnected(connected) { $("#connect").prop("disabled", connected); $("#disconnect").prop("disabled", !connected); if (connected) { $("#conversation").show(); } else { $("#conversation").hide(); } $("#greetings").html(""); } function connect() { stompClient.connectHeaders = { "Test-Header": "test-value" } stompClient.activate(); } function disconnect() { stompClient.deactivate(); setConnected(false); console.log("Disconnected"); } function sendName() { stompClient.publish({ destination: "/app/agent/ping", body: $("#name").val() }); } function showGreeting(message) { $("#greetings").append("<tr><td>" + message + "</td></tr>"); } $(function () { $("form").on('submit', (e) => e.preventDefault()); $( "#connect" ).click(() => connect()); $( "#disconnect" ).click(() => disconnect()); $( "#send" ).click(() => sendName()); });
@Controller @MessageMapping("/agent") class AgentMessageController( // TODO: 사용 여부 확인 후 제거 private val messagingTemplate: SimpMessagingTemplate, ) { private val log = logger() /** *! /app/agent */ @MessageMapping("/ping") @SendTo("/topic/greetings/greetingId") private fun ping(agentId: String): String { val pongMessage = SanrioMessage(AgentPongStompMessage(agentId = agentId)) messagingTemplate.convertAndSend(pongMessage.data.endpoint, pongMessage) log.info("Sent agent pong. User ID: ${pongMessage.data.agentId}") return agentId } }