본문 바로가기

학습 기록 (Learning Logs)/Today I Learned

채팅 테스트

 

 

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/ws-chat")
            .setAllowedOriginPatterns("*") // 이거 있어야 외부에서도 접근 가능
            .withSockJS();
}
  • /ws-chat가 맞아.
  • 근데 .withSockJS()를 썼네?

➡️ withSockJS()를 쓰면 클라이언트도 SockJS 프로토콜로 연결해야 해.

즉, 그냥 WebSocket으로는 안 된다.

SockJS는 표준 WebSocket과 handshake 방식이 달라서 Postman이 못 붙는다.

 

 

현재 설정상

   
withSockJS() 붙음 표준 WebSocket 클라이언트 (Postman) 못 붙음
일반 WebSocket 연결 실패
SockJS 클라이언트 필요 맞음

 

 

  • Postman은 SockJS 지원 못 해.
  • JavaScript 코드 또는 Hoppscotch 같은 곳에서 SockJS를 지원하는 코드 짜야 돼.
  • 이건 테스트 귀찮아져서 일반 WebSocket으로 바꾸는 걸 추천해.

 

  • Postman으로 WebSocket 테스트하려면 withSockJS()를 빼야 한다.
  • registry.addEndpoint("/ws-chat").setAllowedOriginPatterns("*");로 수정하고 서버 재시작
  • 그다음 Postman에서 ws://localhost:8080/ws-chat로 연결하면 정상.

 

 

 

 

✅ Postman WebSocket → SockJS는 지원하지 않음

이 설정은 클라이언트가 SockJS 프로토콜로 연결해야 정상 작동해.
근데 Postman은 표준 WebSocket (ws://)만 지원하고, SockJS는 미지원이야.

 

 

🔍 WebSocket vs Socket.IO 차이


 

항목 WebSocket Socket.IO
프로토콜 표준 WebSocket 프로토콜 (ws://, wss://) 자체 프로토콜 (WebSocket 위에서 동작하는 라이브러리)
표준 여부 RFC 6455로 정의된 브라우저 표준 브라우저 표준 아님 (Node.js 기반에서 시작된 확장 기술)
전송 형식 순수 텍스트/바이너리 JSON 기반 자체 포맷 (handshake도 다름)
연결 방식 클라이언트 ↔ 서버 간 단순한 지속 연결 handshake → ping/pong, namespace, room, ack 등 다양한 기능 내장
사용 목적 실시간 단순 통신 (ex. 채팅, 주식시세 등) 실시간 + 복잡한 기능 (ex. 방송 구독, 그룹 채팅, 재접속 등)
호환성 어떤 언어든 WebSocket 구현만 있으면 가능 클라이언트/서버 둘 다 Socket.IO 라이브러리 필요
예시 Spring WebSocket, ws (Node.js) Express + socket.io, NestJS + gateway

➡️ Socket.IOWebSocket보다 상위 추상화된 기능을 제공하는 자체 통신 프레임워크라고 보면 돼.

 

🔌 1. WebSocket 연결

먼저 WebSocket 연결은 오직 한 번 이루어져:

ws://localhost:8080/ws-chat

 

이건 WebSocket 핸드셰이크를 위한 URL이야.
연결되면 클라이언트와 서버는 계속 열린 상태로 메시지를 주고받을 수 있어.

 

 

📦 2. STOMP 프로토콜로 통신

Spring은 WebSocket 위에서 **STOMP (Simple Text Oriented Messaging Protocol)**를 사용해.

STOMP는 메시지를 주고받을 때 "구독"/"발행" 채널을 명확히 구분하는데,
HTTP처럼 "어떤 경로에 무엇을 보내는가"가 STOMP 프레임으로 정의돼.

 

 

 

 


💬 3. 구독(subscribe)과 전송(send) 경로는 STOMP 내부에서 사용됨

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    //    private final StompHandler stompHandler;
//
//    public StompWebSocketConfig(StompHandler stompHandler) {
//        this.stompHandler = stompHandler;
//    }
    // 메세지 받기: /topic
    // 메시지 전송: /app
    // 메시지 브로커 설정 (/topic, /queue 등)

    // WebSocket STOMP 연결 (SockJS 지원)
    // WebSocket 연결: /ws-chat
    // WebSocket 핸드셰이크 엔드포인트 설정
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {

        registry.addEndpoint("/ws-chat")// 클라이언트가 연결할 endpoint
//            .setAllowedOrigins("*")// CORS 허용
            .setAllowedOriginPatterns("*")
//            .withSockJS()
        ;// SockJS 지원
    }

    // 메시지 브로커 설정 (구독 채널: /topic)
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 메시지 구독 prefix
        // 서버 → 클라이언트 메세지 전송
        // prefix, 구독 경로
        // /topic -> broadcast 용 (채팅방 등 다수 사용자 대상)
        // /queue -> 1:1 개인 메시지용 (point-to-point)
        // localhost:8080/ws-chat/topic/chat/{roomId}
        registry.enableSimpleBroker("/topic", "/queue");

        // 메시지 전송 prefix
        // 클라이언트 → 서버 메시지 전송 prefix
        // 메시지 송신 경로 prefix
        registry.setApplicationDestinationPrefixes("/app", "/pub");

        // convertAndSendToUser 위한 prefix
        // 특정 사용자에게 보내기 위한 prefix (e.g. 1:1 알림, 귓속말)
        // localhost:8080/ws-chat/user/queue/messages
        registry.setUserDestinationPrefix("/user");
    }
}
@Service
@RequiredArgsConstructor
public class WebSocketSender {
    /**
     * Spring WebSocket은 STOMP 프로토콜 위에서 작동하는데
     * SimpMessageSendingOperations 구현체 내부적으로 STOMP를 알아서 처리함
     */
    private final SimpMessagingTemplate messagingTemplate;

