본문 바로가기
프로젝트

웹소켓 STOMP로 채팅구현하기 (with Spring) +웹소켓 테스트 사이트 추천

by 더하리 2024. 11. 17.

 

웹소켓 구현

어떤 프로토콜 활용?

일단 지난 포스팅에서 언급했던 다양한 소켓 종류 중에서 STOMP방식을 결합하기로 했다.

STOMP(Simple Text Oriented Message Protocol)는 기존의 WebSocket 통신 방식을 더 효율적으로 만들어주며 보다 쉽게 다룰 수 있도록 하는 프로토콜로 pub, sub개념이 등장하는 것이 특징이다. 

여기에서 클라이언트가 서버로 메시지를 보내는 것(발행한다)을 pub(publish),
클라이언트가 서버로부터 메시지를 받는 것(구독한다)을 sub(subscribe)라는 개념이 사용된다.

 

 

구현과정을 살펴보자

build.gradle설정

 웹소켓과 stomp 관련 의존성을 추가

implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:stomp-websocket:2.3.4'

 

WebConfig 파일 

WebConfig는 CORS설정을 진행했다.

import ...

@Configuration
public class WebConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**") // 모든 경로에 대해 CORS를 허용
                .allowedOriginPatterns("*"); // 모든 도메인에서 오는 요청을 허용 (허용할 출처를 명시적을 설정하는 것이 좋음)
            }
        };
    }
}

 

 

WebSocket 파일

소켓 연결과 관련된 설정과 STOMP 사용을 위한 Message Broker 설정을 해준다.

import ...
@Configuration
@EnableWebSocketMessageBroker
public class WebSocket implements WebSocketMessageBrokerConfigurer {

	// 클라이언트가 웹소켓 서버에 연결하는데 사용할 소켓 연결 uri
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat") // 접속 주소: ws://(도메인주소)/chat
                .setAllowedOriginPatterns("*"); //소켓 또한 CORS설정을 해주어야 함
    }
    
	// 한 클라이언트에서 다른 클라이언트로 메시지를 라우팅하는데 사용될 메시지 브로커
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
    	// 메세지를 받을 때, 경로를 설정해주는 함수
        registry.enableSimpleBroker("/topic"); // 구독경로
        // 메세지를 보낼 때, 관련 경로를 설정해주는 함수
        registry.setApplicationDestinationPrefixes("/app"); // 발행경로
    }
}

 

ChattingController 파일

@MessageMapping을 통해 WebSocket으로 들어오는 메세지 발행을 처리한다.

매핑이 현재 "/chat/{chatRoomId}"로 되어있는데 앞서 WebSocket파일에서 setApplicationDestinationPrefixes()을 통해 prefix를 "/app"으로 설정을 해주었기 때문에 "/app /chat/{chatRoomId}"를 의미하는 것이 된다.

 

import ...
@RestController
@RequiredArgsConstructor
public class ChattingController {
    private final ChattingService chattingService;

    // 채팅기능
    @MessageMapping("/chat/{chatRoomId}")
    @SendTo("/topic/chat/{chatRoomId}")
    public ChatMessageResponse sendChatMessage(
            @DestinationVariable Long chatRoomId,
            @Payload ChatMessageRequest requestMessage,
            StompHeaderAccessor headerAccessor) {
        Long userPk = Long.valueOf(headerAccessor.getFirstNativeHeader("Authorization"));

        ChatMessageDetailResponse response =
                chattingService.sendMessage(chatRoomId, requestMessage.message(), userPk);

        return new ChatMessageResponse("success", response, null);
    }
}

 

ChattingService 파일

import ...
@Service
@RequiredArgsConstructor
public class ChattingService {
    private final ChattingRepository chattingRepository;
    private final ChatLogRepository chatLogRepository;
    private final WorkspaceRepository workspaceRepository;
    private final UserRepository userRepository;
    private final ChatlogService chatlogService;

