ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 웹소켓 애플리케이션을 위한 Spring Boot 구성
    개발 일지/Back-end 2023. 5. 17. 15:27
    반응형

    웹소켓 애플리케이션의 대표적인 사례는 채팅이라고 할 수 있습니다.

     

    하지만 그 외에도 현대 애플리케이션의 여러 웹 서비스 중에서는 특정 조건에 따라 서버에서 클라이언트(사용자)에게 서비스를 제공하기도 합니다.

    (사용자가 서버에 서비스를 요청하지 않더라도요.)

     

    우리 일상에서 보면 모바일 앱에서 푸시 알림을 떠올려볼 수 있겠죠.


    웹 기술로 보면 "pub/sub"을 생각하면 될 것 같습니다.

    일정 토픽(주제)에 대해 '구독'설정을 해두면, 서버에서 사용자 방향으로 '발행' 서비스를 수행할 수 있습니다.


    자, 이제 Spring.io 의 튜토리얼을 참고해서 Spring Boot 프로젝트를 준비해 보겠습니다.

    https://spring.io/guides/gs/messaging-stomp-websocket/

     

    Getting Started | Using WebSocket to build an interactive web application

    In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController (from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) is mapped to handle messages t

    spring.io

     

    우선, 의존성을 추가해줘야 합니다.

    저는 Gradle 빌드 툴을 사용할 것이기에 'build.gradle'에 의존성을 선언해두겠습니다.

     

    Front-end는 Next.js 프로젝트로 구성할 예정이기에 Spring Boot 프로젝트('build.gradle')에는

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

    'org.springframework.boot:spring-boot-starter-websocket' 만 추가하면 됩니다.

     

     

    다음으로 '메시지' 관련 클래스와 컨트롤러를 생성하기 전에 구성 컴포넌트부터 만들어보겠습니다.

    (튜토리얼 순서와 반대)

     

    WebSocketCofing - Configure Spring for STOMP messaging

    WebSocketConfig를 통해 웹소켓 및 STOMP 메세징을 사용할 수 있도록 설정합니다.

    package com.example.messagingstompwebsocket;
    
    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;
    
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
      @Override
      public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
      }
    
      @Override
      public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/my-websocket").withSockJS();
      }
    
    }

    @Configuration 어노테이션을 통해 '스프링 구성 클래스'라는 것을 명시합니다.

    @EnableWebSocketMessageBroker는 메시지 브로커가 지원하는 WebSocket 메시지 처리를 활성화합니다.

     

    configureMessageBroker() 메서드는 WebSocketMessageBrokerConfigurer의 기본 메서드를 구현하여 메시지 브로커를 구성합니다.

    간단한 메모리 기반 메시지 브로커가 인사말 메시지를 /topic 접두사가 있는 대상의 클라이언트로 다시 전달할 수 있도록 enableSimpleBroker()를 호출하여 시작합니다.

    또한 @MessageMapping으로 어노테이션이 달린 메서드에 바인딩된 메시지의 '/app' prefix를 지정합니다.

     

    registerStompEndpoints() 메서드는 '/my-websocket' end-point를 등록하여 WebSocket을 사용할 수 없는 경우 대체 전송을 사용할 수 있도록 SockJS 폴백 옵션을 활성화합니다.

     


    다시 "채팅" 서비스를 생각해 볼까요.

     

    해당 서비스에서는 2명의 사용자가 있습니다. (물론 그 이상이 될 수도 있죠.)

    A라는 사람이 "안녕(hello)"라는 메시지를 보냅니다.

     

    그러면 A의 메시지가 서버에 도달하고 서버는 특정 사용자(또는 채팅 방/채널에 브로드캐스트)에게 전달합니다.

    앞서 표현한 것처럼 "구독/발행" 관점에서도 생각해 볼 수 있겠죠.

     

    아래는 '메시지'를 처리하는 컨트롤러 코드입니다.

    package com.example.messagingstompwebsocket;
    
    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.handler.annotation.SendTo;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.util.HtmlUtils;
    
    @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()) + "!");
      }
    
    }

    쉽게 생각해 보시면 금방 이해하실 수 있습니다.

     

    @MessageMapping("/hello") 어노테이션은 앞의 설정으로 인해 '사용자 A'가 보낸 "/app/hello" 요청에 해당됩니다.

    이 요청에는 메시지가 담겨 있겠죠. 

    바로 greeting(HelloMessage message) 메소드의 인자로 들어옵니다.

     

    그리고 Thread.sleep(1000)은 1초간 지연을 발생시켜서 웹소켓 통신에 대해 물리적으로 확인가능하게 만든 부분이죠.

     

    그리고 Greeting 클래스에서 생성자를 통해 "Hello, " 텍스트와 함께 HelloMessage message 에 담겨 있는 name을 이어서 리턴합니다.

    리턴한 응답은 @SendTo("/topic/greetings")로 전달됩니다.

    그리고 바로 "/topic/greetings"가 사용자들(현재 예시 시나리오에서)이 구독하고 있는 API 입니다.

     

    메시지 관련 객체 클래스

    1. HelloMessage

    package com.example.messagingstompwebsocket;
    
    public class HelloMessage {
    
      private String name;
    
      public HelloMessage() {
      }
    
      public HelloMessage(String name) {
        this.name = name;
      }
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    }

    2. Greeting

    package com.example.messagingstompwebsocket;
    
    public class Greeting {
    
      private String content;
    
      public Greeting() {
      }
    
      public Greeting(String content) {
        this.content = content;
      }
    
      public String getContent() {
        return content;
      }
    
    }

     


    그럼 다음 포스트에서 클라이언트(사용자: 엔드유저)  웹소켓 구독 관련 부분을 살펴보도록 하겠습니다.

     

    클라이언트의 경우 서버사이드인지 클라이언트사이드(CSR) 방식인지 잘 구분해서 구현해야 합니다.

    Next.js의 경우 웹소켓을 지원하지 않는 것 같고 SSR(서버사이드 렌더링) 방식이라 SockJS가 먹히질 않는군요.

     

    그래서 Next.js 프로젝트에서는 Socket.IO(서버사이드 라이브러리)를 활용해 구현해보겠습니다.

    반응형
Designed by Tistory.