    /**
     * Test > StompChatController > @MessageMapping("/chat") 이 붙은 클라이언트들이 자동으로 메시지를 받음
     * 세션 ID나 커넥션 직접 안 잡아도 됨 ->직접 WebSocket 세션 추적, 사용자 매핑, 메시지 전송 로직 필요 없음
     */
    public void sendToChatRoom(String key, Object payload) {
        messagingTemplate.convertAndSend("/topic/chat/" + key, payload);
    }

    /**
     * Spring은 WebSocket 연결 시 user 정보까지 매핑해줄 수 있어서
     * 특정 사용자한테만 보내는 것도 쉽게 가능
     * 1:1 알림, 귓속말
     */
    public void sendToUser(String userId, Object payload) {
        messagingTemplate.convertAndSendToUser(userId, "/queue/messages", payload);
    }
}

 

/**
 * STOMP 기반 채팅 브로드캐스트 컨트롤러
 * <p>
 * ✅ 용도:
 * - 간단한 실시간 메시지 브로드캐스팅
 * - 테스트/프로토타입용
 * <p>
 * ❌ 한계:
 * - 인증, 유저별 구독 관리 불가
 * - 세션 추적 및 고급 권한 처리 불가
 */
@Slf4j
@Controller
public class StompChatController {

    // 구독: JS: stompClient.subscribe("/topic/chat/{roomId}", callback)
    // 메세지 전송: JS: stompClient.send("/app/chat/{roomId}", {}, JSON.stringify(message)) -> @MessageMapping("/chat/{roomId}")
    // 브로드캐스팅: @SendTo("/topic/chat/{roomId}")-> 구독 중인 모든 클라이언트에게 전달
    @MessageMapping("/chat/{roomId}")
    //@SendTo: 프로토타입, 간단한 실시간 테스트 용도
    @SendTo("/topic/chat/{roomId}")
    public ChatMessageDto sendMessage(@DestinationVariable String roomId, ChatMessageDto message) {
        log.info("🔔 [TEST-STOMP] message received - roomId: {}, senderId: {}, content: {}", roomId, message.getSenderId(), message.getContent());

        // 메시지에 채팅방 ID 및 전송 시간 설정 (테스트용)
        message.setChatRoomId(UUID.fromString(roomId));
        message.setCreatedAt(LocalDateTime.now());
        return message;
    }
}

 

 

📡 구독 클라이언트가 특정 채널을 구독함 (서버 → 클라 메시지 수신용) /topic/chat/{roomId}
📤 메시지 전송 클라이언트가 서버로 메시지를 보냄 /app/chat/{roomId}

 

 

SEND
destination:/topic/chat/6d30530c-e5ea-4cfd-b52e-a37b1b1d2e41
content-type:application/json

 

https://stackoverflow.com/questions/71696431/how-to-test-stomp-application-using-postman

 

How to test stomp application using postman?

I am making a chat application backend using spring WebSockets. The protocol for communication is STOMP and I am using rabbitmq as a message broker. I want to test my application using postman but ...

stackoverflow.com

 

 

🔥 목표

Spring 서버:

  • ws://localhost:8080/ws-chat
  • STOMP 기반
  • /app/chat/{roomId}로 전송
  • /topic/chat/{roomId} 구독

 

withSockJS()는 제거되어 있어야 해!

이유: telnet, netcat은 pure WebSocket만 가능, SockJS는 handshake 방식이 달라서 못 붙음.

 

 

 

 

curl ❌ WebSocket 불가
telnet / nc ❌ WebSocket 핸드셰이크 못함
✅ wscat WebSocket + STOMP 직접 전송 가능 (강력 추천)

✅ WebSocket 연결 후 STOMP 프레임 수동 전송 (예: netcat + wscat)

하지만 기본 curl, telnet, nc는 WebSocket 핸드셰이크 자체를 못함.

그래서 필요한 도구는 wscat

 

npm install -g wscat

wscat -c ws://localhost:8080/ws-chat

 

 

CONNECT
accept-version:1.2
heart-beat:10000,10000

\x00

✅ 연결되면 CONNECTED 프레임이 옴.

 

 

SUBSCRIBE
id:sub-0
destination:/topic/chat/room123

\x00

 

 

SEND
destination:/app/chat/room123
content-type:application/json

{"senderId":"abc-123","content":"hello from wscat"}\x00