본문 바로가기
Skills/Springboot Chat

Spring boot 채팅 구현 #4 STOMP 기반 채팅 구현

by Hoseok 2023. 6. 15.
728x90
반응형

 

 

STOMP 기반 채팅 구현

 

우선 build.gradle에 아래와 같은 의존성을 추가해준다.

 

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

 

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-websocket'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

코드는 단순하게 구성하였다.

 

설정 파일인 WebSocketConfig를 만들고,

 

메세지 객체로 쓰일 Message를 만든다.

 

그리고 ChatController를 통해 라우팅을 해서 Message를 전달할 것이다.

 

WebSocketConfig

package com.example.stomp.config;

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 registry) {
        registry.enableSimpleBroker("/sub");
        registry.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 테스트할 때만 setAllowedOrigins("*"); 허용
        registry.addEndpoint("/ws").setAllowedOrigins("*");
    }
}

 

WebSocketConfig를 만들 때, 아래의 두 가지 메서드를 오버라이딩해야 한다.

 

 

코드를 설명하자면,

 

@Configuration을 붙여서 스프링 컨테이너에 등록하고, 설정 파일임을 알릴 수 있다.

 

@EnableWebSocketMessageBroker를 붙이면 메세지 브로커를 사용해서 메세지 핸들링이 가능해진다.

 

configureMessageBroker는 메세지 브로커를 설정하는 메서드이다.

 

enableSimpleBroker는 "/sub"를 구독한 클라이언트에게 메세지를 발신함을 정의하고,

 

setApplicationDestinationPrefixes는 클라이언트가 메세지를 발행할 때, 브로커의 목적지에 접두사를 정의하는 것이다.

 

그리고 @MessageMapping 어노테이션이 붙은 컨트롤러가 호출되는 식이다.

 

@Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub");
        registry.setApplicationDestinationPrefixes("/pub");
    }

 

registerStompEndpoints는 엔드포인트를 등록하는 메서드이다.

 

/ws가 붙은 엔드포인트로 통신이 이루어질 것이다.

 

현재는 편의상 setAllowedOrigins("*")로 해서 모든 요청을 받아들이도록 했지만,

 

실제 서비스에서는 특정 도메인에서만 허용되게 해야 할 것이다.

 

@Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 테스트할 때만 setAllowedOrigins("*"); 허용
        registry.addEndpoint("/ws").setAllowedOrigins("*");
    }

 

Message

package com.example.stomp.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Message {
    private Long channelId;
    private String sender;
    private String message;
}

 

ChatController

package com.example.stomp.controller;

import com.example.stomp.model.Message;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;

@Controller
@RequiredArgsConstructor
@Slf4j
public class ChatController {

    private final SimpMessageSendingOperations operations;

    // /pub/channel
    @MessageMapping("/channel")
    public void sendMessage(Message message) {
        operations.convertAndSend
                ("/sub/channel/" + message.getChannelId(), message);
        log.info("메세지 전송 성공");
    }
}

 

위에서 설명했듯이, 

 

setApplicationDestinationPrefixes@MessageMapping이 붙은 메서드를 호출한다.

 

즉 메세지를 발행하게 되면, /pub/channel 로 라우팅되고,

 

/sub/channel/channelId를 구독하는 클라이언트로 메세지 객체가 전달된다.

 

그리고 메세지가 잘 전송되었는지 로그도 찍어보자.

 

 


 

테스트

 

테스트는 apic이라는 툴을 사용할 것이다.

 

그동안 RestAPI를 만들 때, Postman을 주로 사용했지만,

 

Postman은 STOMP가 지원되지 않는다. 

 

https://apic.app/

 

apic — The complete API solution

The complete API solution APIC provides an end to end solution for APIs, staring from design to documentation to testing. With a simplistic UI for Designing APIs and a feature rich platform for testing them, APIC provides a common platform for your designe

apic.app

 

apic을 설치하고, 아래와 같이 설정해주자.

 

url은 ws://localhost8080/ws 이다.

 

ws는 http처럼 websocket 통신을 나타내는 의미이다. 

 

보안성을 보장하는 https처럼 wss도 존재한다. 실제 서비스에서는 wss를 사용하자.

 

그리고 stomp를 선택해주고 오른쪽 상단에 connect를 누르자.

 

정상적으로 커넥션이 연결되면 Stomp connected라고 뜬다.

 

*참고로 발행하는 쪽과 구독하는 쪽을 둘다 테스트하기 위해 창을 2개 띄우자.

 

 

 

그리고 스프링 서버로 돌아와서 로그를 확인해보자.

 

 

2개의 WebSocket 세션이 생긴 것을 확인할 수 있다.

 

 

커넥션이 잘 되었는지에 대한 정보.

 

 

stomp 프로토콜로 2개의 통신이 연결되었다.

 

 

외부 메세지 브로커가 없으므로, stompBrokerRelay는 null이다.

 

 

인바운드 채널에 대한 정보.

 

 

아웃바운드 채널에 대한 정보.

 

자, 이제 메세지를 보내보자.

 

 

Publisher

 

/pub/channel로 메세지 브로커에게 메세지를 전달할 것이다. 

 

메세지 객체에 channelId를 넣어놓았으므로, 

 

/sub/channel/1을 구독하는 클라이언트에게 최종적으로 메세지를 전달할 것이다.

 

send 버튼을 누르자.

 

 

 

Subscriber

 

/sub/channel/1을 구독하고 있는 클라이언트에게 메세지가 전달된 것을 확인할 수 있다.

 

 

그리고 다시 우리 스프링 서버로 돌아와서 로그를 확인해보면,

 

메세지 전송 성공이라고 로그가 잘 찍혀있다.

 

 

로그를 확인하다 보니, 메세지를 보낼때마다, boundChannel이 달라지는 것을 확인할 수 있었다.

 

아마 메세지가 메세지 브로커에 전달될 때마다, 매번 새로운 내부 채널을 통해서 라우팅되는 방식으로 추정해본다.

 

 

 


 

 

Reference

 

https://brunch.co.kr/@springboot/695

 

Spring Websocket & STOMP

오랫만에 작성하는 기술블로그 포스팅입니다. 이 글에서는 스프링 부트 기반의 웹소켓 및 STOMP에 대해서 설명합니다. 이 글을 읽기 위해서는 기본적인 HTTP 지식이 있어야 하며, 스프링 프레임워

brunch.co.kr

 

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

 

다음 편에서는 내부 메세지 브로커를 사용한 현재 방식에서,

 

RabbitMQ를 사용한 외부 메세지 브로커 방식으로 구현해보겠습니다.

728x90
반응형