    @Transactional
    public ChatMessageDetailResponse sendMessage(Long chatRoomId, String message, Long userPk) {

        Workspace workspace =
                workspaceRepository
                        .findById(chatRoomId)
                        .orElseThrow(() -> ChatRoomNotFoundException.EXCEPTION);
        User user =
                userRepository.findById(userPk).orElseThrow(() -> UserNotFoundException.EXCEPTION);

        Chatting chatMessage =
                Chatting.builder().workspace(workspace).sender(user).content(message).build();

        chattingRepository.save(chatMessage);

        ChatMessageDetailResponse detailResponse =
                new ChatMessageDetailResponse(message, user.getUid(), chatMessage.getCreatedAt());
        return detailResponse;
    }
}

 

ChattingRepository 파일

import ...
public interface ChattingRepository extends JpaRepository<Chatting, Long> {
    @Query("select c from Chatting c where c.workspace.id =:workspaceId")
    List<Chatting> findByWorkspaceId(Long workspaceId);
}

 


 

채팅을 구현해놓고 이를 확인할 수 있는 테스트 사이트를 찾던 와중에 가장 사람들이 많이 사용하던 apic이라는 웹소켓 에스트 사이트를 발견하였지만..현재는 들어가지지 않는 상태!

 

해서 아래 사이트를 활용하였다. WebSocket Debug Tool

 

WebSocket Debug Tool

 

jiangxy.github.io

 

처음에 들어갔을 때 사용하는 방법을 잘 몰랐기 때문에 아래 사진으로 입력해야하는 정보를 넣어보았다.

 

 

 

 

아래 그림은 실제 테스트를 해보고 캡쳐한 사진이다.

DB에도 잘 들어간 것을 확인했고 기능은 성공적으로 마무리된 것 같다..! 

DB Chatting테이블에 채팅 목록 저장 확인

 

 

사실 채팅을 구현하면서 가장 고민이 됐던 부분은 erd설계 부분이었는데 메세지 안읽은 개수, 채팅방 속 메세지 안읽은 사람의 수 등과 관련하여 머리가 복잡복잡했던 것 같다! 결국 이번 term에선 채팅방 속 readBit는 구현하지 않고 넘어갔지만 다음 텀에선 도입해보려 한다. 다음은 erd~?

 

 

 

*참고 자료*

 

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

 

 

[Spring Boot] STOMP를 이용한 실시간 채팅 및 채팅방 동적 생성

프로젝트 속 구현한 실시간 채팅은 다음과 같이 동작한다.STOMP(Simple Text Oriented Message Protocol)는 기존 WebSocket 통신 방식을 좀 더 효율적으로, 조금 더 쉽게 다룰 수 있게 해주는 프로토콜이다.이 프

velog.io

 

 

[채팅 서버] Springboot + STOMP를 활용한 채팅 구현

이번에 진행하는 프로젝트에서 채팅 서버 관련 개발을 담당하게 되어 학습한 내용을 정리한 포스팅입니다. 개발 환경 SpringBoot : 3.2.2 JDK : 17 Dependency dependencies { implementation 'org.springframework.boot:spri

infinitecode.tistory.com

 

 

[Spring][WebSocket] 스프링 STOMP 사용해서 채팅 구현하기

멋쟁이 사자처럼 - SWU에서 진행하는 프로젝트인 'UniLearn(유니런)'이 개발 착수합니다. 11월 6일에 비대면으로 프로젝트 보고회하기 전까지 휴학생 팀원 한 분이 jwt 로 인증,인가구현을 통해 회원

1son.tistory.com

 

 

[Spring] WebSocket으로 채팅 구현하기 - STOMP를 이용한 채팅 고도화

지난 시간에 이어 WebSocket을 사용한 채팅 기능 구현을 STOMP를 이용하여 고도화 시키도록 하겠습니다. > 이전 포스팅 - 일반적인 WebSocket 사용 STOMP를 이용한 채팅 고도화 우선 STOMP에 대해 알아보도

velog.io

 

STOMP + Spring Boot

stomp + spring boot

velog.io