[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 주소가 있습니다.
메시지가 접속한 클라이언트에만 전송되는것을 확인할수 있습니다. 서버의 로그에서도 각 사용자가 Connect할때 UUID를 생성하여 부여한것을 확인할수 있습니다.
Git Source
연관 포스팅
'WEB > Spring' 카테고리의 다른 글
[Spring] 싱글톤 패턴과 싱글톤 컨테이너 개념 (1) | 2023.12.13 |
---|---|
[Spring] MongoDB 연동하고 데이터 관리하기 (2) | 2023.12.13 |
[Spring] 웹소켓(Websocket) 개념과 예제 (0) | 2023.08.11 |
[Spring] 객체지향 설계의 5원칙 (SOLID) #다형성 #의존관계 #인터페이스 분리 (0) | 2023.01.11 |
[Spring] @RequestMapping HTTP Header 데이터 유형 / produces, consume 의미와 역할 (0) | 2022.07.06 |
댓글