본문 바로가기
WEB/Spring

[Spring] 웹소켓(Websocket) 1:1 통신, HandshakeHandler

by 정권이 내 2023. 9. 5.

[Spring] 웹소켓(Websocket) 응용 1:1 통신

 

웹소켓 1:1 통신, @SendToUser

지난번에 작성했던 웹소켓 가이드에서는 서버에 연결된 모든 클라이언트들과 통신하는 방법에 대해 설명했었습니다.

이번에는 1:n이 아닌 1:1 통신에 대해 설명해보겠습니다.

 

웹소켓을 처음 학습한다면 [Spring] 웹소켓(Websocket) 개념과 예제 글을 먼저 보시기 바랍니다.

 

웹소켓으로 1:1 통신을 하기 위해서는 @SendTo어노테이션이 아닌 @SendToUser어노테이션을 사용해서 컨트롤러를 구성합니다. @SendToUser는 WebSocket을 사용하여 특정 클라이언트에게 메시지 전달기능을 구현할수 있게 하는 어노테이션입니다.

1:N 방식에서 사용하던 @SendTo는 같은 주제(topic)를 구독하는 사용자들에게 모두 동일한 메시지를 전송하지만 @SendToUser는 접속한 세션에 대해 1:1 통신을 할수 있도록 합니다.

 

@SendToUser

@SendToUser 어노테이션은 특정 클라이언트와 통신하기 위해 사용하는데 JS에서 웹소켓 구독시 /user경로가 prefix로 붙습니다. Spring WebSocket은 UserDestinationMessageHandler 라는 컴포넌트를 통해 이 개인 큐를 관리하며, /user 프리픽스를 사용하여 개인 큐에 메시지를 보내고 받을 수 있도록 합니다.

이를 통해 각 사용자의 개인 큐에 메시지를 전송할 수 있고 각 사용자가 자신만의 독립적인 메시지 스트림을 가질 수 있도록 해줍니다.

 

HandshakeHandler 란?

이 글의 예제에서는 1:1 통신하는 방법과 함께 웹소켓으로 접속한 클라이언트 들을 구별할수 있도록 STOMP 엔드 포인트 등록시 HandshakeHandler를 적용하여 접속한 클라이언트에 ID를 부여했습니다.

 

HandshakeHandler

HandshakeHandler로 할수 있는 작업은 다음과 같습니다.

  • 사용자 인증 및 권한 부여: 클라이언트에서 웹소켓 연결을 요청할 때, 사용자 인증 및 권한 부여를 위한 정보를 핸드셰이크 과정에서 수집할 수 있습니다. 이 정보를 기반으로 사용자를 인증하고 웹소켓 세션을 설정할 수 있습니다.
  • 커스텀 핸드셰이크 로직: 기본 핸드셰이크 로직 외에도 사용자 정의 핸드셰이크 로직을 구현하여 클라이언트와 서버 간의 연결을 커스터마이즈할 수 있습니다. 예를 들어, 특정 요청 헤더를 확인하거나 특정 조건을 검사하여 연결을 허용 또는 거부할 수 있습니다.
  • 연결 관련 이벤트 로깅: 핸드셰이크 과정에서 발생하는 이벤트를 기록하거나 모니터링할 때 사용될 수 있습니다. 연결 관련 문제를 디버깅하거나 모니터링하는 데 도움이 됩니다.

 

예제 코드

script.js

function connect() {
    let socket = new SockJS('/chat');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);

        stompClient.subscribe('/user/topic/messages', function (message) {
            showMessageOutput(JSON.parse(message.body));
        });
    });
}

코드 길이상 connect 부분만 있습니다. 전체코드는 git에 올려놓았습니다.

  • 화면에서 웹소켓을 연결하기 위한 javascript 이고 /chat은 WebSocketConfig 클래스에서 정의한 endpoint 입니다.
  • stompclient는 /user/topic/messages와 매핑되어 서버로부터 메시지를 받습니다.

 

WebSocketConfig.java

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat")
                .setAllowedOrigins("http://localhost:8080")
                .setHandshakeHandler(new UserHandshakeHandler())
                .withSockJS();
    }
}
  • addEndpoint: /chat은 script.js의 웹소켓 접속과 연결되있습니다.
  • setAllowOrigins: http://localhost:8080으로부터 들어오는 접속만 허용합니다.
  • setHandshakeHandler: 접속 클라이언트를 판단하기 위한 HandshakeHandler를 지정합니다.
  • withSockJS: 웹소켓을 지원하지 않는 브라우저의 호환성을 해결하기위한 메서드입니다.

 

MessageController.java

@Controller
@Slf4j
public class MessageController {

    @MessageMapping("/chat")
    @SendToUser("/topic/messages")
    public OutputMessage getPrivateMessage(final Message message, final Principal principal) throws InterruptedException {
        log.info("message from: {}({}), text: {}", message.getFrom(), principal.getName(), message.getText());

        String time = new SimpleDateFormat("HH:mm").format(new Date());
        Message sendMsg = new Message();
        sendMsg.setFrom("server");
        sendMsg.setText("Hello, " + message.getFrom() + "!");

        return new OutputMessage(sendMsg.getFrom(), sendMsg.getText(), time);
    }
}
  • @MessageMapping 어노테이션에 지정된 엔드포인트로 메시지 매핑 요청이 들어옵니다.
  • @SendToUser 어노테이션에 지정된 경로로 메시지를 전달하는데 @SendToUser를 사용하게 되면 기존 경로에 /user경로가 앞단에 추가적으로 붙게되어 실제로는 /user/topic/messages경로로 메시지를 전송합니다.

 

UserHandshakeHandler.java

@Slf4j
public class UserHandshakeHandler extends DefaultHandshakeHandler {
    @Override
    protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {

        String randomId = UUID.randomUUID().toString();
        log.info("User '{}' Connect!!", randomId);

        return new UserPrincipal(randomId);
    }
}
  • 웹소켓 연결시 클라이언트의 접속정보를 커스터마이징 할수 있는 DefaultHandshakeHandler클래스를 상속하여 접속정보를 확인할수 있는 determinUser 메서드를 오버라이드 합니다.
  • 클라이언트 웹소켓에 연결시 UUID 랜덤값을 생성한 후 UserPrincipal 객체에 담아서 반환하면 나중에 메시지를 주고받을때 클라이언트의 접속정보에서 Pricipal 객체를 확인하여 비교해볼수 있습니다.

 

웹소켓 테스트

위 코드를 바탕으로 프로젝트를 실행하여 웹소켓 테스트를 해보겠습니다. 전체 소스는 글 하단에 Git 주소가 있습니다.

img

메시지가 접속한 클라이언트에만 전송되는것을 확인할수 있습니다. 서버의 로그에서도 각 사용자가 Connect할때 UUID를 생성하여 부여한것을 확인할수 있습니다.

img

 

 

Git Source

 

GitHub - rlatjd1f/spring.websocket.demo2: WebSocket User Session

WebSocket User Session. Contribute to rlatjd1f/spring.websocket.demo2 development by creating an account on GitHub.

github.com

 

연관 포스팅

반응형

댓글