websocket 만 사용할 경우, websocket session 에 직접 메시지를 전달해줘야만 했다.
"Simple Text Oriented Messaging Protocol"
메시지 전송을 효율적으로 하기 위해 나온 프로토콜
이를 통해, 메세지를 공급하는 주체와 소비하는 주체를 분리해 제공할 수 있다.
예시를 들어보자.
우체통(Topic, 채팅방)
이 있다고 가정할 때,
집배원(Publisher, 메세지 송신자)
은 신문을 우체통(Topic, 채팅방)에 배달할 수 있다.
구독자(Subscriber, 메세지 수신자)
는 우체통(Topic, 채팅방)에 신문이 배달되는 것을 기다렸다가 빼서 볼 수 있다.(단, 구독자는 다수가 될 수 있다)
이를 채팅방의 경우로 바꿔보자.
채팅방 생성
: pub/sub 구현을 위한 Topic 이 생성됨
채팅방 입장
: Topic 을 구독
채팅방에서 메세지를 송수신
: 해당 Topic 으로 메세지를 송신(pub), 수신(sub)
중간 다리 역할을 한다.
(유튜브의 구독을 생각하면 편하다)
Subscriber 가 채팅창 채널을 구독하겠다고 Broker 에게 알린다
해당 채팅창 채널을 구독하고 있는 Sender 가 메시지를 보낸다
Broker 는 해당 채팅창 채널을 구독하는 모든 클라이언트들에게 메시지를 전달한다
Broker 는 외부 Message Broker(RabbitMQ, ActiveMQ)를 사용해도 된다.
websocket 에서는 각 connection 별로 따로따로 handler 를 구현해야만 했으나,
stomp 는 @Controller 가 적용된 객체를 이용해 조직적으로 관리가 가능
라우팅?
- 패킷에 포함된 주소 등의 상세 정보를 이용하여 목적지까지 데이터 또는 메세지를 체계적으로 다른 네트워크에 전달하는 경로 선택, 스위칭하는 과정.
- 데이터가 전달되는 과정에서 여러 네트워크들을 통과해야하는 경우가 생길 수 있는데, 여러 네트워크들의 연결을 담당하고 있는 라우터 장비가 데이터의 목적지가 어디인지 확인하여 빠르고 정확한 길을 찾아 전달해주는 것
Spring Security 를 통해, 메시지를 보호할 수 있다.
메세지의 헤더에 값을 줄 수 있기 때문에, 헤더 값을 기반으로 통신 시 인증 처리를 구현할 수 있다.
dependencies {
// stomp
implementation 'org.webjars:stomp-websocket:2.3.4'
}
@RequiredArgsConstructor
@Configuration
@EnableWebSocket // webSocket 활성화
@EnableWebSocketMessageBroker // stomp 활성화
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { // WebSocketMessageBrokerConfigurer 인터페이스를 구현
// 1. Stomp websocket 연결
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-stomp")
.setAllowedOriginPatterns("*");
}
// Stomp 사용을 위한 Message Broker 설정
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/sub"); // 2.
registry.setApplicationDestinationPrefixes("pub"); // 3.
}
}
1.
websocket 의 연결 주소는 엔드포인트에 설정한 대로, ws://localhost8080/ws-stomp
이 된다.
Postman 에서 테스트 할 경우, .setAllowedOriginPatterns("*") 설정이 있으면 websocket 연결이 불가능했다.
1) 에서와 같은 이유로 .withSockJS(); 설정은 해주지 않았다.
2.
3.
@Controller
@RequiredArgsConstructor
public class MessageController {
private final SimpMessageSendingOperations messagingTemplate;
@MessageMapping("/chat/message") // 1.
public void message(MessageDto message) {
messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message); // 2.
}
}
1.
2.
@RestController
@RequestMapping("/message")
@RequiredArgsConstructor
public class MessageRoomController {
private final MessageRoomService messageRoomService;
// 쪽지방 생성
@PostMapping("/room")
public MessageRoomDto createRoom(@AuthenticationPrincipal UserDetailsImpl userDetails) {
return messageRoomService.createRoom(userDetails.getUser());
}
// 쪽지방 전체 조회
@GetMapping("/rooms")
public List<MessageRoomDto> findAllRoom() {
return messageRoomService.findAllRoom();
}
// 쪽지방 삭제
@DeleteMapping("room/{id}")
public MsgResponseDto deleteRoom(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetails) {
return messageRoomService.deleteRoom(id, userDetails.getUser());
}
}
참고: [Spring Boot] WebSocket과 채팅 (3) - STOMP
참고: [spring 멀티 채팅] websocket,sockjs,STOMP 이용한 채팅 기능-STOMP채팅구현(2)
참고: [Server] Websocket과 Stomp
참고: Stomp 이란?
참고: Spring websocket chatting server(2) - Stomp로 채팅서버 고도화하기